ratio.mx raw
1 // Package ratio implements exact rational arithmetic using integer
2 // numerator/denominator pairs. All values are stored in canonical form:
3 // GCD-reduced with positive denominator.
4 package ratio
5
6 // Ratio is an exact rational number Num/Denom.
7 // The zero value (0/0) is not valid; use Zero instead.
8 type Ratio struct {
9 Num int64 `json:"num"`
10 Denom int64 `json:"denom"`
11 }
12
13 // Predefined constants.
14 var Zero Ratio
15 var One Ratio
16 var Half Ratio
17
18 func main() {
19 Zero = Ratio{0, 1}
20 One = Ratio{1, 1}
21 Half = Ratio{1, 2}
22 }
23
24 // New creates a normalized Ratio. Panics if denom is zero.
25 func New(num, denom int64) (r Ratio) {
26 if denom == 0 {
27 panic("ratio: zero denominator")
28 }
29 return normalize(num, denom)
30 }
31
32 // FromInt returns n/1.
33 func FromInt(n int64) (r Ratio) {
34 return Ratio{n, 1}
35 }
36
37 // normalize reduces num/denom by GCD and ensures positive denominator.
38 func normalize(num, denom int64) (r Ratio) {
39 if num == 0 {
40 return Ratio{0, 1}
41 }
42 if denom < 0 {
43 num = -num
44 denom = -denom
45 }
46 g := gcd(abs(num), denom)
47 return Ratio{num / g, denom / g}
48 }
49
50 // Add returns r + other.
51 func (r *Ratio) Add(other *Ratio) (result Ratio) {
52 return normalize(
53 r.Num*other.Denom+other.Num*r.Denom,
54 r.Denom*other.Denom,
55 )
56 }
57
58 // Sub returns r - other.
59 func (r *Ratio) Sub(other *Ratio) (result Ratio) {
60 return normalize(
61 r.Num*other.Denom-other.Num*r.Denom,
62 r.Denom*other.Denom,
63 )
64 }
65
66 // Mul returns r * other.
67 func (r *Ratio) Mul(other *Ratio) (result Ratio) {
68 return normalize(
69 r.Num*other.Num,
70 r.Denom*other.Denom,
71 )
72 }
73
74 // Div returns r / other. Panics if other is zero.
75 func (r *Ratio) Div(other *Ratio) (result Ratio) {
76 if other.Num == 0 {
77 panic("ratio: division by zero")
78 }
79 return normalize(
80 r.Num*other.Denom,
81 r.Denom*other.Num,
82 )
83 }
84
85 // Neg returns -r.
86 func (r *Ratio) Neg() (result Ratio) {
87 return Ratio{-r.Num, r.Denom}
88 }
89
90 // Abs returns |r|.
91 func (r *Ratio) Abs() (result Ratio) {
92 if r.Num < 0 {
93 return Ratio{-r.Num, r.Denom}
94 }
95 return *r
96 }
97
98 // Less returns true if r < other.
99 func (r *Ratio) Less(other *Ratio) (result bool) {
100 return r.Num*other.Denom < other.Num*r.Denom
101 }
102
103 // LessEq returns true if r <= other.
104 func (r *Ratio) LessEq(other *Ratio) (result bool) {
105 return r.Num*other.Denom <= other.Num*r.Denom
106 }
107
108 // Greater returns true if r > other.
109 func (r *Ratio) Greater(other *Ratio) (result bool) {
110 return other.Less(r)
111 }
112
113 // Equal returns true if r == other.
114 func (r *Ratio) Equal(other *Ratio) (result bool) {
115 return r.Num == other.Num && r.Denom == other.Denom
116 }
117
118 // IsZero returns true if r == 0.
119 func (r *Ratio) IsZero() (result bool) {
120 return r.Num == 0
121 }
122
123 // IsPositive returns true if r > 0.
124 func (r *Ratio) IsPositive() (result bool) {
125 return r.Num > 0
126 }
127
128 // IsNegative returns true if r < 0.
129 func (r *Ratio) IsNegative() (result bool) {
130 return r.Num < 0
131 }
132
133 // Float64 converts to float64 for display purposes only.
134 func (r *Ratio) Float64() (result float64) {
135 if r.Denom == 0 {
136 return 0
137 }
138 return float64(r.Num) / float64(r.Denom)
139 }
140
141 // ScaleInt computes (n * Num) / Denom using integer arithmetic.
142 func (r *Ratio) ScaleInt(n int64) (result int64) {
143 return (n * r.Num) / r.Denom
144 }
145
146 // Max returns the larger of a and b.
147 func Max(a, b *Ratio) (result Ratio) {
148 if b.Less(a) {
149 return *a
150 }
151 return *b
152 }
153
154 // Min returns the smaller of a and b.
155 func Min(a, b *Ratio) (result Ratio) {
156 if a.Less(b) {
157 return *a
158 }
159 return *b
160 }
161
162 // Clamp returns r clamped to [lo, hi].
163 func (r *Ratio) Clamp(lo, hi *Ratio) (result Ratio) {
164 if r.Less(lo) {
165 return *lo
166 }
167 if hi.Less(r) {
168 return *hi
169 }
170 return *r
171 }
172
173 // String returns "Num/Denom" for debugging.
174 func (r *Ratio) String() (s string) {
175 return r.Num.String() | "/" | r.Denom.String()
176 }
177
178 // gcd returns the greatest common divisor of a and b.
179 func gcd(a, b int64) (result int64) {
180 for b != 0 {
181 a, b = b, a%b
182 }
183 return a
184 }
185
186 // abs returns the absolute value of n.
187 func abs(n int64) (result int64) {
188 if n < 0 {
189 return -n
190 }
191 return n
192 }
193