package fixed import ( "math" "testing" ) // The simulation's verifiability depends on this arithmetic behaving // identically everywhere, including at the extremes. These tests attack the // boundaries. func TestMulByZeroAndOne(t *testing.T) { for _, v := range []int64{0, 1, -1, 1000, -1000, 1 << 20} { a := FromInt(v) if got := a.Mul(0); got != 0 { t.Errorf("%d * 0 = %v, want 0", v, got) } if got := a.Mul(One); got != a { t.Errorf("%d * 1 = %v, want %v", v, got, a) } } } func TestDivByOneAndSelf(t *testing.T) { for _, v := range []int64{1, -1, 7, -7, 1000, 1 << 20} { a := FromInt(v) if got := a.Div(One); got != a { t.Errorf("%d / 1 = %v, want %v", v, got, a) } if got := a.Div(a); got != One { t.Errorf("%d / %d = %v, want 1", v, v, got) } } } func TestDivByZeroPanics(t *testing.T) { defer func() { if recover() == nil { t.Fatal("division by zero did not panic") } }() _ = One.Div(0) } func TestSqrtOfNegativePanics(t *testing.T) { defer func() { if recover() == nil { t.Fatal("sqrt of a negative did not panic") } }() _ = Sqrt(FromInt(-1)) } // Multiplication must stay associative-ish and exact for representable values, // which is what keeps a replayed round identical to the original. func TestMulIsExactForFractions(t *testing.T) { cases := []struct { a, b, want F }{ {One / 2, One / 2, One / 4}, {One / 4, One / 4, One / 16}, {One / 2, One / 4, One / 8}, {One * 3 / 2, One * 2, One * 3}, } for _, c := range cases { if got := c.a.Mul(c.b); got != c.want { t.Errorf("%v * %v = %v, want %v", c.a, c.b, got, c.want) } } } // Round-tripping a value through multiply and divide must return it exactly // for powers of two, where no precision can be lost. func TestMulDivRoundTripOnPowersOfTwo(t *testing.T) { for shift := 0; shift < 20; shift++ { v := FromInt(1 << shift) for _, by := range []F{One * 2, One * 4, One * 8} { if got := v.Mul(by).Div(by); got != v { t.Errorf("2^%d round trip through %v gave %v, want %v", shift, by, got, v) } } } } func TestSqrtIsMonotonic(t *testing.T) { prev := Sqrt(0) for i := int64(1); i < 5000; i++ { cur := Sqrt(FromInt(i)) if cur < prev { t.Fatalf("Sqrt decreased at %d: %v -> %v", i, prev, cur) } prev = cur } } // Sqrt must never overshoot: its square must not exceed the input. func TestSqrtNeverOvershoots(t *testing.T) { for i := int64(0); i < 20000; i++ { a := FromInt(i) r := Sqrt(a) if r.Mul(r) > a { t.Fatalf("Sqrt(%d) = %v squares to %v, which exceeds %v", i, r, r.Mul(r), a) } } } func TestSqrtOfLargeValues(t *testing.T) { // Values in the range the crash curve actually produces, up to the // representable maximum. for _, v := range []int64{1_000_000, 12_960_000, 100_000_000, MaxInt} { a := FromInt(v) r := Sqrt(a) if r <= 0 { t.Fatalf("Sqrt(%d) = %v, want positive", v, r) } if r.Mul(r) > a { t.Fatalf("Sqrt(%d) overshoots", v) } } } func TestIntTruncatesTowardNegativeInfinity(t *testing.T) { cases := []struct { in F want int64 }{ {One, 1}, {One + One/2, 1}, {One*2 - 1, 1}, {0, 0}, {-One, -1}, } for _, c := range cases { if got := c.in.Int(); got != c.want { t.Errorf("(%v).Int() = %d, want %d", c.in, got, c.want) } } } func TestFromIntPanicsOutsideRange(t *testing.T) { for _, v := range []int64{MaxInt + 1, MinInt - 1, 4_000_000_000, -4_000_000_000} { func() { defer func() { if recover() == nil { t.Errorf("FromInt(%d) did not panic", v) } }() _ = FromInt(v) }() } // The boundaries themselves must be accepted. _ = FromInt(MaxInt) _ = FromInt(MinInt) } func TestStringNeverPanicsAcrossRange(t *testing.T) { values := []F{ 0, 1, -1, One, -One, One / 3, math.MaxInt64, math.MinInt64 + 1, FromInt(4_000_000), } for _, v := range values { if s := v.String(); s == "" { t.Errorf("String() of %d returned empty", int64(v)) } } } // Addition and subtraction are plain integer ops, but the inverse property is // what payout arithmetic relies on. func TestAddSubAreInverse(t *testing.T) { for _, a := range []F{0, One, -One, One * 12345, One / 7} { for _, b := range []F{0, One, -One, One * 999} { if got := a.Add(b).Sub(b); got != a { t.Errorf("(%v + %v) - %v = %v, want %v", a, b, b, got, a) } } } } // Determinism check: the same operations in the same order must produce // bit-identical results every time, which is the whole premise of replay. func TestOperationsAreBitStable(t *testing.T) { compute := func() F { acc := One for i := int64(1); i < 500; i++ { acc = acc.Mul(One + One/F(i+1)) acc = acc.Div(One + One/F(i+2)) acc = acc.Add(FromInt(i % 3)) acc = Sqrt(acc) } return acc } first := compute() for i := 0; i < 200; i++ { if got := compute(); got != first { t.Fatalf("run %d diverged: %v != %v", i, got, first) } } }