What's wrong
PreciseNumber/PreciseNumber.Roots.cs (around lines 207-208):
public static PreciseNumber Hypot(PreciseNumber x, PreciseNumber y, int significantDigits) =>
Sqrt(Add(Multiply(x, x), Multiply(y, y)), significantDigits);
The operands are squared without being scaled first. Multiply (PreciseNumber.cs, around line 1572) computes checked(left.Exponent + right.Exponent), so squaring any value with |exponent| above about 1.07e9 throws.
The remark on Hypot (around lines 186-191) says the scaling a double implementation needs is unnecessary because "a PreciseNumber has no such range". That isn't true, because Exponent is an int. Neither overload documents an OverflowException.
Failure scenarios (run against HEAD 09efd30)
Hypot(3e1500000000, 4e1500000000) throws OverflowException. The correct result, 5e1500000000, can be represented, and Sqrt(9e2000000000) returns 3e1000000000 without trouble.
Hypot(3e-1500000000, 4e-1500000000) throws OverflowException.
Hypot(1e100000, 1) takes about 840 ms, and Hypot(1e20000, 1) about 73 ms. Squaring followed by exponent alignment in Add builds an integer of roughly 2×10⁵ digits, then runs a Newton square root on it. The answer at default precision is just 1e100000.
Related but distinct: #149, #150 and #133 cover the same family of extreme-exponent cost and overflow problems in Sin/Exp, Exp2 and ExpM1. None of them covers Hypot.
Suggested fix
- Shift both operands by the larger operand's exponent so the larger is near 10⁰. Compute hypot, then shift the result back with
ShiftExponent. Exact results stay exact.
- If the smaller operand is more than
significantDigits plus a guard below the larger, return |larger| rounded to significantDigits without doing any arithmetic.
- Correct the remark, and document any remaining
OverflowException.
Acceptance criteria
Hypot(3e1500000000, 4e1500000000) == 5e1500000000
Hypot(3e-1500000000, 4e-1500000000) == 5e-1500000000
Hypot(1e100000, 1) returns 1e100000 at default precision in milliseconds
Hypot(3, 4) == 5 exactly, and existing Hypot tests still pass
What's wrong
PreciseNumber/PreciseNumber.Roots.cs(around lines 207-208):The operands are squared without being scaled first.
Multiply(PreciseNumber.cs, around line 1572) computeschecked(left.Exponent + right.Exponent), so squaring any value with |exponent| above about 1.07e9 throws.The remark on
Hypot(around lines 186-191) says the scaling adoubleimplementation needs is unnecessary because "a PreciseNumber has no such range". That isn't true, becauseExponentis anint. Neither overload documents anOverflowException.Failure scenarios (run against HEAD 09efd30)
Hypot(3e1500000000, 4e1500000000)throwsOverflowException. The correct result,5e1500000000, can be represented, andSqrt(9e2000000000)returns3e1000000000without trouble.Hypot(3e-1500000000, 4e-1500000000)throwsOverflowException.Hypot(1e100000, 1)takes about 840 ms, andHypot(1e20000, 1)about 73 ms. Squaring followed by exponent alignment inAddbuilds an integer of roughly 2×10⁵ digits, then runs a Newton square root on it. The answer at default precision is just1e100000.Related but distinct: #149, #150 and #133 cover the same family of extreme-exponent cost and overflow problems in Sin/Exp, Exp2 and ExpM1. None of them covers Hypot.
Suggested fix
ShiftExponent. Exact results stay exact.significantDigitsplus a guard below the larger, return|larger|rounded tosignificantDigitswithout doing any arithmetic.OverflowException.Acceptance criteria
Hypot(3e1500000000, 4e1500000000) == 5e1500000000Hypot(3e-1500000000, 4e-1500000000) == 5e-1500000000Hypot(1e100000, 1)returns1e100000at default precision in millisecondsHypot(3, 4) == 5exactly, and existing Hypot tests still pass