restrict PERL_PRESERVE_IVUV to things that should really give an
Hugo van der Sanden [Wed, 12 Feb 2003 03:12:43 +0000 (03:12 +0000)]
integer, but extend to runtime. Based on:
Subject: Re: [perl #20827] Unexpected scientific notation.
Message-Id: <200302120312.h1C3ChS02613@crypt.compulink.co.uk>

p4raw-id: //depot/perl@18720

op.c
pp.c

diff --git a/op.c b/op.c
index 7cb7fad..4cba2c5 100644 (file)
--- a/op.c
+++ b/op.c
@@ -1942,19 +1942,7 @@ Perl_fold_constants(pTHX_ register OP *o)
     op_free(o);
     if (type == OP_RV2GV)
        return newGVOP(OP_GV, 0, (GV*)sv);
-    else {
-       /* try to smush double to int, but don't smush -2.0 to -2 */
-       if ((SvFLAGS(sv) & (SVf_IOK|SVf_NOK|SVf_POK)) == SVf_NOK &&
-           type != OP_NEGATE)
-       {
-#ifdef PERL_PRESERVE_IVUV
-           /* Only bother to attempt to fold to IV if
-              most operators will benefit  */
-           SvIV_please(sv);
-#endif
-       }
-       return newSVOP(OP_CONST, 0, sv);
-    }
+    return newSVOP(OP_CONST, 0, sv);
 
   nope:
     return o;
diff --git a/pp.c b/pp.c
index 0d531dc..cc6b5c3 100644 (file)
--- a/pp.c
+++ b/pp.c
@@ -879,16 +879,15 @@ PP(pp_postdec)
 
 PP(pp_pow)
 {
-    dSP; dATARGET; tryAMAGICbin(pow,opASSIGN);
+    dSP; dATARGET;
 #ifdef PERL_PRESERVE_IVUV
-    /* ** is implemented with pow. pow is floating point. Perl programmers
-       write 2 ** 31 and expect it to be 2147483648
-       pow never made any guarantee to deliver a result to 53 (or whatever)
-       bits of accuracy. Which is unfortunate, as perl programmers expect it
-       to, and on some platforms (eg Irix with long doubles) it doesn't in
-       a very visible case. (2 ** 31, which a regression test uses)
-       So we'll implement power-of-2 ** +ve integer with multiplies, to avoid
-       these problems.  */
+    bool is_int = 0;
+#endif
+    tryAMAGICbin(pow,opASSIGN);
+#ifdef PERL_PRESERVE_IVUV
+    /* For integer to integer power, we do the calculation by hand wherever
+       we're sure it is safe; otherwise we call pow() and try to convert to
+       integer afterwards. */
     {
         SvIV_please(TOPm1s);
         if (SvIOK(TOPm1s)) {
@@ -920,10 +919,12 @@ PP(pp_pow)
                         goto float_it; /* Can't do negative powers this way.  */
                     }
                 }
-                /* now we have integer ** positive integer.
-                   foo & (foo - 1) is zero only for a power of 2.  */
+                /* now we have integer ** positive integer. */
+                is_int = 1;
+
+                /* foo & (foo - 1) is zero only for a power of 2.  */
                 if (!(baseuv & (baseuv - 1))) {
-                    /* We are raising power-of-2 to postive integer.
+                    /* We are raising power-of-2 to a positive integer.
                        The logic here will work for any base (even non-integer
                        bases) but it can be less accurate than
                        pow (base,power) or exp (power * log (base)) when the
@@ -935,20 +936,6 @@ PP(pp_pow)
                     NV base = baseuok ? baseuv : -(NV)baseuv;
                     int n = 0;
 
-                    /* The logic is this.
-                       x ** n === x ** m1 * x ** m2 where n = m1 + m2
-                       so as 42 is 32 + 8 + 2
-                       x ** 42 can be written as
-                       x ** 32 * x ** 8 * x ** 2
-                       I can calculate x ** 2, x ** 4, x ** 8 etc trivially:
-                       x ** 2n is x ** n * x ** n
-                       So I loop round, squaring x each time
-                       (x, x ** 2, x ** 4, x ** 8) and multiply the result
-                       by the x-value whenever that bit is set in the power.
-                       To finish as soon as possible I zero bits in the power
-                       when I've done them, so that power becomes zero when
-                       I clear the last bit (no more to do), and the loop
-                       terminates.  */
                     for (; power; base *= base, n++) {
                         /* Do I look like I trust gcc with long longs here?
                            Do I hell.  */
@@ -956,24 +943,69 @@ PP(pp_pow)
                         if (power & bit) {
                             result *= base;
                             /* Only bother to clear the bit if it is set.  */
-                            power &= ~bit;
+                            power -= bit;
                            /* Avoid squaring base again if we're done. */
                            if (power == 0) break;
                         }
                     }
                     SP--;
                     SETn( result );
+                    SvIV_please(TOPs);
                     RETURN;
-                }
-            }
-        }
+               } else {
+                   register unsigned int highbit = 8 * sizeof(UV);
+                   register unsigned int lowbit = 0;
+                   register unsigned int diff;
+                   while ((diff = (highbit - lowbit) >> 1)) {
+                       if (baseuv & ~((1 << (lowbit + diff)) - 1))
+                           lowbit += diff;
+                       else 
+                           highbit -= diff;
+                   }
+                   /* we now have baseuv < 2 ** highbit */
+                   if (power * highbit <= 8 * sizeof(UV)) {
+                       /* result will definitely fit in UV, so use UV math
+                          on same algorithm as above */
+                       register UV result = 1;
+                       register UV base = baseuv;
+                       register int n = 0;
+                       for (; power; base *= base, n++) {
+                           register UV bit = (UV)1 << (UV)n;
+                           if (power & bit) {
+                               result *= base;
+                               power -= bit;
+                               if (power == 0) break;
+                           }
+                       }
+                       SP--;
+                       if (baseuok || !(power & 1))
+                           /* answer is positive */
+                           SETu( result );
+                       else if (result <= (UV)IV_MAX)
+                           /* answer negative, fits in IV */
+                           SETi( -(IV)result );
+                       else if (result == (UV)IV_MIN) 
+                           /* 2's complement assumption: special case IV_MIN */
+                           SETi( IV_MIN );
+                       else
+                           /* answer negative, doesn't fit */
+                           SETn( -(NV)result );
+                       RETURN;
+                   } 
+               }
+           }
+       }
     }
-      float_it:
+  float_it:
 #endif    
     {
-        dPOPTOPnnrl;
-        SETn( Perl_pow( left, right) );
-        RETURN;
+       dPOPTOPnnrl;
+       SETn( Perl_pow( left, right) );
+#ifdef PERL_PRESERVE_IVUV
+       if (is_int)
+           SvIV_please(TOPs);
+#endif
+       RETURN;
     }
 }