Skip to content

Commit 0221b8b

Browse files
committed
Add support for * width and precision in printf()
If * is used for width/precision in printf, then the width/precision is provided by a printf argument instead of being part of the format string. Semantics generally match those of printf in C. This can be used to easily reproduce PHP's float printing behavior: // Locale-sensitive using precision ini setting. // Used prior to PHP 8.0. sprintf("%.*G", (int) ini_get('precision'), $float); // Locale-insensitive using precision ini setting. // Used since to PHP 8.0. sprintf("%.*H", (int) ini_get('precision'), $float); // Locale-insensitive using serialize_precision ini setting. // Used in serialize(), json_encode() etc. sprintf("%.*H", (int) ini_get('serialize_precision'), $float); Closes GH-5432.
1 parent 2423288 commit 0221b8b

File tree

3 files changed

+196
-17
lines changed

3 files changed

+196
-17
lines changed

UPGRADING

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,13 @@ PHP 8.0 UPGRADE NOTES
539539
. printf() and friends how support the %h and %H format specifiers. These
540540
are the same as %g and %G, but always use "." as the decimal separator,
541541
rather than determining it through the LC_NUMERIC locale.
542+
. printf() and friends now support using "*" as width or precision, in which
543+
case the width/precision is passed as an argument to printf. This also
544+
allows using precision -1 with %g, %G, %h and %H. For example, the following
545+
code can be used to reproduce PHP's default floating point formatting:
546+
547+
printf("%.*H", (int) ini_get("precision"), $float);
548+
printf("%.*H", (int) ini_get("serialize_precision"), $float);
542549

543550
- Zip:
544551
. Extension updated to version 1.19.0

ext/standard/formatted_print.c

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,27 @@ php_sprintf_getnumber(char **buffer, size_t *len)
373373
}
374374
/* }}} */
375375

376+
#define ARG_NUM_NEXT -1
377+
#define ARG_NUM_INVALID -2
378+
379+
int php_sprintf_get_argnum(char **format, size_t *format_len) {
380+
char *temppos = *format;
381+
while (isdigit((int) *temppos)) temppos++;
382+
if (*temppos != '$') {
383+
return ARG_NUM_NEXT;
384+
}
385+
386+
int argnum = php_sprintf_getnumber(format, format_len);
387+
if (argnum <= 0) {
388+
zend_value_error("Argument number must be greater than zero");
389+
return ARG_NUM_INVALID;
390+
}
391+
392+
(*format)++; /* skip the '$' */
393+
(*format_len)--;
394+
return argnum - 1;
395+
}
396+
376397
/* php_formatted_print() {{{
377398
* New sprintf implementation for PHP.
378399
*
@@ -447,23 +468,12 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
447468
*format, format - Z_STRVAL_P(z_format)));
448469
if (isalpha((int)*format)) {
449470
width = precision = 0;
450-
argnum = currarg++;
471+
argnum = ARG_NUM_NEXT;
451472
} else {
452473
/* first look for argnum */
453-
temppos = format;
454-
while (isdigit((int)*temppos)) temppos++;
455-
if (*temppos == '$') {
456-
argnum = php_sprintf_getnumber(&format, &format_len);
457-
458-
if (argnum <= 0) {
459-
zend_value_error("Argument number must be greater than zero");
460-
goto fail;
461-
}
462-
argnum--;
463-
format++; /* skip the '$' */
464-
format_len--;
465-
} else {
466-
argnum = currarg++;
474+
argnum = php_sprintf_get_argnum(&format, &format_len);
475+
if (argnum == ARG_NUM_INVALID) {
476+
goto fail;
467477
}
468478

469479
/* after argnum comes modifiers */
@@ -498,7 +508,34 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
498508

499509

500510
/* after modifiers comes width */
501-
if (isdigit((int)*format)) {
511+
if (*format == '*') {
512+
format++;
513+
format_len--;
514+
515+
int width_argnum = php_sprintf_get_argnum(&format, &format_len);
516+
if (width_argnum == ARG_NUM_INVALID) {
517+
goto fail;
518+
}
519+
if (width_argnum == ARG_NUM_NEXT) {
520+
width_argnum = currarg++;
521+
}
522+
if (width_argnum >= argc) {
523+
max_missing_argnum = MAX(max_missing_argnum, width_argnum);
524+
continue;
525+
}
526+
tmp = &args[width_argnum];
527+
ZVAL_DEREF(tmp);
528+
if (Z_TYPE_P(tmp) != IS_LONG) {
529+
zend_value_error("Width must be an integer");
530+
goto fail;
531+
}
532+
if (Z_LVAL_P(tmp) < 0 || Z_LVAL_P(tmp) > INT_MAX) {
533+
zend_value_error("Width must be greater than zero and less than %d", INT_MAX);
534+
goto fail;
535+
}
536+
width = Z_LVAL_P(tmp);
537+
adjusting |= ADJ_WIDTH;
538+
} else if (isdigit((int)*format)) {
502539
PRINTF_DEBUG(("sprintf: getting width\n"));
503540
if ((width = php_sprintf_getnumber(&format, &format_len)) < 0) {
504541
zend_value_error("Width must be greater than zero and less than %d", INT_MAX);
@@ -515,7 +552,35 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
515552
format++;
516553
format_len--;
517554
PRINTF_DEBUG(("sprintf: getting precision\n"));
518-
if (isdigit((int)*format)) {
555+
if (*format == '*') {
556+
format++;
557+
format_len--;
558+
559+
int prec_argnum = php_sprintf_get_argnum(&format, &format_len);
560+
if (prec_argnum == ARG_NUM_INVALID) {
561+
goto fail;
562+
}
563+
if (prec_argnum == ARG_NUM_NEXT) {
564+
prec_argnum = currarg++;
565+
}
566+
if (prec_argnum >= argc) {
567+
max_missing_argnum = MAX(max_missing_argnum, prec_argnum);
568+
continue;
569+
}
570+
tmp = &args[prec_argnum];
571+
ZVAL_DEREF(tmp);
572+
if (Z_TYPE_P(tmp) != IS_LONG) {
573+
zend_value_error("Precision must be an integer");
574+
goto fail;
575+
}
576+
if (Z_LVAL_P(tmp) < -1 || Z_LVAL_P(tmp) > INT_MAX) {
577+
zend_value_error("Precision must be between -1 and %d", INT_MAX);
578+
goto fail;
579+
}
580+
precision = Z_LVAL_P(tmp);
581+
adjusting |= ADJ_PRECISION;
582+
expprec = 1;
583+
} else if (isdigit((int)*format)) {
519584
if ((precision = php_sprintf_getnumber(&format, &format_len)) < 0) {
520585
zend_value_error("Precision must be greater than zero and less than %d", INT_MAX);
521586
goto fail;
@@ -537,11 +602,20 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
537602
}
538603
PRINTF_DEBUG(("sprintf: format character='%c'\n", *format));
539604

605+
if (argnum == ARG_NUM_NEXT) {
606+
argnum = currarg++;
607+
}
540608
if (argnum >= argc) {
541609
max_missing_argnum = MAX(max_missing_argnum, argnum);
542610
continue;
543611
}
544612

613+
if (expprec && precision == -1
614+
&& *format != 'g' && *format != 'G' && *format != 'h' && *format != 'H') {
615+
zend_value_error("Precision -1 is only supported for %%g, %%G, %%h and %%H");
616+
goto fail;
617+
}
618+
545619
/* now we expect to find a type specifier */
546620
tmp = &args[argnum];
547621
switch (*format) {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
--TEST--
2+
Star width and precision in sprintf()
3+
--FILE--
4+
<?php
5+
6+
7+
$f = 1.23456789012345678;
8+
$fx = 1.23456789012345678e100;
9+
var_dump($f, $fx);
10+
11+
printf("%.*f\n", 10, $f);
12+
printf("%.*G\n", 10, $f);
13+
printf("%.*g\n", -1, $fx);
14+
printf("%.*G\n", -1, $fx);
15+
printf("%.*h\n", -1, $fx);
16+
printf("%.*H\n", -1, $fx);
17+
printf("%.*s\n", 3, "foobar");
18+
echo "\n";
19+
20+
printf("%*f\n", 10, $f);
21+
printf("%*G\n", 10, $f);
22+
printf("%*s\n", 10, "foobar");
23+
echo "\n";
24+
25+
printf("%*.*f\n", 10, 3, $f);
26+
printf("%*.*G\n", 10, 3, $f);
27+
printf("%*.*s\n", 10, 3, "foobar");
28+
echo "\n";
29+
30+
printf("%1$.*2\$f\n", $f, 10);
31+
printf("%.*2\$f\n", $f, 10);
32+
printf("%2$.*f\n", 10, $f);
33+
printf("%1$*2\$f\n", $f, 10);
34+
printf("%*2\$f\n", $f, 10);
35+
printf("%2$*f\n", 10, $f);
36+
printf("%1$*2$.*3\$f\n", $f, 10, 3);
37+
printf("%*2$.*3\$f\n", $f, 10, 3);
38+
printf("%3$*.*f\n", 10, 3, $f);
39+
echo "\n";
40+
41+
try {
42+
printf("%.*G\n", "foo", 1.5);
43+
} catch (ValueError $e) {
44+
echo $e->getMessage(), "\n";
45+
}
46+
47+
try {
48+
printf("%.*G\n", -100, 1.5);
49+
} catch (ValueError $e) {
50+
echo $e->getMessage(), "\n";
51+
}
52+
53+
try {
54+
printf("%.*s\n", -1, "Foo");
55+
} catch (ValueError $e) {
56+
echo $e->getMessage(), "\n";
57+
}
58+
59+
try {
60+
printf("%*G\n", -1, $f);
61+
} catch (ValueError $e) {
62+
echo $e->getMessage(), "\n";
63+
}
64+
65+
?>
66+
--EXPECT--
67+
float(1.2345678901234567)
68+
float(1.2345678901234569E+100)
69+
1.2345678901
70+
1.23456789
71+
1.2345678901234569e+100
72+
1.2345678901234569E+100
73+
1.2345678901234569e+100
74+
1.2345678901234569E+100
75+
foo
76+
77+
1.234568
78+
1.23457
79+
foobar
80+
81+
1.235
82+
1.23
83+
foo
84+
85+
1.2345678901
86+
1.2345678901
87+
1.2345678901
88+
1.234568
89+
1.234568
90+
1.234568
91+
1.235
92+
1.235
93+
1.235
94+
95+
Precision must be an integer
96+
Precision must be between -1 and 2147483647
97+
Precision -1 is only supported for %g, %G, %h and %H
98+
Width must be greater than zero and less than 2147483647

0 commit comments

Comments
 (0)