Skip to content

Commit d26c0a5

Browse files
committed
Make float to string casts locale-independent
1 parent 1bba691 commit d26c0a5

File tree

16 files changed

+265
-109
lines changed

16 files changed

+265
-109
lines changed

Zend/zend.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ ZEND_INI_BEGIN()
176176
STD_ZEND_INI_BOOLEAN("zend.signal_check", "0", ZEND_INI_SYSTEM, OnUpdateBool, check, zend_signal_globals_t, zend_signal_globals)
177177
#endif
178178
STD_ZEND_INI_BOOLEAN("zend.exception_ignore_args", "0", ZEND_INI_ALL, OnUpdateBool, exception_ignore_args, zend_executor_globals, executor_globals)
179+
STD_ZEND_INI_BOOLEAN("zend.debug_locale_sensitive_float_casts", "0", ZEND_INI_ALL, OnUpdateBool, debug_locale_sensitive_float_casts, zend_executor_globals, executor_globals)
179180
ZEND_INI_END()
180181

181182
ZEND_API size_t zend_vspprintf(char **pbuf, size_t max_len, const char *format, va_list ap) /* {{{ */

Zend/zend_globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ struct _zend_executor_globals {
237237
HashTable weakrefs;
238238

239239
zend_bool exception_ignore_args;
240+
zend_bool debug_locale_sensitive_float_casts;
240241

241242
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
242243
};

Zend/zend_operators.c

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -549,15 +549,7 @@ ZEND_API void ZEND_FASTCALL convert_to_boolean(zval *op) /* {{{ */
549549

550550
ZEND_API void ZEND_FASTCALL _convert_to_cstring(zval *op) /* {{{ */
551551
{
552-
if (Z_TYPE_P(op) == IS_DOUBLE) {
553-
zend_string *str;
554-
double dval = Z_DVAL_P(op);
555-
556-
str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), dval);
557-
ZVAL_NEW_STR(op, str);
558-
} else {
559-
_convert_to_string(op);
560-
}
552+
_convert_to_string(op);
561553
}
562554
/* }}} */
563555

@@ -590,8 +582,17 @@ ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op) /* {{{ */
590582
zend_string *str;
591583
double dval = Z_DVAL_P(op);
592584

593-
str = zend_strpprintf(0, "%.*G", (int) EG(precision), dval);
594-
/* %G already handles removing trailing zeros from the fractional part, yay */
585+
str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), dval);
586+
587+
if (EG(debug_locale_sensitive_float_casts)) {
588+
zend_string *original_str = zend_strpprintf(0, "%.*G", (int) EG(precision), dval);
589+
590+
if (!zend_string_equals(str, original_str)) {
591+
zend_error(E_WARNING, "Locale-independent float to string conversion");
592+
}
593+
zend_string_release(original_str);
594+
}
595+
595596
ZVAL_NEW_STR(op, str);
596597
break;
597598
}
@@ -900,7 +901,18 @@ static zend_always_inline zend_string* __zval_get_string_func(zval *op, zend_boo
900901
return zend_long_to_str(Z_LVAL_P(op));
901902
}
902903
case IS_DOUBLE: {
903-
return zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op));
904+
zend_string *str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), Z_DVAL_P(op));
905+
906+
if (EG(debug_locale_sensitive_float_casts)) {
907+
zend_string *original_str = zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op));
908+
909+
if (!zend_string_equals(str, original_str)) {
910+
zend_error(E_WARNING, "Locale-independent float to string conversion");
911+
}
912+
zend_string_release(original_str);
913+
}
914+
915+
return str;
904916
}
905917
case IS_ARRAY:
906918
zend_error(E_WARNING, "Array to string conversion");

Zend/zend_strtod.c

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@
159159
* floating-point numbers and flushes underflows to zero rather
160160
* than implementing gradual underflow, then you must also #define
161161
* Sudden_Underflow.
162-
* #define USE_LOCALE to use the current locale's decimal_point value.
163162
* #define SET_INEXACT if IEEE arithmetic is being used and extra
164163
* computation should be done to set the inexact flag when the
165164
* result is inexact and avoid setting inexact when the result
@@ -206,10 +205,6 @@ static void Bug(const char *message) {
206205
#include "stdlib.h"
207206
#include "string.h"
208207

209-
#ifdef USE_LOCALE
210-
#include "locale.h"
211-
#endif
212-
213208
#ifdef Honor_FLT_ROUNDS
214209
#ifndef Trust_FLT_ROUNDS
215210
#include <fenv.h>
@@ -1827,25 +1822,6 @@ gethex( CONST char **sp, U *rvp, int rounding, int sign)
18271822
#endif
18281823
#endif /*}}*/
18291824
};
1830-
#ifdef USE_LOCALE
1831-
int i;
1832-
#ifdef NO_LOCALE_CACHE
1833-
const unsigned char *decimalpoint = (unsigned char*)
1834-
localeconv()->decimal_point;
1835-
#else
1836-
const unsigned char *decimalpoint;
1837-
static unsigned char *decimalpoint_cache;
1838-
if (!(s0 = decimalpoint_cache)) {
1839-
s0 = (unsigned char*)localeconv()->decimal_point;
1840-
if ((decimalpoint_cache = (unsigned char*)
1841-
MALLOC(strlen((CONST char*)s0) + 1))) {
1842-
strcpy((char*)decimalpoint_cache, (CONST char*)s0);
1843-
s0 = decimalpoint_cache;
1844-
}
1845-
}
1846-
decimalpoint = s0;
1847-
#endif
1848-
#endif
18491825

18501826
/**** if (!hexdig['0']) hexdig_init(); ****/
18511827
havedig = 0;
@@ -1861,17 +1837,9 @@ gethex( CONST char **sp, U *rvp, int rounding, int sign)
18611837
havedig++;
18621838
else {
18631839
zret = 1;
1864-
#ifdef USE_LOCALE
1865-
for(i = 0; decimalpoint[i]; ++i) {
1866-
if (s[i] != decimalpoint[i])
1867-
goto pcheck;
1868-
}
1869-
decpt = s += i;
1870-
#else
18711840
if (*s != '.')
18721841
goto pcheck;
18731842
decpt = ++s;
1874-
#endif
18751843
if (!hexdig[*s])
18761844
goto pcheck;
18771845
while(*s == '0')
@@ -1883,17 +1851,8 @@ gethex( CONST char **sp, U *rvp, int rounding, int sign)
18831851
}
18841852
while(hexdig[*s])
18851853
s++;
1886-
#ifdef USE_LOCALE
1887-
if (*s == *decimalpoint && !decpt) {
1888-
for(i = 1; decimalpoint[i]; ++i) {
1889-
if (s[i] != decimalpoint[i])
1890-
goto pcheck;
1891-
}
1892-
decpt = s += i;
1893-
#else
18941854
if (*s == '.' && !decpt) {
18951855
decpt = ++s;
1896-
#endif
18971856
while(hexdig[*s])
18981857
s++;
18991858
}/*}*/
@@ -1982,19 +1941,9 @@ gethex( CONST char **sp, U *rvp, int rounding, int sign)
19821941
x = b->x;
19831942
n = 0;
19841943
L = 0;
1985-
#ifdef USE_LOCALE
1986-
for(i = 0; decimalpoint[i+1]; ++i);
1987-
#endif
19881944
while(s1 > s0) {
1989-
#ifdef USE_LOCALE
1990-
if (*--s1 == decimalpoint[i]) {
1991-
s1 -= i;
1992-
continue;
1993-
}
1994-
#else
19951945
if (*--s1 == '.')
19961946
continue;
1997-
#endif
19981947
if (n == ULbits) {
19991948
*x++ = L;
20001949
L = 0;
@@ -2566,10 +2515,6 @@ zend_strtod
25662515
}
25672516
#endif /*}}*/
25682517
#endif /*}*/
2569-
#ifdef USE_LOCALE
2570-
CONST char *s2;
2571-
#endif
2572-
25732518
sign = nz0 = nz1 = nz = bc.dplen = bc.uflchk = 0;
25742519
dval(&rv) = 0.;
25752520
for(s = s00;;s++) switch(*s) {
@@ -2622,25 +2567,6 @@ zend_strtod
26222567
bc.dp0 = bc.dp1 = s - s0;
26232568
for(s1 = s; s1 > s0 && *--s1 == '0'; )
26242569
++nz1;
2625-
#ifdef USE_LOCALE
2626-
s1 = localeconv()->decimal_point;
2627-
if (c == *s1) {
2628-
c = '.';
2629-
if (*++s1) {
2630-
s2 = s;
2631-
for(;;) {
2632-
if (*++s2 != *s1) {
2633-
c = 0;
2634-
break;
2635-
}
2636-
if (!*++s1) {
2637-
s = s2;
2638-
break;
2639-
}
2640-
}
2641-
}
2642-
}
2643-
#endif
26442570
if (c == '.') {
26452571
c = *++s;
26462572
bc.dp1 = s - s0;

Zend/zend_strtod_int.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,6 @@ typedef unsigned long int uint32_t;
6464
# endif
6565
#endif
6666

67-
#ifdef USE_LOCALE
68-
#undef USE_LOCALE
69-
#endif
70-
7167
#ifndef NO_INFNAN_CHECK
7268
#define NO_INFNAN_CHECK
7369
#endif

ext/intl/tests/bug67052.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ ut_run();
3030

3131
?>
3232
--EXPECT--
33-
1234567,891
33+
1234567.891
3434
de_DE.UTF-8

ext/json/tests/bug41403.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ echo "Done\n";
2323
--EXPECT--
2424
array(1) {
2525
[0]=>
26-
float(2,1)
26+
float(2.1)
2727
}
2828
array(1) {
2929
[0]=>
30-
float(0,15)
30+
float(0.15)
3131
}
3232
array(1) {
3333
[0]=>
34-
float(123,13452345)
34+
float(123.13452345)
3535
}
3636
array(2) {
3737
[0]=>

ext/pdo/pdo_stmt.c

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -230,15 +230,8 @@ static int really_register_bound_param(struct pdo_bound_param_data *param, pdo_s
230230
}
231231

232232
if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_STR && param->max_value_len <= 0 && !Z_ISNULL_P(parameter)) {
233-
if (Z_TYPE_P(parameter) == IS_DOUBLE) {
234-
char *p;
235-
int len = zend_spprintf_unchecked(&p, 0, "%.*H", (int) EG(precision), Z_DVAL_P(parameter));
236-
ZVAL_STRINGL(parameter, p, len);
237-
efree(p);
238-
} else {
239-
if (!try_convert_to_string(parameter)) {
240-
return 0;
241-
}
233+
if (!try_convert_to_string(parameter)) {
234+
return 0;
242235
}
243236
} else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_INT && (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE)) {
244237
convert_to_long(parameter);

ext/soap/tests/bugs/bug39815.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ setlocale(LC_ALL,"en_US","en_US.ISO8859-1");
4141
var_dump($x->test());
4242
echo $x->__getLastResponse();
4343
--EXPECT--
44-
float(123,456)
44+
float(123.456)
4545
<?xml version="1.0" encoding="UTF-8"?>
4646
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://testuri.org" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:testResponse><return xsi:type="xsd:float">123.456</return></ns1:testResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
4747
float(123.456)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
Test that float to string and string to float casts are consistent
3+
--SKIPIF--
4+
<?php
5+
if (!setlocale(
6+
LC_ALL,
7+
"german", "de", "de_DE", "de_DE.ISO8859-1", "de_DE.ISO_8859-1", "de_DE.UTF-8",
8+
"french", "fr", "fr_FR", "fr_FR.ISO8859-1", "fr_FR.ISO_8859-1", "fr_FR.UTF-8",
9+
)) {
10+
die("skip locale needed for this test is not supported on this platform");
11+
}
12+
?>
13+
--FILE--
14+
<?php
15+
16+
setlocale(
17+
LC_ALL,
18+
"german", "de", "de_DE", "de_DE.ISO8859-1", "de_DE.ISO_8859-1", "de_DE.UTF-8",
19+
"french", "fr", "fr_FR", "fr_FR.ISO8859-1", "fr_FR.ISO_8859-1", "fr_FR.UTF-8",
20+
);
21+
22+
$float = 1/3;
23+
$string = (string) $float;
24+
$float = (float) $string;
25+
26+
printf("%.2f", $float);
27+
28+
?>
29+
--EXPECT--
30+
0,33

0 commit comments

Comments
 (0)