]> granicus.if.org Git - php/commitdiff
Add support for * width and precision in printf()
authorNikita Popov <nikita.ppv@gmail.com>
Tue, 21 Apr 2020 14:54:22 +0000 (16:54 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Wed, 27 May 2020 08:42:25 +0000 (10:42 +0200)
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.

UPGRADING
ext/standard/formatted_print.c
ext/standard/tests/strings/sprintf_star.phpt [new file with mode: 0644]

index 6f3abbfc7c8b557b0890bc5bbe63b74c465ef7e3..81b945d2045416fa12e0a3ddd02506b0092c615a 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -539,6 +539,13 @@ PHP 8.0 UPGRADE NOTES
   . printf() and friends how support the %h and %H format specifiers. These
     are the same as %g and %G, but always use "." as the decimal separator,
     rather than determining it through the LC_NUMERIC locale.
+  . printf() and friends now support using "*" as width or precision, in which
+    case the width/precision is passed as an argument to printf. This also
+    allows using precision -1 with %g, %G, %h and %H. For example, the following
+    code can be used to reproduce PHP's default floating point formatting:
+
+        printf("%.*H", (int) ini_get("precision"), $float);
+        printf("%.*H", (int) ini_get("serialize_precision"), $float);
 
 - Zip:
   . Extension updated to version 1.19.0
index f32f9f8abf4b70c90c13710b0d73cd63d270852c..b39cc32860d60bcaeef9d1c5b77cc5f7f5c0082e 100644 (file)
@@ -373,6 +373,27 @@ php_sprintf_getnumber(char **buffer, size_t *len)
 }
 /* }}} */
 
+#define ARG_NUM_NEXT -1
+#define ARG_NUM_INVALID -2
+
+int php_sprintf_get_argnum(char **format, size_t *format_len) {
+       char *temppos = *format;
+       while (isdigit((int) *temppos)) temppos++;
+       if (*temppos != '$') {
+               return ARG_NUM_NEXT;
+       }
+
+       int argnum = php_sprintf_getnumber(format, format_len);
+       if (argnum <= 0) {
+               zend_value_error("Argument number must be greater than zero");
+               return ARG_NUM_INVALID;
+       }
+
+       (*format)++;  /* skip the '$' */
+       (*format_len)--;
+       return argnum - 1;
+}
+
 /* php_formatted_print() {{{
  * New sprintf implementation for PHP.
  *
@@ -447,23 +468,12 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
                                                  *format, format - Z_STRVAL_P(z_format)));
                        if (isalpha((int)*format)) {
                                width = precision = 0;
-                               argnum = currarg++;
+                               argnum = ARG_NUM_NEXT;
                        } else {
                                /* first look for argnum */
-                               temppos = format;
-                               while (isdigit((int)*temppos)) temppos++;
-                               if (*temppos == '$') {
-                                       argnum = php_sprintf_getnumber(&format, &format_len);
-
-                                       if (argnum <= 0) {
-                                               zend_value_error("Argument number must be greater than zero");
-                                               goto fail;
-                                       }
-                                       argnum--;
-                                       format++;  /* skip the '$' */
-                                       format_len--;
-                               } else {
-                                       argnum = currarg++;
+                               argnum = php_sprintf_get_argnum(&format, &format_len);
+                               if (argnum == ARG_NUM_INVALID) {
+                                       goto fail;
                                }
 
                                /* after argnum comes modifiers */
@@ -498,7 +508,34 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
 
 
                                /* after modifiers comes width */
-                               if (isdigit((int)*format)) {
+                               if (*format == '*') {
+                                       format++;
+                                       format_len--;
+
+                                       int width_argnum = php_sprintf_get_argnum(&format, &format_len);
+                                       if (width_argnum == ARG_NUM_INVALID) {
+                                               goto fail;
+                                       }
+                                       if (width_argnum == ARG_NUM_NEXT) {
+                                               width_argnum = currarg++;
+                                       }
+                                       if (width_argnum >= argc) {
+                                               max_missing_argnum = MAX(max_missing_argnum, width_argnum);
+                                               continue;
+                                       }
+                                       tmp = &args[width_argnum];
+                                       ZVAL_DEREF(tmp);
+                                       if (Z_TYPE_P(tmp) != IS_LONG) {
+                                               zend_value_error("Width must be an integer");
+                                               goto fail;
+                                       }
+                                       if (Z_LVAL_P(tmp) < 0 || Z_LVAL_P(tmp) > INT_MAX) {
+                                               zend_value_error("Width must be greater than zero and less than %d", INT_MAX);
+                                               goto fail;
+                                       }
+                                       width = Z_LVAL_P(tmp);
+                                       adjusting |= ADJ_WIDTH;
+                               } else if (isdigit((int)*format)) {
                                        PRINTF_DEBUG(("sprintf: getting width\n"));
                                        if ((width = php_sprintf_getnumber(&format, &format_len)) < 0) {
                                                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
                                        format++;
                                        format_len--;
                                        PRINTF_DEBUG(("sprintf: getting precision\n"));
-                                       if (isdigit((int)*format)) {
+                                       if (*format == '*') {
+                                               format++;
+                                               format_len--;
+
+                                               int prec_argnum = php_sprintf_get_argnum(&format, &format_len);
+                                               if (prec_argnum == ARG_NUM_INVALID) {
+                                                       goto fail;
+                                               }
+                                               if (prec_argnum == ARG_NUM_NEXT) {
+                                                       prec_argnum = currarg++;
+                                               }
+                                               if (prec_argnum >= argc) {
+                                                       max_missing_argnum = MAX(max_missing_argnum, prec_argnum);
+                                                       continue;
+                                               }
+                                               tmp = &args[prec_argnum];
+                                               ZVAL_DEREF(tmp);
+                                               if (Z_TYPE_P(tmp) != IS_LONG) {
+                                                       zend_value_error("Precision must be an integer");
+                                                       goto fail;
+                                               }
+                                               if (Z_LVAL_P(tmp) < -1 || Z_LVAL_P(tmp) > INT_MAX) {
+                                                       zend_value_error("Precision must be between -1 and %d", INT_MAX);
+                                                       goto fail;
+                                               }
+                                               precision = Z_LVAL_P(tmp);
+                                               adjusting |= ADJ_PRECISION;
+                                               expprec = 1;
+                                       } else if (isdigit((int)*format)) {
                                                if ((precision = php_sprintf_getnumber(&format, &format_len)) < 0) {
                                                        zend_value_error("Precision must be greater than zero and less than %d", INT_MAX);
                                                        goto fail;
@@ -537,11 +602,20 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
                        }
                        PRINTF_DEBUG(("sprintf: format character='%c'\n", *format));
 
+                       if (argnum == ARG_NUM_NEXT) {
+                               argnum = currarg++;
+                       }
                        if (argnum >= argc) {
                                max_missing_argnum = MAX(max_missing_argnum, argnum);
                                continue;
                        }
 
+                       if (expprec && precision == -1
+                                       && *format != 'g' && *format != 'G' && *format != 'h' && *format != 'H') {
+                               zend_value_error("Precision -1 is only supported for %%g, %%G, %%h and %%H");
+                               goto fail;
+                       }
+
                        /* now we expect to find a type specifier */
                        tmp = &args[argnum];
                        switch (*format) {
diff --git a/ext/standard/tests/strings/sprintf_star.phpt b/ext/standard/tests/strings/sprintf_star.phpt
new file mode 100644 (file)
index 0000000..0e3e16c
--- /dev/null
@@ -0,0 +1,98 @@
+--TEST--
+Star width and precision in sprintf()
+--FILE--
+<?php
+
+
+$f = 1.23456789012345678;
+$fx = 1.23456789012345678e100;
+var_dump($f, $fx);
+
+printf("%.*f\n", 10, $f);
+printf("%.*G\n", 10, $f);
+printf("%.*g\n", -1, $fx);
+printf("%.*G\n", -1, $fx);
+printf("%.*h\n", -1, $fx);
+printf("%.*H\n", -1, $fx);
+printf("%.*s\n", 3, "foobar");
+echo "\n";
+
+printf("%*f\n", 10, $f);
+printf("%*G\n", 10, $f);
+printf("%*s\n", 10, "foobar");
+echo "\n";
+
+printf("%*.*f\n", 10, 3, $f);
+printf("%*.*G\n", 10, 3, $f);
+printf("%*.*s\n", 10, 3, "foobar");
+echo "\n";
+
+printf("%1$.*2\$f\n", $f, 10);
+printf("%.*2\$f\n", $f, 10);
+printf("%2$.*f\n", 10, $f);
+printf("%1$*2\$f\n", $f, 10);
+printf("%*2\$f\n", $f, 10);
+printf("%2$*f\n", 10, $f);
+printf("%1$*2$.*3\$f\n", $f, 10, 3);
+printf("%*2$.*3\$f\n", $f, 10, 3);
+printf("%3$*.*f\n", 10, 3, $f);
+echo "\n";
+
+try {
+    printf("%.*G\n", "foo", 1.5);
+} catch (ValueError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    printf("%.*G\n", -100, 1.5);
+} catch (ValueError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    printf("%.*s\n", -1, "Foo");
+} catch (ValueError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    printf("%*G\n", -1, $f);
+} catch (ValueError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+float(1.2345678901234567)
+float(1.2345678901234569E+100)
+1.2345678901
+1.23456789
+1.2345678901234569e+100
+1.2345678901234569E+100
+1.2345678901234569e+100
+1.2345678901234569E+100
+foo
+
+  1.234568
+   1.23457
+    foobar
+
+     1.235
+      1.23
+       foo
+
+1.2345678901
+1.2345678901
+1.2345678901
+  1.234568
+  1.234568
+  1.234568
+     1.235
+     1.235
+     1.235
+
+Precision must be an integer
+Precision must be between -1 and 2147483647
+Precision -1 is only supported for %g, %G, %h and %H
+Width must be greater than zero and less than 2147483647