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>
array, resource or non-overloaded object. The only exception to this is the
array + array merge operation, which remains supported.
RFC: https://wiki.php.net/rfc/arithmetic_operator_type_checks
+ . Float to string casting will now always behave locale-independently.
+ RFC: https://wiki.php.net/rfc/locale_independent_float_to_string
- COM:
. Removed the ability to import case-insensitive constants from type
}
/* }}} */
-ZEND_API void ZEND_FASTCALL _convert_to_cstring(zval *op) /* {{{ */
-{
- if (Z_TYPE_P(op) == IS_DOUBLE) {
- zend_string *str;
- double dval = Z_DVAL_P(op);
-
- str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), dval);
- ZVAL_NEW_STR(op, str);
- } else {
- _convert_to_string(op);
- }
-}
-/* }}} */
-
ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op) /* {{{ */
{
try_again:
zend_string *str;
double dval = Z_DVAL_P(op);
- str = zend_strpprintf(0, "%.*G", (int) EG(precision), dval);
- /* %G already handles removing trailing zeros from the fractional part, yay */
+ str = zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), dval);
+
ZVAL_NEW_STR(op, str);
break;
}
return zend_long_to_str(Z_LVAL_P(op));
}
case IS_DOUBLE: {
- return zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op));
+ return zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), Z_DVAL_P(op));
}
case IS_ARRAY:
zend_error(E_WARNING, "Array to string conversion");
ZEND_API int ZEND_FASTCALL decrement_function(zval *op2);
ZEND_API void ZEND_FASTCALL convert_scalar_to_number(zval *op);
-ZEND_API void ZEND_FASTCALL _convert_to_cstring(zval *op);
ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op);
ZEND_API void ZEND_FASTCALL convert_to_long(zval *op);
ZEND_API void ZEND_FASTCALL convert_to_double(zval *op);
#define _zval_get_double_func(op) zval_get_double_func(op)
#define _zval_get_string_func(op) zval_get_string_func(op)
-#define convert_to_cstring(op) if (Z_TYPE_P(op) != IS_STRING) { _convert_to_cstring((op)); }
#define convert_to_string(op) if (Z_TYPE_P(op) != IS_STRING) { _convert_to_string((op)); }
?>
--EXPECT--
-1234567,891
+1234567.891
de-de
?>
--EXPECT--
-1234567,891
+1234567.891
de_DE.UTF-8
--EXPECT--
array(1) {
[0]=>
- float(2,1)
+ float(2.1)
}
array(1) {
[0]=>
- float(0,15)
+ float(0.15)
}
array(1) {
[0]=>
- float(123,13452345)
+ float(123.13452345)
}
array(2) {
[0]=>
}
if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_STR && param->max_value_len <= 0 && !Z_ISNULL_P(parameter)) {
- if (Z_TYPE_P(parameter) == IS_DOUBLE) {
- char *p;
- int len = zend_spprintf_unchecked(&p, 0, "%.*H", (int) EG(precision), Z_DVAL_P(parameter));
- ZVAL_STRINGL(parameter, p, len);
- efree(p);
- } else {
- if (!try_convert_to_string(parameter)) {
- return 0;
- }
+ if (!try_convert_to_string(parameter)) {
+ return 0;
}
} else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_INT && (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE)) {
convert_to_long(parameter);
zval tmp_val;
ZVAL_COPY(&tmp_val, tmp);
- convert_to_cstring(&tmp_val);
+ convert_to_string(&tmp_val);
if (Z_TYPE(tmp_val) != IS_STRING) {
php_error_docref(NULL, E_WARNING,"Error converting parameter");
zval_ptr_dtor(&tmp_val);
var_dump($x->test());
echo $x->__getLastResponse();
--EXPECT--
-float(123,456)
+float(123.456)
<?xml version="1.0" encoding="UTF-8"?>
<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>
float(123.456)
--- /dev/null
+--TEST--
+Test that floats are converted to string locale independently
+--SKIPIF--
+<?php
+
+if (!setlocale
+ (LC_ALL,
+ "german", "de", "de_DE", "de_DE.ISO8859-1", "de_DE.ISO_8859-1", "de_DE.UTF-8",
+ "french", "fr", "fr_FR", "fr_FR.ISO8859-1", "fr_FR.ISO_8859-1", "fr_FR.UTF-8",
+ )) {
+ die("skip - locale needed for this test is not supported on this platform");
+}
+
+if (!extension_loaded("json")) {
+ print "skip - test requires the json extension";
+}
+
+?>
+--FILE--
+<?php
+
+function print_float(float $f)
+{
+ echo "- casting:\n";
+ echo $f . "\n";
+ echo strval($f) . "\n";
+ $g = $f;
+ settype($g, "string");
+ echo $g . "\n";
+
+ echo "- *printf functions:\n";
+ printf("%.2f\n", $f);
+ printf("%.2F\n", $f);
+ echo sprintf("%.2f", $f) . "\n";
+ echo sprintf("%.2F", $f) . "\n";
+
+ echo "- export/import:\n";
+ echo var_export($f, true) . "\n";
+ echo serialize($f) . "\n";
+ echo json_encode($f) . "\n";
+
+ echo "- debugging:\n";
+ echo print_r($f, true) . "\n";
+ var_dump($f);
+ debug_zval_dump($f);
+
+ echo "- other:\n";
+ echo implode([$f]) . "\n";
+}
+
+setlocale(LC_ALL, "C");
+echo "C locale:\n";
+
+print_float(3.14);
+
+setlocale(
+ LC_ALL,
+ "german", "de", "de_DE", "de_DE.ISO8859-1", "de_DE.ISO_8859-1", "de_DE.UTF-8",
+ "french", "fr", "fr_FR", "fr_FR.ISO8859-1", "fr_FR.ISO_8859-1", "fr_FR.UTF-8",
+);
+echo "\nde_DE locale:\n";
+
+print_float(3.14);
+
+?>
+--EXPECT--
+C locale:
+- casting:
+3.14
+3.14
+3.14
+- *printf functions:
+3.14
+3.14
+3.14
+3.14
+- export/import:
+3.14
+d:3.14;
+3.14
+- debugging:
+3.14
+float(3.14)
+float(3.14)
+- other:
+3.14
+
+de_DE locale:
+- casting:
+3.14
+3.14
+3.14
+- *printf functions:
+3,14
+3.14
+3,14
+3.14
+- export/import:
+3.14
+d:3.14;
+3.14
+- debugging:
+3.14
+float(3.14)
+float(3.14)
+- other:
+3.14
php_printf("%sint(" ZEND_LONG_FMT ")\n", COMMON, Z_LVAL_P(struc));
break;
case IS_DOUBLE:
- php_printf("%sfloat(%.*G)\n", COMMON, (int) PG(serialize_precision), Z_DVAL_P(struc));
+ php_printf_unchecked("%sfloat(%.*H)\n", COMMON, (int) PG(serialize_precision), Z_DVAL_P(struc));
break;
case IS_STRING:
php_printf("%sstring(%zd) \"", COMMON, Z_STRLEN_P(struc));
php_printf("%sint(" ZEND_LONG_FMT ")\n", COMMON, Z_LVAL_P(struc));
break;
case IS_DOUBLE:
- php_printf("%sfloat(%.*G)\n", COMMON, (int) PG(serialize_precision), Z_DVAL_P(struc));
+ php_printf_unchecked("%sfloat(%.*H)\n", COMMON, (int) PG(serialize_precision), Z_DVAL_P(struc));
break;
case IS_STRING:
php_printf("%sstring(%zd) \"", COMMON, Z_STRLEN_P(struc));
}
/* }}} */
+/* {{{ php_printf_unchecked
+ */
+PHPAPI size_t php_printf_unchecked(const char *format, ...)
+{
+ va_list args;
+ size_t ret;
+ char *buffer;
+ size_t size;
+
+ va_start(args, format);
+ size = vspprintf(&buffer, 0, format, args);
+ ret = PHPWRITE(buffer, size);
+ efree(buffer);
+ va_end(args);
+
+ return ret;
+}
+/* }}} */
+
static zend_string *escape_html(const char *buffer, size_t buffer_len) {
zend_string *result = php_escape_html_entities_ex(
(const unsigned char *) buffer, buffer_len, 0, ENT_COMPAT,
BEGIN_EXTERN_C()
void phperror(char *error);
PHPAPI size_t php_write(void *buf, size_t size);
-PHPAPI size_t php_printf(const char *format, ...) PHP_ATTRIBUTE_FORMAT(printf, 1,
- 2);
+PHPAPI size_t php_printf(const char *format, ...) PHP_ATTRIBUTE_FORMAT(printf, 1, 2);
+PHPAPI size_t php_printf_unchecked(const char *format, ...);
PHPAPI int php_get_module_initialized(void);
#ifdef HAVE_SYSLOG_H
#include "php_syslog.h"
--- /dev/null
+--TEST--
+Test that float to string and string to float casts are consistent
+--SKIPIF--
+<?php
+if (!setlocale(
+ LC_ALL,
+ "german", "de", "de_DE", "de_DE.ISO8859-1", "de_DE.ISO_8859-1", "de_DE.UTF-8",
+ "french", "fr", "fr_FR", "fr_FR.ISO8859-1", "fr_FR.ISO_8859-1", "fr_FR.UTF-8",
+)) {
+ die("skip locale needed for this test is not supported on this platform");
+}
+?>
+--FILE--
+<?php
+
+setlocale(
+ LC_ALL,
+ "german", "de", "de_DE", "de_DE.ISO8859-1", "de_DE.ISO_8859-1", "de_DE.UTF-8",
+ "french", "fr", "fr_FR", "fr_FR.ISO8859-1", "fr_FR.ISO_8859-1", "fr_FR.UTF-8",
+);
+
+$float = 1/3;
+$string = (string) $float;
+$float = (float) $string;
+
+printf("%.2f", $float);
+
+?>
+--EXPECT--
+0,33
echo (float)"3.14", "\n";
?>
--EXPECT--
-3,14
+3.14