This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Perl_sv_vcatpvfn_flags: sort PL_numeric_radix_sv
[perl5.git] / sv.c
diff --git a/sv.c b/sv.c
index 27ca2ee..1f40c98 100644 (file)
--- a/sv.c
+++ b/sv.c
@@ -3147,8 +3147,8 @@ Perl_sv_2pv_flags(pTHX_ SV *const sv, STRLEN *const lp, const I32 flags)
                     STORE_LC_NUMERIC_SET_TO_NEEDED();
 
                     local_radix = PL_numeric_local && PL_numeric_radix_sv;
-                    if (local_radix && SvLEN(PL_numeric_radix_sv) > 1) {
-                        size += SvLEN(PL_numeric_radix_sv) - 1;
+                    if (local_radix && SvCUR(PL_numeric_radix_sv) > 1) {
+                        size += SvCUR(PL_numeric_radix_sv) - 1;
                         s = SvGROW_mutable(sv, size);
                     }
 
@@ -11000,6 +11000,12 @@ S_expect_number(pTHX_ char **const pattern)
     return var;
 }
 
+/* Implement a fast "%.0f": given a pointer to the end of a buffer (caller
+ * ensures it's big enough), back fill it with the rounded integer part of
+ * nv. Returns ptr to start of string, and sets *len to its length.
+ * Returns NULL if not convertible.
+ */
+
 STATIC char *
 S_F0convert(NV nv, char *const endbuf, STRLEN *const len)
 {
@@ -11008,11 +11014,7 @@ S_F0convert(NV nv, char *const endbuf, STRLEN *const len)
 
     PERL_ARGS_ASSERT_F0CONVERT;
 
-    if (UNLIKELY(Perl_isinfnan(nv))) {
-        STRLEN n = S_infnan_2pv(nv, endbuf - *len, *len, 0);
-        *len = n;
-        return endbuf - n;
-    }
+    assert(!Perl_isinfnan(nv));
     if (neg)
        nv = -nv;
     if (nv < UV_MAX) {
@@ -11518,7 +11520,7 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
     }
 
 #if !defined(USE_LONG_DOUBLE) && !defined(USE_QUADMATH)
-    /* special-case "%.<number>[gf]" */
+    /* special-case "%.0f" and "%.<number>g" */
     if ( !args && patlen <= 5 && pat[0] == '%' && pat[1] == '.'
         && (pat[patlen-1] == 'g' || pat[patlen-1] == 'f') ) {
        unsigned digits = 0;
@@ -11541,7 +11543,7 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
                     if (digits && digits < sizeof(ebuf) - NV_DIG - 10) {
                         /* 0, point, slack */
                         STORE_LC_NUMERIC_SET_TO_NEEDED();
-                        SNPRINTF_G(nv, ebuf, size, digits);
+                        SNPRINTF_G(nv, ebuf, sizeof(ebuf), digits);
                         sv_catpv_nomg(sv, ebuf);
                         if (*ebuf)     /* May return an empty string for digits==0 */
                             return;
@@ -11580,12 +11582,6 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
        bool is_utf8 = FALSE;  /* is this item utf8?   */
         bool used_explicit_ix = FALSE;
         bool arg_missing = FALSE;
-#ifdef HAS_LDBL_SPRINTF_BUG
-       /* This is to try to fix a bug with irix/nonstop-ux/powerux and
-          with sfio - Allen <allens@cpan.org> */
-       bool fix_ldbl_sprintf_bug = FALSE;
-#endif
-
        char esignbuf[4];
        U8 utf8buf[UTF8_MAXBYTES+1];
        STRLEN esignlen = 0;
@@ -11600,6 +11596,9 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
        unsigned base = 0;
        IV iv = 0;
        UV uv = 0;
+        bool is_simple = TRUE; /* no fancy qualifiers */
+        STRLEN radix_len;  /* SvCUR(PL_numeric_radix_sv) */
+
        /* We need a long double target in case HAS_LONG_DOUBLE,
          * even without USE_LONG_DOUBLE, so that we can printf with
          * long double formats, even without NV being long double.
@@ -12412,7 +12411,36 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
                 NV_TO_FV(nv, fv);
             }
 
-           float_need = 0;
+            /* special-case "%.0f" */
+            is_simple = ( !(width || left || plus || alt)
+                        && fill != '0'
+                        && has_precis
+                        && intsize != 'q');
+
+            if (is_simple && c == 'f' && !precis && !Perl_isinfnan(nv)) {
+                if ((eptr = F0convert(nv, ebuf + sizeof ebuf, &elen)))
+                    break;
+            }
+
+            /*determine the radix point len, e.g. length(".") in "1.2" */
+            radix_len  = 1; /* assume '.' */
+#ifdef USE_LOCALE_NUMERIC
+            /* note that we may either explicitly use PL_numeric_radix_sv
+             * below, or implicitly, via an snprintf() variant.
+             * Note also things like ps_AF.utf8 which has
+             * "\N{ARABIC DECIMAL SEPARATOR} as a radix point */
+            STORE_LC_NUMERIC_SET_TO_NEEDED();
+            if (PL_numeric_radix_sv && IN_LC(LC_NUMERIC)) {
+                radix_len  = SvCUR(PL_numeric_radix_sv);
+                /* note that this will convert the output to utf8 even if
+                 * if the radix point didn't get output */
+                is_utf8 = SvUTF8(PL_numeric_radix_sv);
+            }
+            RESTORE_LC_NUMERIC();
+#endif
+            /* most basic: space for "Inf"/"Nan" and \0, plus the "." */
+           float_need = 4 + radix_len;
+
            /* frexp() (or frexpl) has some unspecified behaviour for
              * nan/inf/-inf, so let's avoid calling that on non-finites. */
            if (isALPHA_FOLD_NE(c, 'e') && FV_ISFINITE(fv)) {
@@ -12452,12 +12480,6 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
                     float_need += (DOUBLEDOUBLE_MAXBITS/8 + 1) * 2;
                     /* the size for the exponent already added */
 #endif
-#ifdef USE_LOCALE_NUMERIC
-                        STORE_LC_NUMERIC_SET_TO_NEEDED();
-                        if (PL_numeric_radix_sv && IN_LC(LC_NUMERIC))
-                            float_need += SvLEN(PL_numeric_radix_sv);
-                        RESTORE_LC_NUMERIC();
-#endif
                 }
                 else if (i > 0) {
                     float_need = BIT_DIGITS(i);
@@ -12506,7 +12528,10 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
 
            if ((intsize == 'q') && (c == 'f') &&
                ((fv < MY_DBL_MAX_BUG) && (fv > -MY_DBL_MAX_BUG)) &&
-               (float_need < DBL_DIG)) {
+               (float_need < DBL_DIG))
+            {
+                bool fix_ldbl_sprintf_bug = FALSE;
+
                /* it's going to be short enough that
                 * long double precision is not needed */
 
@@ -12525,6 +12550,7 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
                        fix_ldbl_sprintf_bug = TRUE;
                    }
                }
+
                if (fix_ldbl_sprintf_bug == TRUE) {
                    double temp;
 
@@ -12550,9 +12576,8 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
                PL_efloatbuf[0] = '\0';
            }
 
-           if ( !(width || left || plus || alt) && fill != '0'
-                && has_precis && intsize != 'q'        /* Shortcuts */
-                 && LIKELY(!Perl_isinfnan((NV)fv)) ) {
+            /* special-case "%.<number>g" */
+            if (is_simple && LIKELY(!Perl_isinfnan((NV)fv)) ) {
                /* See earlier comment about buggy Gconvert when digits,
                   aka precis is 0  */
                if ( c == 'g' && precis ) {
@@ -12563,10 +12588,7 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
                        elen = strlen(PL_efloatbuf);
                        goto float_converted;
                    }
-               } else if ( c == 'f' && !precis ) {
-                   if ((eptr = F0convert(nv, ebuf + sizeof ebuf, &elen)))
-                       break;
-               }
+                }
            }
 
             if (UNLIKELY(hexfp)) {
@@ -12939,15 +12961,6 @@ Perl_sv_vcatpvfn_flags(pTHX_ SV *const sv, const char *const pat, const STRLEN p
            eptr = PL_efloatbuf;
             assert((IV)elen > 0); /* here zero elen is bad */
 
-#ifdef USE_LOCALE_NUMERIC
-            /* If the decimal point character in the string is UTF-8, make the
-             * output utf8 */
-            if (PL_numeric_radix_sv && SvUTF8(PL_numeric_radix_sv)
-                && instr(eptr, SvPVX_const(PL_numeric_radix_sv)))
-            {
-                is_utf8 = TRUE;
-            }
-#endif
 
            break;