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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}