diff --git a/math/lcm/lcm.go b/math/lcm/lcm.go index e1de23e17..1f25728e6 100644 --- a/math/lcm/lcm.go +++ b/math/lcm/lcm.go @@ -1,12 +1,33 @@ package lcm import ( - "math" - "github.com/TheAlgorithms/Go/math/gcd" ) +// absInt64 returns the absolute value of x as an int64. +func absInt64(x int64) int64 { + if x < 0 { + return -x + } + return x +} + // Lcm returns the lcm of two numbers using the fact that lcm(a,b) * gcd(a,b) = | a * b | +// +// The computation is performed with integer arithmetic and the division by gcd +// is applied to |a| before multiplying by |b|, so the running intermediate never +// exceeds the true LCM. This avoids two failure modes of the previous +// float64-based formula: +// - int64 multiply overflow: a*b wraps mod 2^64 before the float64 cast, so +// e.g. Lcm(4294967296, 8589934592) returned 0 (impossible for positives). +// - float64 precision loss: float64 cannot represent every int64 past 2^53, +// so e.g. Lcm(9007199254740993, 1) returned 9007199254740992, violating the +// identity lcm(x, 1) == x. func Lcm(a, b int64) int64 { - return int64(math.Abs(float64(a*b)) / float64(gcd.Iterative(a, b))) + g := gcd.Iterative(a, b) + if g == 0 { + // gcd.Iterative(0, 0) == 0; LCM(0, x) is conventionally 0. + return 0 + } + return (absInt64(a) / absInt64(g)) * absInt64(b) } diff --git a/math/lcm/lcm_test.go b/math/lcm/lcm_test.go index e685a6728..4f7669ae0 100644 --- a/math/lcm/lcm_test.go +++ b/math/lcm/lcm_test.go @@ -30,6 +30,35 @@ func TestLcm(t *testing.T) { b: 5, output: 5, }, + // Regression: integer-arithmetic fix for float precision loss and overflow. + { + // Mode A: |a*b| overflows int64 even though the true LCM fits. + // Previously returned 0 (impossible for positive inputs). + name: "LCM of 2^32 & 2^33 (overflow-safe intermediate)", + a: 4294967296, + b: 8589934592, + output: 8589934592, + }, { + // Mode A: lcm(x, x) == x. Previously lcm(x,x) != x for large x. + name: "LCM of 3037000500 & 3037000500 (lcm(x,x)==x)", + a: 3037000500, + b: 3037000500, + output: 3037000500, + }, { + // Mode B: float64 cannot represent 2^53+1 exactly. + // Previously returned 9007199254740992, violating lcm(x,1)==x. + name: "LCM of 9007199254740993 & 1 (2^53+1 precision)", + a: 9007199254740993, + b: 1, + output: 9007199254740993, + }, { + // Mode B: product exceeds 2^53 but fits int64; float64 loses low bits. + // Previously returned 1000000016000000000 (off by 63). + name: "LCM of 1000000007 & 1000000009 (large coprime precision)", + a: 1000000007, + b: 1000000009, + output: 1000000016000000063, + }, } for _, tc := range testCases {