@@ -586,7 +586,22 @@ public SignificantNumber Pow(PreciseNumber power)
586586 throw new ArgumentException ( "Cannot raise a negative number to a non-integer power." , nameof ( power ) ) ;
587587 }
588588
589- int significantDigits = LowestSignificantDigits ( this , power ) ;
589+ if ( PreciseNumber . IsInteger ( power ) && Math . Abs ( power . To < double > ( ) ) <= MaxExactIntegerPower )
590+ {
591+ // An integer power is an exact count, like the counting numbers in a multiplication, so the
592+ // result keeps the base's significant digits. Computing it by exact repeated multiplication
593+ // makes x.Pow(2) agree with x * x and keeps the sign of a negative base.
594+ BigInteger exponent = IntegerValue ( power ) ;
595+ PreciseNumber positivePower = ExactIntegerPower ( Value , BigInteger . Abs ( exponent ) ) ;
596+ PreciseNumber exact = exponent . Sign < 0 ? PreciseNumber . Divide ( PreciseNumber . One , positivePower ) : positivePower ;
597+ return exact . ToSignificantNumber ( Value . SignificantDigits ) ;
598+ }
599+
600+ // An integer power too large to compute exactly is still exact, so it keeps the base's
601+ // precision, bounded by the double the result is computed in.
602+ int significantDigits = PreciseNumber . IsInteger ( power )
603+ ? int . Min ( Value . SignificantDigits , DoubleSignificantDigits )
604+ : LowestSignificantDigits ( this , power ) ;
590605
591606 // Use logarithm and exponential to support decimal powers. This computes |x|^p, so the
592607 // sign of a negative base is restored for odd integer powers.
@@ -612,12 +627,64 @@ public static SignificantNumber Exp(PreciseNumber power)
612627 return E ;
613628 }
614629
615- int significantDigits = LowestSignificantDigits ( E , power ) ;
630+ // An integer power is exact, so the precision is bounded only by the double the result is
631+ // computed in, not by the single significant digit an integer like 2 carries.
632+ int significantDigits = PreciseNumber . IsInteger ( power )
633+ ? int . Min ( E . SignificantDigits , DoubleSignificantDigits )
634+ : LowestSignificantDigits ( E , power ) ;
616635
617636 return Math . Exp ( power . To < double > ( ) )
618637 . ToSignificantNumber ( significantDigits ) ;
619638 }
620639
640+ /// <summary>
641+ /// The number of significant decimal digits a <see cref="double"/> always represents exactly.
642+ /// </summary>
643+ private const int DoubleSignificantDigits = 15 ;
644+
645+ /// <summary>
646+ /// The largest integer power <see cref="Pow"/> computes exactly. Larger powers fall back to the
647+ /// logarithm path, so that an extreme exponent cannot build an arbitrarily large intermediate.
648+ /// </summary>
649+ private const int MaxExactIntegerPower = 1024 ;
650+
651+ /// <summary>
652+ /// Gets the value of an integer <see cref="PreciseNumber"/> as a <see cref="BigInteger"/>.
653+ /// </summary>
654+ /// <param name="value">A number that <see cref="PreciseNumber.IsInteger"/> reports as an integer.</param>
655+ /// <returns>The integer value of <paramref name="value"/>.</returns>
656+ private static BigInteger IntegerValue ( PreciseNumber value ) =>
657+ value . Exponent >= 0
658+ ? value . Significand * BigInteger . Pow ( 10 , value . Exponent )
659+ : value . Significand / BigInteger . Pow ( 10 , - value . Exponent ) ;
660+
661+ /// <summary>
662+ /// Raises a number to a non-negative integer power by exact repeated squaring.
663+ /// </summary>
664+ /// <param name="value">The base.</param>
665+ /// <param name="exponent">The non-negative power.</param>
666+ /// <returns><paramref name="value"/> raised to <paramref name="exponent"/>, with no rounding.</returns>
667+ private static PreciseNumber ExactIntegerPower ( PreciseNumber value , BigInteger exponent )
668+ {
669+ PreciseNumber result = PreciseNumber . One ;
670+ PreciseNumber square = value ;
671+ while ( ! exponent . IsZero )
672+ {
673+ if ( ! exponent . IsEven )
674+ {
675+ result = PreciseNumber . Multiply ( result , square ) ;
676+ }
677+
678+ exponent >>= 1 ;
679+ if ( ! exponent . IsZero )
680+ {
681+ square = PreciseNumber . Multiply ( square , square ) ;
682+ }
683+ }
684+
685+ return result ;
686+ }
687+
621688 /// <summary>
622689 /// Compares the current instance with another <see cref="SignificantNumber"/> at the lower of their significant digit counts.
623690 /// </summary>
0 commit comments