Repository navigation
fix(math): signed overflow in lcm() and SIGFPE when both inputs are zero - #1596
Open
newmathphys wants to merge 1 commit into
Open
newmathphys wants to merge 1 commit into
newmathphys wants to merge 1 commit into
Conversation
`int lcm(int a, int b) { return (a * b) / gcd(a, b); }` has two defects in the
same expression.
1. The intermediate product overflows even when the answer does not.
For a = b = 50000 the true lcm is 50000 - it fits in int with ~40 000x
headroom - but `a * b` is 2500000000, which exceeds INT_MAX (2147483647).
That is UB in C11, and the program prints a wrong answer:
$ ./lcm_plain 50000 50000
lcm(50000, 50000) = -35899 # should be 50000
$ ./lcm_ubsan 50000 50000
runtime error: signed integer overflow: 50000 * 50000 cannot be
represented in type 'int'
The file's own test cases (15,20 -> 60 and 12,18 -> 36) both stay far below
sqrt(INT_MAX) ~ 46341, which is why this went unnoticed.
2. SIGFPE when both inputs are zero: gcd(0, 0) returns 0 (the `a == 0` branch
returns b), so the divisor is zero.
$ ./lcm_plain 0 0
Floating point exception (core dumped) # exit code 136
Fix: a zero guard plus DIVIDE BEFORE MULTIPLYING, `(a / gcd(a, b)) * b`.
gcd(a, b) divides a exactly, so the intermediate value never exceeds the true LCM.
Verified after the patch (built with -fsanitize=undefined):
50000,50000 -> 50000 0,0 -> 0
15,20 -> 60 (unchanged) 12,18 -> 36 (unchanged)
no sanitizer diagnostic on any of the four inputs.
gcd() is untouched; the change is local to lcm(). If lcm(a, b) itself does not fit
in int the result is still unrepresentable - that follows from the return type.
newmathphys
requested review from
Panquesito7,
alexpantyukhin and
tjgurwara99
as code owners
October 26, 2026 15:12
newmathphys
pushed a commit
to newmathphys/kern
that referenced
this pull request
Oct 26, 2026
Opened TheAlgorithms/C#1596 from the fork newmathphys/C, branch fix/lcm-overflow-and-divzero based on upstream master@e5dad3f. One file, +13 -1, mergeable: True. The patch was applied to a FRESH upstream/master (git apply --check clean) and the fix was re-verified with a -fsanitize=undefined build ON THE PATCHED REPOSITORY FILE: 50000,50000 -> 50000 0,0 -> 0 15,20 -> 60 (unchanged) 12,18 -> 36 (unchanged) no sanitizer diagnostic on any of the four inputs. Recorded the PR number in docs/pr/thealgorithms_c_lcm_PR.md (now marked SUBMITTED), docs/EXAMPLE_WHY_KERN.md, README.md, README.ru.md and both defect journals (#364). This is the third upstream report: #1595 (catalan.c, C), #1075 (Rust), #1596 (lcm.c, C).
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
math/lcm.chas two defects in the same expression:1. The intermediate product overflows even when the answer does not
(a * b)is computed before the division. Fora = b = 50000the truelcm(50000, 50000)is50000- it fits inintwith room to spare (about 40 000 times over) - but the intermediate product is2500000000, which exceedsINT_MAX(2147483647). In C11 that is undefined behaviour, and the program prints a wrong answer:The file's own documented test cases (
15,20 -> 60and12,18 -> 36) both stay far belowsqrt(INT_MAX) ~ 46341, which is why this went unnoticed.2.
SIGFPEwhen both inputs are zerogcd(0, 0)returns0(thea == 0branch returnsb), so the divisor is zero:lcm(0, 0)is conventionally0.The fix
gcd(a, b)dividesaexactly, soa / gcd(a, b)is exact and the intermediate value never exceeds the true LCM.gcd(0, 0) == 0cannot become a divisor.Verification after the patch
Scope
The change is local to
lcm().gcd()is untouched. Iflcm(a, b)itself does not fit inint, the result still cannot be represented - that is inherent in theintreturn type and is not claimed to be fixed here.Found by stating a contract for the arithmetic and letting a solver refute it, then confirming with
gcc.