镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Bisect instead of halving when RootN's estimate overflows [patch] - #558

Open
matt-edmondson wants to merge 3 commits into
mainfrom
fix/rootn-narrow-exact-powers-557
Open

matt-edmondson wants to merge 3 commits into
mainfrom
fix/rootn-narrow-exact-powers-557

Conversation

@matt-edmondson

@matt-edmondson matt-edmondson commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #557

What was wrong

StorageMath.RootByDescent depends on the estimate staying at or above the root. When the estimate's degree - 1 power overflowed the type, it halved the estimate, assuming the half would still be at or above the root. Once the lift has doubled past the root, that assumption fails. For RootN((short)15625, 6), the lift went 4 → 8, 8⁵ overflowed short, and halving gave 4, which is below the root of 5. The Newton step from 4 did not descend, so the stale 4 was returned. The same happened for (short)16384 at degree 7 and for 19⁷, 20⁷ and 21⁷ in int.

Change

  • The descent now tracks below, the largest estimate known to be at or below the floor of the root. It starts at one, and the lift raises it each time it doubles.
  • When the estimate's power overflows, the estimate is known to be above the root. Instead of halving blindly, the overflow branch bisects between below and the estimate. If the two are adjacent, below is the floor and is returned.
  • RootBySettling (fractional types) and Cbrt are unchanged.

Tests

  • AnExactPowerInANarrowTypeRootsToItsRoot covers the issue's five cases, which now return 5, 4, 19, 20 and 21. It also covers two values where the bisection closes on the floor: (byte)244 at degree 5, and 5¹³+1 at degree 13.
  • TheNarrowIntegerTypesRootEveryExactPowerToItsFloor runs over sbyte, byte, short, ushort, int and uint. For degrees 3–16 it checks every kᵈ the type holds, plus kᵈ − 1 and kᵈ + 1, against a BigInteger floor root.
  • With the StorageMath.cs change reverted, both tests fail.
  • Every changed line is covered, which fixes the Sonar new-code coverage gate (it was 75%). In a sweep of every 8- and 16-bit value and 200k random int/long values per degree, no input took the bisection's "midpoint is below the root" arm. It stays in because it is what keeps the bound correct.
  • Full Semantics.Test on Linux / .NET 10: 2011 passed, 0 failed, 14 skipped (Windows-only path tests).

🤖 Generated with Claude Code

https://claude.ai/code/session_017vfJW5ffCNUovhUpTzJZn7

RootByDescent halved an estimate whose own power overflowed the type, on
the assumption that the half stays at or above the root. Once the lift has
doubled past the root that does not hold: RootN((short)15625, 6) halved 8
to 4, the Newton step from 4 did not descend, and the answer was 4, not 5.

The descent now keeps the largest estimate known to be at or below the
floor, and bisects between it and the overflowing estimate.

Fixes #557

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW5ffCNUovhUpTzJZn7
Sonar's new-code coverage was 75%: no test reached the step that returns
the floor once the bisection bounds are adjacent. Values one above an exact
power do, so the sweep now checks k^d - 1, k^d and k^d + 1 for degrees 3 to
16, and the bisection's two-way update is a single assignment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW5ffCNUovhUpTzJZn7
SonarCloud S3776: RootByDescent's cognitive complexity was 16, one over
the 15 allowed, after the bisection was added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW5ffCNUovhUpTzJZn7
@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StorageMath.RootN returns one less than the exact root of a perfect power for short and int: RootN((short)15625, 6) is 4, RootN(1280000000, 7) is 19

2 participants