-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fix #76093: Format strings w/o loss of precision w/ FORMAT_TYPE_DECIMAL #7782
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -34,11 +34,22 @@ PHP_FUNCTION( numfmt_format ) | |||||||||||||||||||||
int32_t formatted_len = USIZE(format_buf); | ||||||||||||||||||||||
FORMATTER_METHOD_INIT_VARS; | ||||||||||||||||||||||
|
||||||||||||||||||||||
object = getThis(); | ||||||||||||||||||||||
|
||||||||||||||||||||||
/* Parse parameters. */ | ||||||||||||||||||||||
if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "On|l", | ||||||||||||||||||||||
&object, NumberFormatter_ce_ptr, &number, &type ) == FAILURE ) | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
RETURN_THROWS(); | ||||||||||||||||||||||
if (object) { | ||||||||||||||||||||||
ZEND_PARSE_PARAMETERS_START(1, 2) | ||||||||||||||||||||||
Z_PARAM_STR_OR_NUMBER(number) | ||||||||||||||||||||||
Z_PARAM_OPTIONAL | ||||||||||||||||||||||
Z_PARAM_LONG(type) | ||||||||||||||||||||||
ZEND_PARSE_PARAMETERS_END(); | ||||||||||||||||||||||
} else { | ||||||||||||||||||||||
ZEND_PARSE_PARAMETERS_START(2, 3) | ||||||||||||||||||||||
Z_PARAM_OBJECT_OF_CLASS(object, NumberFormatter_ce_ptr) | ||||||||||||||||||||||
Z_PARAM_STR_OR_NUMBER(number) | ||||||||||||||||||||||
Z_PARAM_OPTIONAL | ||||||||||||||||||||||
Z_PARAM_LONG(type) | ||||||||||||||||||||||
ZEND_PARSE_PARAMETERS_END(); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/* Fetch the object. */ | ||||||||||||||||||||||
|
@@ -53,10 +64,21 @@ PHP_FUNCTION( numfmt_format ) | |||||||||||||||||||||
case IS_DOUBLE: | ||||||||||||||||||||||
type = FORMAT_TYPE_DOUBLE; | ||||||||||||||||||||||
break; | ||||||||||||||||||||||
case IS_STRING: | ||||||||||||||||||||||
type = FORMAT_TYPE_DECIMAL; | ||||||||||||||||||||||
break; | ||||||||||||||||||||||
EMPTY_SWITCH_DEFAULT_CASE(); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
// Avoid losing precision on 32-bit platforms where PHP's "long" isn't | ||||||||||||||||||||||
// as long as the FORMAT_TYPE_INT64 which is requested. | ||||||||||||||||||||||
if(sizeof(zend_long) < 8) { | ||||||||||||||||||||||
if (Z_TYPE_P(number) == IS_STRING && type == FORMAT_TYPE_INT64) { | ||||||||||||||||||||||
type = FORMAT_TYPE_DECIMAL; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
Comment on lines
+76
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should better be a compile directive:
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
switch(type) { | ||||||||||||||||||||||
case FORMAT_TYPE_INT32: | ||||||||||||||||||||||
convert_to_long(number); | ||||||||||||||||||||||
|
@@ -76,7 +98,13 @@ PHP_FUNCTION( numfmt_format ) | |||||||||||||||||||||
|
||||||||||||||||||||||
case FORMAT_TYPE_INT64: | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
int64_t value = (Z_TYPE_P(number) == IS_DOUBLE)?(int64_t)Z_DVAL_P(number):Z_LVAL_P(number); | ||||||||||||||||||||||
int64_t value; | ||||||||||||||||||||||
if (Z_TYPE_P(number) == IS_DOUBLE) { | ||||||||||||||||||||||
value = (int64_t)Z_DVAL_P(number); | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK, this conversion is undefined behavior if the double value is outside the range of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you could be justified in broadening the expected test result if platforms really do crazy things with this conversion. But IMO if you've asked for TYPE_INT64 you are explicitly asking for the result of casting the double to the int64, however that's defined on your platform. And as you note, this isn't new behavior in this patch, this is exactly how it worked previously. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As it turns out, your comment was prescient: the s390 architecture got |
||||||||||||||||||||||
} else { | ||||||||||||||||||||||
convert_to_long(number); | ||||||||||||||||||||||
value = Z_LVAL_P(number); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
formatted_len = unum_formatInt64(FORMATTER_OBJECT(nfo), value, formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo)); | ||||||||||||||||||||||
if (INTL_DATA_ERROR_CODE(nfo) == U_BUFFER_OVERFLOW_ERROR) { | ||||||||||||||||||||||
intl_error_reset(INTL_DATA_ERROR_P(nfo)); | ||||||||||||||||||||||
|
@@ -104,6 +132,23 @@ PHP_FUNCTION( numfmt_format ) | |||||||||||||||||||||
INTL_METHOD_CHECK_STATUS( nfo, "Number formatting failed" ); | ||||||||||||||||||||||
break; | ||||||||||||||||||||||
|
||||||||||||||||||||||
case FORMAT_TYPE_DECIMAL: | ||||||||||||||||||||||
if (!try_convert_to_string(number)) { | ||||||||||||||||||||||
RETURN_THROWS(); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
// Convert string as a DecimalNumber, so we don't lose precision | ||||||||||||||||||||||
formatted_len = unum_formatDecimal(FORMATTER_OBJECT(nfo), Z_STRVAL_P(number), Z_STRLEN_P(number), formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo)); | ||||||||||||||||||||||
if (INTL_DATA_ERROR_CODE(nfo) == U_BUFFER_OVERFLOW_ERROR) { | ||||||||||||||||||||||
intl_error_reset(INTL_DATA_ERROR_P(nfo)); | ||||||||||||||||||||||
formatted = eumalloc(formatted_len); | ||||||||||||||||||||||
unum_formatDecimal(FORMATTER_OBJECT(nfo), Z_STRVAL_P(number), Z_STRLEN_P(number), formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo)); | ||||||||||||||||||||||
if (U_FAILURE( INTL_DATA_ERROR_CODE(nfo) ) ) { | ||||||||||||||||||||||
efree(formatted); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
INTL_METHOD_CHECK_STATUS( nfo, "Number formatting failed" ); | ||||||||||||||||||||||
break; | ||||||||||||||||||||||
|
||||||||||||||||||||||
default: | ||||||||||||||||||||||
zend_argument_value_error(3, "must be a NumberFormatter::TYPE_* constant"); | ||||||||||||||||||||||
RETURN_THROWS(); | ||||||||||||||||||||||
|
@@ -116,7 +161,7 @@ PHP_FUNCTION( numfmt_format ) | |||||||||||||||||||||
/* {{{ Format a number as currency. */ | ||||||||||||||||||||||
PHP_FUNCTION( numfmt_format_currency ) | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
double number; | ||||||||||||||||||||||
zval *number; | ||||||||||||||||||||||
UChar format_buf[32]; | ||||||||||||||||||||||
UChar* formatted = format_buf; | ||||||||||||||||||||||
int32_t formatted_len = USIZE(format_buf); | ||||||||||||||||||||||
|
@@ -126,11 +171,20 @@ PHP_FUNCTION( numfmt_format_currency ) | |||||||||||||||||||||
int32_t scurrency_len = 0; | ||||||||||||||||||||||
FORMATTER_METHOD_INIT_VARS; | ||||||||||||||||||||||
|
||||||||||||||||||||||
object = getThis(); | ||||||||||||||||||||||
|
||||||||||||||||||||||
/* Parse parameters. */ | ||||||||||||||||||||||
if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Ods", | ||||||||||||||||||||||
&object, NumberFormatter_ce_ptr, &number, ¤cy, ¤cy_len ) == FAILURE ) | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
RETURN_THROWS(); | ||||||||||||||||||||||
if (object) { | ||||||||||||||||||||||
ZEND_PARSE_PARAMETERS_START(2, 2) | ||||||||||||||||||||||
Z_PARAM_STR_OR_NUMBER(number) | ||||||||||||||||||||||
Z_PARAM_STRING(currency, currency_len) | ||||||||||||||||||||||
ZEND_PARSE_PARAMETERS_END(); | ||||||||||||||||||||||
} else { | ||||||||||||||||||||||
ZEND_PARSE_PARAMETERS_START(3, 3) | ||||||||||||||||||||||
Z_PARAM_OBJECT_OF_CLASS(object, NumberFormatter_ce_ptr) | ||||||||||||||||||||||
Z_PARAM_STR_OR_NUMBER(number) | ||||||||||||||||||||||
Z_PARAM_STRING(currency, currency_len) | ||||||||||||||||||||||
ZEND_PARSE_PARAMETERS_END(); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/* Fetch the object. */ | ||||||||||||||||||||||
|
@@ -139,9 +193,16 @@ PHP_FUNCTION( numfmt_format_currency ) | |||||||||||||||||||||
/* Convert currency to UTF-16. */ | ||||||||||||||||||||||
intl_convert_utf8_to_utf16(&scurrency, &scurrency_len, currency, currency_len, &INTL_DATA_ERROR_CODE(nfo)); | ||||||||||||||||||||||
INTL_METHOD_CHECK_STATUS( nfo, "Currency conversion to UTF-16 failed" ); | ||||||||||||||||||||||
unum_setTextAttribute(FORMATTER_OBJECT(nfo), UNUM_CURRENCY_CODE, scurrency, scurrency_len, &INTL_DATA_ERROR_CODE(nfo)); | ||||||||||||||||||||||
INTL_METHOD_CHECK_STATUS( nfo, "Setting currency code failed" ); | ||||||||||||||||||||||
|
||||||||||||||||||||||
/* Format the number using a fixed-length buffer. */ | ||||||||||||||||||||||
formatted_len = unum_formatDoubleCurrency(FORMATTER_OBJECT(nfo), number, scurrency, formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo)); | ||||||||||||||||||||||
if (Z_TYPE_P(number) == IS_STRING) { | ||||||||||||||||||||||
formatted_len = unum_formatDecimal(FORMATTER_OBJECT(nfo), Z_STRVAL_P(number), Z_STRLEN_P(number), formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo)); | ||||||||||||||||||||||
} else { | ||||||||||||||||||||||
convert_to_double(number); | ||||||||||||||||||||||
formatted_len = unum_formatDoubleCurrency(FORMATTER_OBJECT(nfo), Z_DVAL_P(number), scurrency, formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo)); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/* If the buffer turned out to be too small | ||||||||||||||||||||||
* then allocate another buffer dynamically | ||||||||||||||||||||||
|
@@ -150,7 +211,11 @@ PHP_FUNCTION( numfmt_format_currency ) | |||||||||||||||||||||
if (INTL_DATA_ERROR_CODE(nfo) == U_BUFFER_OVERFLOW_ERROR) { | ||||||||||||||||||||||
intl_error_reset(INTL_DATA_ERROR_P(nfo)); | ||||||||||||||||||||||
formatted = eumalloc(formatted_len); | ||||||||||||||||||||||
unum_formatDoubleCurrency(FORMATTER_OBJECT(nfo), number, scurrency, formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo)); | ||||||||||||||||||||||
if (Z_TYPE_P(number) == IS_STRING) { | ||||||||||||||||||||||
unum_formatDecimal(FORMATTER_OBJECT(nfo), Z_STRVAL_P(number), Z_STRLEN_P(number), formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo)); | ||||||||||||||||||||||
} else { | ||||||||||||||||||||||
unum_formatDoubleCurrency(FORMATTER_OBJECT(nfo), Z_DVAL_P(number), scurrency, formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo)); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
if( U_FAILURE( INTL_DATA_ERROR_CODE((nfo)) ) ) { | ||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in this case it might make sense to stick with
zend_parse_method_parameters()
, but replace then
specifier withz
, and to replace theEMPTY_SWITCH_DEFAULT_CASE()
some lines below to throw a type error manually.