Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

muldiv_test.gno

3.42 Kb · 114 lines
  1package xmath
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/nt/uassert/v0"
  7)
  8
  9func TestMulDiv(t *testing.T) {
 10	cases := []struct {
 11		name    string
 12		a, b, c int64
 13		want    int64
 14	}{
 15		{"exact", 10, 3, 5, 6},
 16		{"rounds toward zero", 10, 1, 3, 3},
 17		{"identity", 7, 1, 1, 7},
 18		{"zero numerator", 0, 99, 7, 0},
 19		{"half of a pot", 1000000, 50, 100, 500000},
 20		{"one basis point", 1000000, 1, 10000, 100},
 21	}
 22	for _, tc := range cases {
 23		t.Run(tc.name, func(t *testing.T) {
 24			uassert.Equal(t, tc.want, MulDiv(tc.a, tc.b, tc.c))
 25		})
 26	}
 27}
 28
 29// TestMulDivSurvivesAnIntermediateThatDoesNotFit is the whole point. The naive
 30// a*b/c computes a*b first, which wraps past the int64 maximum and wraps to a
 31// plausible number rather than an obvious one. Through a 128-bit intermediate
 32// the answer is exact.
 33func TestMulDivSurvivesAnIntermediateThatDoesNotFit(t *testing.T) {
 34	// A variable, not a const: gno rejects a constant expression that
 35	// overflows at compile time, and the point here is the runtime wrap.
 36	big := int64(1) << 62 // 4611686018427387904
 37	four := int64(4)
 38
 39	// big*4 overflows int64; big*4/8 is big/2 and fits comfortably.
 40	uassert.Equal(t, big/2, MulDiv(big, four, 8))
 41
 42	// The naive form, computed here so the test shows what it would have done
 43	// rather than asserting it in prose.
 44	naive := (big * four) / 8
 45	uassert.NotEqual(t, big/2, naive)
 46
 47	// big*4 is exactly 2^64, so it wraps to 0 and the naive form answers 0 for
 48	// a share that is actually 2305843009213693952. Measured, not assumed: the
 49	// first version of this test asserted the wrap went negative, which is the
 50	// intuition and is wrong for this input.
 51	uassert.Equal(t, int64(0), naive)
 52}
 53
 54func TestMulDivRefusesRatherThanReturningAWrongNumber(t *testing.T) {
 55	big := int64(1) << 62
 56	cases := []struct {
 57		name    string
 58		a, b, c int64
 59		msg     string
 60	}{
 61		{"negative a", -1, 2, 3, "xmath: MulDiv on a negative operand"},
 62		{"negative b", 1, -2, 3, "xmath: MulDiv on a negative operand"},
 63		{"zero denominator", 1, 2, 0, "xmath: MulDiv by a non-positive denominator"},
 64		{"negative denominator", 1, 2, -3, "xmath: MulDiv by a non-positive denominator"},
 65		{"quotient too large", big, 8, 2, "xmath: MulDiv quotient overflows int64"},
 66	}
 67	for _, tc := range cases {
 68		t.Run(tc.name, func(t *testing.T) {
 69			defer func() {
 70				r := recover()
 71				if r == nil {
 72					t.Errorf("MulDiv(%d,%d,%d) did not panic", tc.a, tc.b, tc.c)
 73					return
 74				}
 75				uassert.Equal(t, tc.msg, r.(string))
 76			}()
 77			MulDiv(tc.a, tc.b, tc.c)
 78		})
 79	}
 80}
 81
 82func TestMulDivUpRoundsAwayFromZero(t *testing.T) {
 83	cases := []struct {
 84		name    string
 85		a, b, c int64
 86		want    int64
 87	}{
 88		{"exact is unchanged", 10, 3, 5, 6},
 89		{"inexact rounds up", 10, 1, 3, 4},
 90		{"one short rounds up", 7, 1, 2, 4},
 91		{"zero stays zero", 0, 5, 3, 0},
 92	}
 93	for _, tc := range cases {
 94		t.Run(tc.name, func(t *testing.T) {
 95			uassert.Equal(t, tc.want, MulDivUp(tc.a, tc.b, tc.c))
 96		})
 97	}
 98}
 99
100// TestRoundingDirectionIsTheMoneyQuestion states the invariant the two
101// functions exist to give a caller: what is received never exceeds what is
102// owed for the same ratio, so a pool cannot pay out more than it holds.
103func TestRoundingDirectionIsTheMoneyQuestion(t *testing.T) {
104	for _, n := range []int64{1, 2, 3, 7, 99, 1000001} {
105		down := MulDiv(n, 1, 3)
106		up := MulDivUp(n, 1, 3)
107		if up < down {
108			t.Errorf("MulDivUp(%d,1,3)=%d below MulDiv=%d", n, up, down)
109		}
110		if up-down > 1 {
111			t.Errorf("MulDivUp(%d,1,3)=%d more than one above MulDiv=%d", n, up, down)
112		}
113	}
114}