Skip to content

Commit

Permalink
Add additional double to string APIs
Browse files Browse the repository at this point in the history
zend_double_to_str() converts a double to string in the way that
(string) would (using %.*H using precision).

smart_str_append_double() provides some more fine control over
the precision, and whether a zero fraction should be appeneded
for whole numbers.

A caveat here is that raw calls to zend_gcvt and going through
s*printf has slightly different behavior for the degenarate
precision=0 case. zend_gcvt will add a dummy E+0 in that case,
while s*printf convert this to precision=1 and will not. I'm
going with the s*printf behavior here, which is more common,
but does result in a minor change to the precision.phpt test.
  • Loading branch information
nikic committed Aug 2, 2021
1 parent e14fbc8 commit 6048481
Show file tree
Hide file tree
Showing 8 changed files with 48 additions and 45 deletions.
20 changes: 9 additions & 11 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1409,11 +1409,6 @@ static ZEND_COLD void zend_ast_export_if_stmt(smart_str *str, zend_ast_list *lis

static ZEND_COLD void zend_ast_export_zval(smart_str *str, zval *zv, int priority, int indent)
{
zend_long idx;
zend_string *key;
zval *val;
int first;

ZVAL_DEREF(zv);
switch (Z_TYPE_P(zv)) {
case IS_NULL:
Expand All @@ -1429,21 +1424,23 @@ static ZEND_COLD void zend_ast_export_zval(smart_str *str, zval *zv, int priorit
smart_str_append_long(str, Z_LVAL_P(zv));
break;
case IS_DOUBLE:
key = zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(zv));
smart_str_appendl(str, ZSTR_VAL(key), ZSTR_LEN(key));
zend_string_release_ex(key, 0);
smart_str_append_double(
str, Z_DVAL_P(zv), (int) EG(precision), /* zero_fraction */ false);
break;
case IS_STRING:
smart_str_appendc(str, '\'');
zend_ast_export_str(str, Z_STR_P(zv));
smart_str_appendc(str, '\'');
break;
case IS_ARRAY:
case IS_ARRAY: {
zend_long idx;
zend_string *key;
zval *val;
bool first = true;
smart_str_appendc(str, '[');
first = 1;
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(zv), idx, key, val) {
if (first) {
first = 0;
first = false;
} else {
smart_str_appends(str, ", ");
}
Expand All @@ -1459,6 +1456,7 @@ static ZEND_COLD void zend_ast_export_zval(smart_str *str, zval *zv, int priorit
} ZEND_HASH_FOREACH_END();
smart_str_appendc(str, ']');
break;
}
case IS_CONSTANT_AST:
zend_ast_export_ex(str, Z_ASTVAL_P(zv), priority, indent);
break;
Expand Down
6 changes: 3 additions & 3 deletions Zend/zend_exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -519,11 +519,11 @@ static void _build_trace_args(zval *arg, smart_str *str) /* {{{ */
smart_str_append_long(str, Z_LVAL_P(arg));
smart_str_appends(str, ", ");
break;
case IS_DOUBLE: {
smart_str_append_printf(str, "%.*G", (int) EG(precision), Z_DVAL_P(arg));
case IS_DOUBLE:
smart_str_append_double(
str, Z_DVAL_P(arg), (int) EG(precision), /* zero_fraction */ false);
smart_str_appends(str, ", ");
break;
}
case IS_ARRAY:
smart_str_appends(str, "Array, ");
break;
Expand Down
35 changes: 17 additions & 18 deletions Zend/zend_operators.c
Original file line number Diff line number Diff line change
Expand Up @@ -637,19 +637,12 @@ ZEND_API void ZEND_FASTCALL _convert_to_string(zval *op) /* {{{ */
ZVAL_NEW_STR(op, str);
break;
}
case IS_LONG: {
case IS_LONG:
ZVAL_STR(op, zend_long_to_str(Z_LVAL_P(op)));
break;
}
case 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);
case IS_DOUBLE:
ZVAL_NEW_STR(op, zend_double_to_str(Z_DVAL_P(op)));
break;
}
case IS_ARRAY:
zend_error(E_WARNING, "Array to string conversion");
zval_ptr_dtor(op);
Expand Down Expand Up @@ -917,15 +910,12 @@ static zend_always_inline zend_string* __zval_get_string_func(zval *op, bool try
return ZSTR_EMPTY_ALLOC();
case IS_TRUE:
return ZSTR_CHAR('1');
case IS_RESOURCE: {
case IS_RESOURCE:
return zend_strpprintf(0, "Resource id #" ZEND_LONG_FMT, (zend_long)Z_RES_HANDLE_P(op));
}
case IS_LONG: {
case IS_LONG:
return zend_long_to_str(Z_LVAL_P(op));
}
case IS_DOUBLE: {
return zend_strpprintf_unchecked(0, "%.*H", (int) EG(precision), Z_DVAL_P(op));
}
case IS_DOUBLE:
return zend_double_to_str(Z_DVAL_P(op));
case IS_ARRAY:
zend_error(E_WARNING, "Array to string conversion");
return (try && UNEXPECTED(EG(exception))) ?
Expand Down Expand Up @@ -2089,7 +2079,7 @@ static int compare_double_to_string(double dval, zend_string *str) /* {{{ */
return ZEND_NORMALIZE_BOOL(dval - str_dval);
}

zend_string *dval_as_str = zend_strpprintf(0, "%.*G", (int) EG(precision), dval);
zend_string *dval_as_str = zend_double_to_str(dval);
int cmp_result = zend_binary_strcmp(
ZSTR_VAL(dval_as_str), ZSTR_LEN(dval_as_str), ZSTR_VAL(str), ZSTR_LEN(str));
zend_string_release(dval_as_str);
Expand Down Expand Up @@ -3148,6 +3138,15 @@ ZEND_API zend_string* ZEND_FASTCALL zend_i64_to_str(int64_t num)
}
}

ZEND_API zend_string* ZEND_FASTCALL zend_double_to_str(double num)
{
char buf[ZEND_DOUBLE_MAX_LENGTH];
/* Model snprintf precision behavior. */
int precision = (int) EG(precision);
zend_gcvt(num, precision ? precision : 1, '.', 'E', buf);
return zend_string_init(buf, strlen(buf), 0);
}

ZEND_API zend_uchar ZEND_FASTCALL is_numeric_str_function(const zend_string *str, zend_long *lval, double *dval) /* {{{ */
{
return is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), lval, dval, false);
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_operators.h
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,7 @@ ZEND_API zend_string* ZEND_FASTCALL zend_long_to_str(zend_long num);
ZEND_API zend_string* ZEND_FASTCALL zend_ulong_to_str(zend_ulong num);
ZEND_API zend_string* ZEND_FASTCALL zend_u64_to_str(uint64_t num);
ZEND_API zend_string* ZEND_FASTCALL zend_i64_to_str(int64_t num);
ZEND_API zend_string* ZEND_FASTCALL zend_double_to_str(double num);

static zend_always_inline void zend_unwrap_reference(zval *op) /* {{{ */
{
Expand Down
11 changes: 11 additions & 0 deletions Zend/zend_smart_str.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ ZEND_API void ZEND_FASTCALL smart_str_append_escaped(smart_str *str, const char
}
}

ZEND_API void ZEND_FASTCALL smart_str_append_double(
smart_str *str, double num, int precision, bool zero_fraction) {
char buf[ZEND_DOUBLE_MAX_LENGTH];
/* Model snprintf precision behavior. */
zend_gcvt(num, precision ? precision : 1, '.', 'E', buf);
smart_str_appends(str, buf);
if (zero_fraction && zend_finite(num) && !strchr(buf, '.')) {
smart_str_appendl(str, ".0", 2);
}
}

ZEND_API void smart_str_append_printf(smart_str *dest, const char *format, ...) {
va_list arg;
va_start(arg, format);
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_smart_str.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ BEGIN_EXTERN_C()
ZEND_API void ZEND_FASTCALL smart_str_erealloc(smart_str *str, size_t len);
ZEND_API void ZEND_FASTCALL smart_str_realloc(smart_str *str, size_t len);
ZEND_API void ZEND_FASTCALL smart_str_append_escaped(smart_str *str, const char *s, size_t l);
/* If zero_fraction is true, then a ".0" will be added to numbers that would not otherwise
* have a fractional part and look like integers. */
ZEND_API void ZEND_FASTCALL smart_str_append_double(
smart_str *str, double num, int precision, bool zero_fraction);
ZEND_API void smart_str_append_printf(smart_str *dest, const char *format, ...)
ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);

Expand Down
14 changes: 2 additions & 12 deletions ext/standard/var.c
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,6 @@ static void php_object_element_export(zval *zv, zend_ulong index, zend_string *k
PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */
{
HashTable *myht;
char tmp_str[ZEND_DOUBLE_MAX_LENGTH];
zend_string *ztmp, *ztmp2;
zend_ulong index;
zend_string *key;
Expand Down Expand Up @@ -508,17 +507,8 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */
smart_str_append_long(buf, Z_LVAL_P(struc));
break;
case IS_DOUBLE:
zend_gcvt(Z_DVAL_P(struc), (int)PG(serialize_precision), '.', 'E', tmp_str);
smart_str_appends(buf, tmp_str);
/* Without a decimal point, PHP treats a number literal as an int.
* This check even works for scientific notation, because the
* mantissa always contains a decimal point.
* We need to check for finiteness, because INF, -INF and NAN
* must not have a decimal point added.
*/
if (zend_finite(Z_DVAL_P(struc)) && NULL == strchr(tmp_str, '.')) {
smart_str_appendl(buf, ".0", 2);
}
smart_str_append_double(
buf, Z_DVAL_P(struc), (int) PG(serialize_precision), /* zero_fraction */ true);
break;
case IS_STRING:
ztmp = php_addcslashes(Z_STR_P(struc), "'\\", 2);
Expand Down
2 changes: 1 addition & 1 deletion tests/basic/precision.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ OUTPUTS
string(60) "a:4:{i:0;d:1.0E+8;i:1;d:3.0E+0;i:2;d:1.0E+103;i:3;d:1.0E+1;}"
array (
0 => 1.0E+8,
1 => 3.0E+0,
1 => 3.0,
2 => 1.0E+103,
3 => 1.0E+1,
)
Expand Down

0 comments on commit 6048481

Please sign in to comment.