Skip to content

Commit

Permalink
Fixed negative sum issues, thanks u/Skeeve-on-git
Browse files Browse the repository at this point in the history
  • Loading branch information
theHamdiz committed Feb 2, 2025
1 parent ccffd4c commit c06c471
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 16 deletions.
25 changes: 22 additions & 3 deletions math/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -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)
Expand Down
70 changes: 57 additions & 13 deletions math/math_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit c06c471

Please sign in to comment.