fraction_test.gno
2.76 Kb · 99 lines
1package fraction
2
3import (
4 "testing"
5
6 "gno.land/p/nt/uassert/v0"
7)
8
9func mk(t *testing.T, n, d int64) Fraction {
10 t.Helper()
11 f, err := New(n, d)
12 uassert.NoError(t, err)
13 return f
14}
15
16func TestNewReduces(t *testing.T) {
17 uassert.Equal(t, "1/2", mk(t, 2, 4).String())
18 uassert.Equal(t, "2", mk(t, 6, 3).String())
19 uassert.Equal(t, "0", mk(t, 0, 5).String())
20}
21
22// The sign always lives in the numerator, so -1/2 and 1/-2 are the same value.
23func TestSignNormalised(t *testing.T) {
24 a := mk(t, 1, -2)
25 uassert.Equal(t, "-1/2", a.String())
26 uassert.True(t, a.Equal(mk(t, -1, 2)))
27 uassert.True(t, mk(t, -2, -4).Equal(mk(t, 1, 2)))
28}
29
30func TestZeroDenominator(t *testing.T) {
31 _, err := New(1, 0)
32 uassert.ErrorIs(t, err, ErrZeroDenominator)
33}
34
35// The headline: exact arithmetic where floats would drift.
36func TestExactThirds(t *testing.T) {
37 third := mk(t, 1, 3)
38 sum, ok := third.Add(third)
39 uassert.True(t, ok)
40 sum, ok = sum.Add(third)
41 uassert.True(t, ok)
42 uassert.Equal(t, "1", sum.String()) // exactly 1, not 0.9999…
43}
44
45func TestArithmetic(t *testing.T) {
46 a, b := mk(t, 1, 2), mk(t, 1, 3)
47 s, _ := a.Add(b)
48 uassert.Equal(t, "5/6", s.String())
49 d, _ := a.Sub(b)
50 uassert.Equal(t, "1/6", d.String())
51 m, _ := a.Mul(b)
52 uassert.Equal(t, "1/6", m.String())
53 q, _ := a.Div(b)
54 uassert.Equal(t, "3/2", q.String())
55 uassert.Equal(t, "-1/2", a.Neg().String())
56}
57
58func TestDivByZero(t *testing.T) {
59 _, ok := mk(t, 1, 2).Div(Zero())
60 uassert.False(t, ok)
61}
62
63// Overflow must be reported, never silently wrapped into a wrong answer.
64func TestOverflowReported(t *testing.T) {
65 big := Fraction{num: 1 << 62, den: 3}
66 _, ok := big.Mul(Fraction{num: 1 << 62, den: 5})
67 uassert.False(t, ok)
68 _, ok2 := big.Add(Fraction{num: 1 << 62, den: 7})
69 uassert.False(t, ok2)
70}
71
72// Comparison is exact: cross-multiplication, never a decimal detour.
73func TestCmp(t *testing.T) {
74 uassert.Equal(t, -1, mk(t, 1, 3).Cmp(mk(t, 1, 2)))
75 uassert.Equal(t, 1, mk(t, 2, 3).Cmp(mk(t, 1, 2)))
76 uassert.Equal(t, 0, mk(t, 2, 4).Cmp(mk(t, 1, 2)))
77 // 1/3 vs 33333/100000 — a decimal comparison at 5 places would call these equal
78 uassert.Equal(t, 1, mk(t, 1, 3).Cmp(mk(t, 33333, 100000)))
79}
80
81func TestDecimal(t *testing.T) {
82 uassert.Equal(t, "0.333", Decimal(mk(t, 1, 3), 3))
83 uassert.Equal(t, "0.5", Decimal(mk(t, 1, 2), 1))
84 uassert.Equal(t, "1", Decimal(mk(t, 3, 2), 0)) // truncates toward zero
85 uassert.Equal(t, "-0.333", Decimal(mk(t, -1, 3), 3))
86 uassert.Equal(t, "2.000", Decimal(Int(2), 3))
87 uassert.Equal(t, "0", Decimal(Zero(), 0))
88}
89
90// The zero value must behave as 0/1 rather than dividing by zero.
91func TestZeroValueUsable(t *testing.T) {
92 var f Fraction
93 uassert.Equal(t, int64(1), f.Den())
94 uassert.True(t, f.IsZero())
95 uassert.Equal(t, "0", f.String())
96 s, ok := f.Add(Int(1))
97 uassert.True(t, ok)
98 uassert.Equal(t, "1", s.String())
99}