Skip to content

Hypot(3e1500000000, 4e1500000000) throws OverflowException instead of returning 5e1500000000, and Hypot(1e100000, 1) takes almost a second #155

Description

@matt-edmondson

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

  1. 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.
  2. If the smaller operand is more than significantDigits plus a guard below the larger, return |larger| rounded to significantDigits without doing any arithmetic.
  3. 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

Activity

  1. matt-edmondson commented on Oct 6, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions