Skip to content

Commit 4a81658

Browse files
kocsismateGirgias
andcommitted
Make float to string casts locale-independent
From now on, float to string casting will always behave locale-independently. RFC: https://wiki.php.net/rfc/locale_independent_float_to_string Closes GH-5224 Co-authored-by: George Peter Banyard <girgias@php.net>
1 parent 6e2cd97 commit 4a81658

File tree

15 files changed

+175
-40
lines changed

15 files changed

+175
-40
lines changed

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ PHP 8.0 UPGRADE NOTES
189189
array, resource or non-overloaded object. The only exception to this is the
190190
array + array merge operation, which remains supported.
191191
RFC: https://wiki.php.net/rfc/arithmetic_operator_type_checks
192+
. Float to string casting will now always behave locale-independently.
193+
RFC: https://wiki.php.net/rfc/locale_independent_float_to_string
192194

193195
- COM:
194196
. Removed the ability to import case-insensitive constants from type

Zend/zend_operators.c

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -605,20 +605,6 @@ ZEND_API void ZEND_FASTCALL convert_to_boolean(zval *op) /* {{{ */
605605
}
606606
/* }}} */
607607

608-
ZEND_API void ZEND_FASTCALL _convert_to_cstring(zval *op) /* {{{ */
609-
{
610-
if (Z_TYPE_P(op) == IS_DOUBLE) {
611-
zend_string *str;
612-
double dval = Z_DVAL_P(op);
613-
614-
str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), dval);
615-
ZVAL_NEW_STR(op, str);
616-
} else {
617-
_convert_to_string(op);
618-
}
619-
}
620-
/* }}} */
621-
622608
ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op) /* {{{ */
623609
{
624610
try_again:
@@ -648,8 +634,8 @@ ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op) /* {{{ */
648634
zend_string *str;
649635
double dval = Z_DVAL_P(op);
650636

651-
str = zend_strpprintf(0, "%.*G", (int) EG(precision), dval);
652-
/* %G already handles removing trailing zeros from the fractional part, yay */
637+
str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), dval);
638+
653639
ZVAL_NEW_STR(op, str);
654640
break;
655641
}
@@ -953,7 +939,7 @@ static zend_always_inline zend_string* __zval_get_string_func(zval *op, zend_boo
953939
return zend_long_to_str(Z_LVAL_P(op));
954940
}
955941
case IS_DOUBLE: {
956-
return zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op));
942+
return zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), Z_DVAL_P(op));
957943
}
958944
case IS_ARRAY:
959945
zend_error(E_WARNING, "Array to string conversion");

Zend/zend_operators.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,6 @@ ZEND_API int ZEND_FASTCALL increment_function(zval *op1);
257257
ZEND_API int ZEND_FASTCALL decrement_function(zval *op2);
258258

259259
ZEND_API void ZEND_FASTCALL convert_scalar_to_number(zval *op);
260-
ZEND_API void ZEND_FASTCALL _convert_to_cstring(zval *op);
261260
ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op);
262261
ZEND_API void ZEND_FASTCALL convert_to_long(zval *op);
263262
ZEND_API void ZEND_FASTCALL convert_to_double(zval *op);
@@ -340,7 +339,6 @@ static zend_always_inline zend_bool try_convert_to_string(zval *op) {
340339
#define _zval_get_double_func(op) zval_get_double_func(op)
341340
#define _zval_get_string_func(op) zval_get_string_func(op)
342341

343-
#define convert_to_cstring(op) if (Z_TYPE_P(op) != IS_STRING) { _convert_to_cstring((op)); }
344342
#define convert_to_string(op) if (Z_TYPE_P(op) != IS_STRING) { _convert_to_string((op)); }
345343

346344

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/pgsql/pgsql.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1987,7 +1987,7 @@ PHP_FUNCTION(pg_query_params)
19871987
zval tmp_val;
19881988

19891989
ZVAL_COPY(&tmp_val, tmp);
1990-
convert_to_cstring(&tmp_val);
1990+
convert_to_string(&tmp_val);
19911991
if (Z_TYPE(tmp_val) != IS_STRING) {
19921992
php_error_docref(NULL, E_WARNING,"Error converting parameter");
19931993
zval_ptr_dtor(&tmp_val);

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

0 commit comments

Comments
 (0)