Instead of O(n) in the value of the exponent, we can compute exponents
in O(log n), with exponentiation by squaring.  With this patch, "time
echo 'eval(3**2000000000)' | m4" drops from 2 seconds to under 10
milliseconds.

* src/eval.c (parse_expr): Use exponentiation by squaring.
* doc/m4.texi (Eval): Test it.
---
 doc/m4.texi | 16 ++++++++++++++++
 src/eval.c  | 17 +++++++++++++----
 2 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/doc/m4.texi b/doc/m4.texi
index 81476bc8..612ed945 100644
--- a/doc/m4.texi
+++ b/doc/m4.texi
@@ -7235,6 +7235,22 @@ Eval
 @result{}
 @end example

+@ignore
+@comment This test makes sure the algorithm is fast, but does not
+@comment need to be displayed as-is in the manual.
+
+@example
+eval(`3**2000000000')
+@result{}632360961
+define(`loop', `ifelse(`$1', `1000', `pass',
+`loop(eval($1+!!(3**2000000000)))')')loop(0)
+@result{}pass
+define(`check', `ifelse(`$1', `100', `pass', `$2', eval(-3**$1),
+`check(incr($1), eval(-3*$2))', `oops')')check(0, 1)
+@result{}pass
+@end example
+@end ignore
+
 Within @var{expression}, (but not @var{radix} or @var{width}), numbers
 without a special prefix are decimal.  A simple @samp{0} prefix
 introduces an octal number.  @samp{0x} introduces a hexadecimal number.
diff --git a/src/eval.c b/src/eval.c
index 163e2634..69cd5e41 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -389,6 +389,8 @@ parse_expr (int32_t *v1, eval_error er, unsigned min_prec)
   int32_t v2;
   int32_t v3;
   uint32_t u1;
+  uint32_t u2;
+  uint32_t u3;

   if (er >= SYNTAX_ERROR)
     return er;
@@ -429,17 +431,24 @@ parse_expr (int32_t *v1, eval_error er, unsigned min_prec)
              that the implementation-defined overflow when casting
              unsigned to signed is a silent twos-complement
              wrap-around.  */
-          u1 = 1;
           if (v2 < 0)
             er = NEGATIVE_EXPONENT;
           else if (*v1 == 0 && v2 == 0)
             er = DIVIDE_ZERO;
           else
             {
-              while (v2-- > 0)
-                u1 *= (uint32_t) *v1;
+              u1 = *v1;
+              u2 = v2;
+              u3 = 1;
+              while (u2)
+                {
+                  if (u2 & 1)
+                    u3 *= u1;
+                  u1 *= u1;
+                  u2 >>= 1;
+                }
             }
-          *v1 = u1;
+          *v1 = u3;
           break;

         case TIMES:
-- 
2.49.0


_______________________________________________
M4-patches mailing list
M4-patches@gnu.org
https://lists.gnu.org/mailman/listinfo/m4-patches

Reply via email to