From c06c47159a33e4d09f8d6d19e72287b374196bf1 Mon Sep 17 00:00:00 2001 From: Ahmad Hamdi <6674568+theHamdiz@users.noreply.github.com> Date: Sun, 2 Feb 2025 23:37:13 +0300 Subject: [PATCH] Fixed negative sum issues, thanks u/Skeeve-on-git --- math/math.go | 25 +++++++++++++++-- math/math_test.go | 70 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/math/math.go b/math/math.go index 3a68f69..2028b58 100644 --- a/math/math.go +++ b/math/math.go @@ -7,10 +7,16 @@ import ( "golang.org/x/exp/constraints" ) -// Sum calculates the sum of numbers from 1 to n in O(1) time +// Sum calculates the sum of numbers from 1 to n (or n to 1 if n is negative) in O(1) time // because who needs loops when you have math from 300 BC? // Gauss would be proud, or maybe just mildly amused. func Sum[T constraints.Integer](n T) T { + // For n = 0, because sometimes nothing plus nothing is... nothing + // Thanks to to u/Skeeve-on-git for highlighting negative sum bug. + if n == 0 { + return 0 + } + // If you're wondering why this works: // 1. Gauss figured this out when he was 8 // 2. You're probably older than 8 @@ -23,8 +29,20 @@ func Sum[T constraints.Integer](n T) T { // obviously this should not be used, it's just here for the memes. func SumSlow[T constraints.Integer](n T) T { var total T - for i := T(1); i <= n; i++ { - total += i + // Explicitly cast 1 to type T + one := T(1) + // Thanks to to u/Skeeve-on-git for highlighting this. + // Compute -1 as type T + negOne := -one + + if n > 0 { + for i := one; i <= n; i++ { + total += i + } + } else { + for i := n; i <= negOne; i++ { + total += i + } } return total } @@ -38,6 +56,7 @@ func SumRange[T constraints.Integer](start, end T) T { start, end = end, start } + // One formula to rule them all // The formula is: (end * (end + 1) / 2) - (start * (start - 1) / 2) // Don't worry if you don't understand it, neither do most people return Sum(end) - Sum(start-1) diff --git a/math/math_test.go b/math/math_test.go index 15ab8b4..6155f4e 100644 --- a/math/math_test.go +++ b/math/math_test.go @@ -62,32 +62,76 @@ func TestCorrectness(t *testing.T) { // TestSumRangeCorrectness checks that SumRange and SumRangeSlow produce // the same results, even for negative values. func TestSumRangeCorrectness(t *testing.T) { + // Thanks to to u/Skeeve-on-git for highlighting negative sum bug. type testCase struct { start int64 end int64 - want int64 // optional: if you already know the correct result + want int64 } + cases := []testCase{ + // Zero cases {start: 0, end: 0, want: 0}, + {start: -0, end: 0, want: 0}, + {start: 0, end: -0, want: 0}, + + // Single number cases {start: 1, end: 1, want: 1}, - {start: 1, end: 10, want: 55}, // 1+2+3+...+10 - {start: -5, end: 5, want: 0}, // -5..5 sums to 0 - {start: 999, end: 1000, want: 999 + 1000}, - {start: 100, end: 50}, // reversed, tests the swap logic - {start: -3, end: -1, want: -6}, // -3 + -2 + -1 + {start: -1, end: -1, want: -1}, + {start: 42, end: 42, want: 42}, + + // Small positive ranges + {start: 1, end: 5, want: 15}, + {start: 3, end: 7, want: 25}, + {start: 1, end: 10, want: 55}, + + // Small negative ranges + {start: -5, end: -1, want: -15}, + {start: -7, end: -3, want: -25}, + {start: -10, end: -1, want: -55}, + + // Mixed ranges crossing zero + {start: -5, end: 5, want: 0}, + {start: -3, end: 3, want: 0}, + {start: -2, end: 2, want: 0}, + {start: -10, end: 10, want: 0}, + + // Asymmetric mixed ranges + {start: -3, end: 5, want: 9}, + {start: -7, end: 4, want: -18}, + {start: -2, end: 8, want: 33}, + + // Reversed ranges (testing swap logic) + {start: 5, end: 1, want: 15}, + {start: -1, end: -5, want: -15}, + {start: 5, end: -5, want: 0}, + {start: 10, end: -10, want: 0}, + + // Larger ranges + {start: 1, end: 100, want: 5050}, + {start: -100, end: -1, want: -5050}, + {start: -50, end: 50, want: 0}, + {start: 999, end: 1000, want: 1999}, + + // Adjacent numbers + {start: -2, end: -1, want: -3}, + {start: -1, end: 0, want: -1}, + {start: 0, end: 1, want: 1}, + {start: 1, end: 2, want: 3}, + + // Gaps + {start: -10, end: -5, want: -45}, + {start: 5, end: 10, want: 45}, + {start: -3, end: 2, want: -3}, } for _, c := range cases { gotSlow := sumRangeSlow(c.start, c.end) gotFast := math2.SumRange[int64](c.start, c.end) - // If "want" is 0, we either didn't provide it or we think it might not matter. - // We'll just compare slow vs fast. Otherwise, we check the known value. - if c.want != 0 { - if gotSlow != c.want { - t.Errorf("sumRangeSlow(%d, %d) = %d; want %d", - c.start, c.end, gotSlow, c.want) - } + if gotSlow != c.want { + t.Errorf("sumRangeSlow(%d, %d) = %d; want %d", + c.start, c.end, gotSlow, c.want) } if gotSlow != gotFast { t.Errorf("SumRange mismatch for start=%d end=%d: slow=%d fast=%d",