Skip to content

Commit 4627a4a

Browse files
committed
Make float to string casts locale-independent
1 parent 657f756 commit 4627a4a

File tree

15 files changed

+302
-32
lines changed

15 files changed

+302
-32
lines changed

Zend/zend.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ static ZEND_INI_MH(OnUpdateAssertions) /* {{{ */
167167

168168
ZEND_INI_BEGIN()
169169
ZEND_INI_ENTRY("error_reporting", NULL, ZEND_INI_ALL, OnUpdateErrorReporting)
170+
STD_ZEND_INI_BOOLEAN("debug_locale_sensitive_float_casts", "0", ZEND_INI_ALL, OnUpdateBool, debug_locale_sensitive_float_casts, zend_executor_globals, executor_globals)
170171
STD_ZEND_INI_ENTRY("zend.assertions", "1", ZEND_INI_ALL, OnUpdateAssertions, assertions, zend_executor_globals, executor_globals)
171172
ZEND_INI_ENTRY3_EX("zend.enable_gc", "1", ZEND_INI_ALL, OnUpdateGCEnabled, NULL, NULL, NULL, zend_gc_enabled_displayer_cb)
172173
STD_ZEND_INI_BOOLEAN("zend.multibyte", "0", ZEND_INI_PERDIR, OnUpdateBool, multibyte, zend_compiler_globals, compiler_globals)

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");

ext/intl/tests/bug67052-win32.phpt

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

2626
?>
2727
--EXPECT--
28-
1234567,891
28+
1234567.891
2929
de-de

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
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
--TEST--
2+
Test that floats are converted to string locale independently
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+
function print_float(float $f)
17+
{
18+
echo "- casting:\n";
19+
echo $f . "\n";
20+
echo strval($f) . "\n";
21+
$g = $f;
22+
settype($g, "string");
23+
echo $g . "\n";
24+
25+
echo "- *printf functions:\n";
26+
printf("%.2f\n", $f);
27+
printf("%.2F\n", $f);
28+
echo sprintf("%.2f", $f) . "\n";
29+
echo sprintf("%.2F", $f) . "\n";
30+
31+
echo "- export/import:\n";
32+
echo var_export($f, true) . "\n";
33+
echo serialize($f) . "\n";
34+
echo json_encode($f) . "\n";
35+
36+
echo "- debugging:\n";
37+
echo print_r($f, true) . "\n";
38+
var_dump($f);
39+
debug_zval_dump($f);
40+
41+
echo "- other:\n";
42+
echo implode([$f]) . "\n";
43+
}
44+
45+
setlocale(LC_ALL, "C");
46+
echo "C locale:\n";
47+
48+
print_float(3.14);
49+
50+
setlocale(
51+
LC_ALL,
52+
"german", "de", "de_DE", "de_DE.ISO8859-1", "de_DE.ISO_8859-1", "de_DE.UTF-8",
53+
"french", "fr", "fr_FR", "fr_FR.ISO8859-1", "fr_FR.ISO_8859-1", "fr_FR.UTF-8",
54+
);
55+
echo "\nde_DE locale:\n";
56+
57+
print_float(3.14);
58+
59+
?>
60+
--EXPECT--
61+
C locale:
62+
- casting:
63+
3.14
64+
3.14
65+
3.14
66+
- *printf functions:
67+
3.14
68+
3.14
69+
3.14
70+
3.14
71+
- export/import:
72+
3.14
73+
d:3.14;
74+
3.14
75+
- debugging:
76+
3.14
77+
float(3.14)
78+
float(3.14)
79+
- other:
80+
3.14
81+
82+
de_DE locale:
83+
- casting:
84+
3.14
85+
3.14
86+
3.14
87+
- *printf functions:
88+
3,14
89+
3.14
90+
3,14
91+
3.14
92+
- export/import:
93+
3.14
94+
d:3.14;
95+
3.14
96+
- debugging:
97+
3.14
98+
float(3.14)
99+
float(3.14)
100+
- other:
101+
3.14

0 commit comments

Comments
 (0)