fixedpoint.gno
2.31 Kb · 114 lines
1// Package fixedpoint provides deterministic decimal arithmetic with a
2// fixed number of decimal places, backed by int64. gno.land has no
3// float type reachable from consensus-critical code (and even if it
4// did, floats aren't deterministic across platforms), so any realm
5// doing price/ratio/percentage math needs something like this instead
6// of drifting into ad-hoc basis-points arithmetic scattered everywhere.
7//
8// A Fixed value stores its number scaled by 10^Decimals in Raw. All
9// arithmetic is overflow-checked via math/overflow and panics rather
10// than silently wrapping -- a wrapped overflow in financial math is a
11// far worse failure mode than a loud panic.
12package fixedpoint
13
14import (
15 "math/overflow"
16
17 "gno.land/p/nt/ufmt/v0"
18)
19
20const Decimals = 6
21
22const Scale int64 = 1000000
23
24type Fixed struct {
25 Raw int64
26}
27
28func FromInt(i int64) Fixed {
29 raw, ok := overflow.Mul64(i, Scale)
30 if !ok {
31 panic("fixedpoint: FromInt overflowed")
32 }
33 return Fixed{Raw: raw}
34}
35
36func FromRaw(raw int64) Fixed {
37 return Fixed{Raw: raw}
38}
39
40func (f Fixed) Add(other Fixed) Fixed {
41 raw, ok := overflow.Add64(f.Raw, other.Raw)
42 if !ok {
43 panic("fixedpoint: Add overflowed")
44 }
45 return Fixed{Raw: raw}
46}
47
48func (f Fixed) Sub(other Fixed) Fixed {
49 raw, ok := overflow.Sub64(f.Raw, other.Raw)
50 if !ok {
51 panic("fixedpoint: Sub overflowed")
52 }
53 return Fixed{Raw: raw}
54}
55
56func (f Fixed) Mul(other Fixed) Fixed {
57 product, ok := overflow.Mul64(f.Raw, other.Raw)
58 if !ok {
59 panic("fixedpoint: Mul overflowed")
60 }
61 return Fixed{Raw: product / Scale}
62}
63
64func (f Fixed) Div(other Fixed) Fixed {
65 if other.Raw == 0 {
66 panic("fixedpoint: division by zero")
67 }
68 scaled, ok := overflow.Mul64(f.Raw, Scale)
69 if !ok {
70 panic("fixedpoint: Div overflowed")
71 }
72 return Fixed{Raw: scaled / other.Raw}
73}
74
75func (f Fixed) IsZero() bool {
76 return f.Raw == 0
77}
78
79func (f Fixed) IsNegative() bool {
80 return f.Raw < 0
81}
82
83func (f Fixed) Cmp(other Fixed) int {
84 if f.Raw < other.Raw {
85 return -1
86 }
87 if f.Raw > other.Raw {
88 return 1
89 }
90 return 0
91}
92
93func (f Fixed) String() string {
94 raw := f.Raw
95 negative := raw < 0
96 if negative {
97 raw = -raw
98 }
99
100 whole := raw / Scale
101 frac := raw % Scale
102
103 fracStr := ufmt.Sprintf("%d", frac)
104 for len(fracStr) < Decimals {
105 fracStr = "0" + fracStr
106 }
107
108 sign := ""
109 if negative {
110 sign = "-"
111 }
112
113 return ufmt.Sprintf("%s%d.%s", sign, whole, fracStr)
114}