Skip to content

Pow with a fractional exponent misrounds the last digit: Pow(19, 1.5) ends in …417 instead of …416 (double rounding without guard digits) #127

Description

@matt-edmondson

What's wrong

FractionalPow (PreciseNumber/PreciseNumber.Exponentials.cs:683) ends with:

return Exp(product, significantDigits + consumed).ReduceSignificance(significantDigits);

Exp rounds half away from zero to significantDigits + consumed digits, which is often only one digit more than the caller asked for. ReduceSignificance then rounds half away from zero again. When the true value continues …49… just past the requested width, the first rounding turns the 4 into a 5 and the second rounding then carries it up. The last digit comes out one unit too high.

Every other function that finishes with ReduceSignificance(significantDigits) first computes at working = significantDigits + ExponentialGuardDigits. This path is the only one that skips the guard digits.

Repro (default 50 significant digits; reference values from Python decimal at 80 digits)

Call True value (tail) Expected Observed
Pow(Parse("19"), Parse("1.5")) …5794164538… …579416 …579417
Pow(Parse("30"), Parse("1.5")) …0849939394976… …849939 …84994
Pow(Parse("2957"), Parse("1.5")) …814757 …814758

A sweep of 400 random integer bases raised to 1.5 found two more misrounded results besides 19. About 3,000 cases for Exp, Log*, ExpM1, LogP1, the hyperbolic functions, Sin, Cos, Atan and Sqrt at 3–20 digits all matched the correctly rounded values, so the problem is specific to Pow.

This is not #121 (Divide's exact path) or #123 (Exp2's ln 2 widening).

Suggested fix / acceptance criteria

Round once, from a guarded width:

return Exp(product, working + consumed).ReduceSignificance(significantDigits);

With this change applied locally, all three repros return the expected digits and the existing 411 tests pass. Add pinning tests for Pow(19, 1.5) and Pow(30, 1.5).

Activity

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 workingreadyFully specified; implement as written

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions