Skip to content

Fix StackOverflowException in Precision.Increment/Decrement for count == int.MinValue#1154

Open
sailro wants to merge 1 commit into
mathnet:masterfrom
sailro:fix/precision-increment-decrement-intminvalue
Open

Fix StackOverflowException in Precision.Increment/Decrement for count == int.MinValue#1154
sailro wants to merge 1 commit into
mathnet:masterfrom
sailro:fix/precision-increment-decrement-intminvalue

Conversation

@sailro

@sailro sailro commented Jun 28, 2026

Copy link
Copy Markdown

Fixes #1153.

Problem

Precision.Increment(double, int) and Precision.Decrement(double, int) throw an uncatchable StackOverflowException when count == int.MinValue. Each method delegates a negative count to its sibling with the negated count, and -int.MinValue overflows back to int.MinValue, so the two methods recurse into each other forever:

Precision.Increment(1.0, int.MinValue); // StackOverflowException
Precision.Decrement(1.0, int.MinValue); // StackOverflowException

Fix

Move the logic into private long-count overloads; the public int methods delegate to them. The negation is then performed in long, where -(long)int.MinValue == 2147483648 is representable, so a count of int.MinValue steps |int.MinValue| representable values in the opposite direction like any other negative count.

Behavior is unchanged for every other (value, count) — the public API is identical and the only previously-reachable difference is that int.MinValue crashed. I verified this with a differential check comparing the old and new implementations over a grid of boundary values (0, ±0.0, ±1, ±ε, MaxValue, MinValue, NaN, ±∞, …) × counts (0, ±1, ±2, ±5, ±1e6, ±int.MaxValue): bit-identical for every pair.

Test

Added PrecisionTest.IncrementDecrementWithIntMinValueCount, which asserts the int.MinValue case steps the bit pattern by 2147483648 in the correct direction (and, by completing at all, that it no longer overflows the stack). The full PrecisionTest class passes (162 tests).

… == int.MinValue

Increment(value, count) and Decrement(value, count) delegate a negative count to the sibling with
the negated count (Decrement(value, -count) / Increment(value, -count)). For count == int.MinValue,
-count overflows back to int.MinValue, so the two methods recurse into each other forever and throw
an uncatchable StackOverflowException.

Move the logic into long-count overloads and have the public int methods delegate, so the negation
is done in long: -(long)int.MinValue == 2147483648 is representable, and a count of int.MinValue now
steps |int.MinValue| representable values in the opposite direction like any other negative count.
Behavior is unchanged for every other count (verified by differential check over a grid of values).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

Precision.Increment/Decrement(value, int.MinValue) throws StackOverflowException (infinite recursion)

1 participant