v2 / vlib / math / fractions / fraction.v
421 lines · 373 sloc · 10.22 KB · c88cc38f4b0cb6160f14c643926176b73ee4becb
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module fractions
5
6import math.big
7
8// Fraction stores a numerator and denominator using the existing `i64` backend.
9pub struct Fraction {
10pub:
11 n i64
12 d i64
13 is_reduced bool
14}
15
16// Rational stores a numerator and denominator using the backend type `T`.
17//
18// Supported backend types are signed builtin integers and `math.big.Integer`.
19pub struct Rational[T] {
20pub:
21 n T
22 d T
23 is_reduced bool
24}
25
26// fraction creates an `i64`-backed Fraction.
27//
28// The denominator must be non-zero. Negative denominators are normalized so
29// that the stored denominator is always positive.
30pub fn fraction(n i64, d i64) Fraction {
31 return rational_to_fraction(rational[i64](n, d))
32}
33
34// big_fraction creates a `math.big.Integer`-backed Rational.
35//
36// The denominator must be non-zero. Negative denominators are normalized so
37// that the stored denominator is always positive.
38pub fn big_fraction(n big.Integer, d big.Integer) Rational[big.Integer] {
39 return rational[big.Integer](n, d)
40}
41
42// rational creates a Rational backed by the concrete numeric type `T`.
43//
44// Fractions created this way are not reduced automatically, but `is_reduced`
45// reflects whether the input was already in lowest terms.
46pub fn rational[T](n T, d T) Rational[T] {
47 ensure_supported_backend[T]()
48 if is_zero_value[T](d) {
49 panic('Denominator cannot be zero')
50 }
51 if is_negative_value[T](d) {
52 return rational[T](negate_value[T](n), negate_value[T](d))
53 }
54 return Rational[T]{
55 n: n
56 d: d
57 is_reduced: gcd_values[T](n, d) == one_value[T]()
58 }
59}
60
61// str returns the fraction in `n/d` form.
62pub fn (f Fraction) str() string {
63 return f.to_rational().str()
64}
65
66// Fraction add using operator overloading.
67pub fn (f1 Fraction) + (f2 Fraction) Fraction {
68 return rational_to_fraction(f1.to_rational() + f2.to_rational())
69}
70
71// Fraction subtract using operator overloading.
72pub fn (f1 Fraction) - (f2 Fraction) Fraction {
73 return rational_to_fraction(f1.to_rational() - f2.to_rational())
74}
75
76// Fraction multiply using operator overloading.
77pub fn (f1 Fraction) * (f2 Fraction) Fraction {
78 return rational_to_fraction(f1.to_rational() * f2.to_rational())
79}
80
81// Fraction divide using operator overloading.
82pub fn (f1 Fraction) / (f2 Fraction) Fraction {
83 return rational_to_fraction(f1.to_rational() / f2.to_rational())
84}
85
86// negate returns the additive inverse of the Fraction.
87pub fn (f Fraction) negate() Fraction {
88 return rational_to_fraction(f.to_rational().negate())
89}
90
91// reciprocal returns the reciprocal of the Fraction.
92pub fn (f Fraction) reciprocal() Fraction {
93 return rational_to_fraction(f.to_rational().reciprocal())
94}
95
96// reduce returns the Fraction reduced to lowest terms.
97pub fn (f Fraction) reduce() Fraction {
98 return rational_to_fraction(f.to_rational().reduce())
99}
100
101// f64 converts the Fraction to 64-bit floating point.
102pub fn (f Fraction) f64() f64 {
103 return f.to_rational().f64()
104}
105
106// return true if f1 == f2.
107pub fn (f1 Fraction) == (f2 Fraction) bool {
108 return cmp_fraction(f1, f2) == 0
109}
110
111// return true if f1 < f2.
112pub fn (f1 Fraction) < (f2 Fraction) bool {
113 return cmp_fraction(f1, f2) < 0
114}
115
116// str returns the fraction in `n/d` form.
117pub fn (f Rational[T]) str() string {
118 return '${f.n}/${f.d}'
119}
120
121//
122// + ---------------------+
123// | Arithmetic functions.|
124// + ---------------------+
125//
126// These are implemented from Knuth, TAOCP Vol 2. Section 4.5
127//
128// Returns a correctly reduced result for both addition and subtraction.
129// NOTE: requires reduced inputs.
130fn general_addition_result[T](f1 Rational[T], f2 Rational[T], addition bool) Rational[T] {
131 d1 := gcd_values[T](f1.d, f2.d)
132 // d1 happens to be 1 around 600/(pi)^2 or 61 percent of the time (Theorem 4.5.2D)
133 if d1 == one_value[T]() {
134 num1n2d := f1.n * f2.d
135 num1d2n := f1.d * f2.n
136 if addition {
137 return Rational[T]{
138 n: add_values[T](num1n2d, num1d2n)
139 d: f1.d * f2.d
140 is_reduced: true
141 }
142 }
143 return Rational[T]{
144 n: sub_values[T](num1n2d, num1d2n)
145 d: f1.d * f2.d
146 is_reduced: true
147 }
148 }
149 // Here d1 > 1.
150 f1den := f1.d / d1
151 f2den := f2.d / d1
152 term1 := f1.n * f2den
153 term2 := f2.n * f1den
154 if addition {
155 t := add_values[T](term1, term2)
156 d2 := gcd_values[T](t, d1)
157 return Rational[T]{
158 n: t / d2
159 d: f1den * (f2.d / d2)
160 is_reduced: true
161 }
162 }
163 t := sub_values[T](term1, term2)
164 d2 := gcd_values[T](t, d1)
165 return Rational[T]{
166 n: t / d2
167 d: f1den * (f2.d / d2)
168 is_reduced: true
169 }
170}
171
172// Fraction add using operator overloading.
173pub fn (f1 Rational[T]) + (f2 Rational[T]) Rational[T] {
174 return general_addition_result[T](f1.reduce(), f2.reduce(), true)
175}
176
177// Fraction subtract using operator overloading.
178pub fn (f1 Rational[T]) - (f2 Rational[T]) Rational[T] {
179 return general_addition_result[T](f1.reduce(), f2.reduce(), false)
180}
181
182// Returns a correctly reduced result for both multiplication and division.
183// NOTE: requires reduced inputs.
184fn general_multiplication_result[T](f1 Rational[T], f2 Rational[T], multiplication bool) Rational[T] {
185 // * Theorem: If f1 and f2 are reduced i.e. gcd(f1.n, f1.d) == 1 and gcd(f2.n, f2.d) == 1,
186 // then gcd(f1.n * f2.n, f1.d * f2.d) == gcd(f1.n, f2.d) * gcd(f1.d, f2.n)
187 // * Knuth poses this an exercise for 4.5.1. - Exercise 2
188 // * Also, note that:
189 // The terms are flipped for multiplication and division, so the gcds must be calculated carefully
190 // We do multiple divisions in order to prevent any possible overflows.
191 // * One more thing:
192 // if d = gcd(a, b) for example, then d divides both a and b
193 if multiplication {
194 d1 := gcd_values[T](f1.n, f2.d)
195 d2 := gcd_values[T](f1.d, f2.n)
196 return Rational[T]{
197 n: (f1.n / d1) * (f2.n / d2)
198 d: (f2.d / d1) * (f1.d / d2)
199 is_reduced: true
200 }
201 } else {
202 d1 := gcd_values[T](f1.n, f2.n)
203 d2 := gcd_values[T](f1.d, f2.d)
204 return Rational[T]{
205 n: (f1.n / d1) * (f2.d / d2)
206 d: (f2.n / d1) * (f1.d / d2)
207 is_reduced: true
208 }
209 }
210}
211
212// Fraction multiply using operator overloading.
213pub fn (f1 Rational[T]) * (f2 Rational[T]) Rational[T] {
214 return general_multiplication_result[T](f1.reduce(), f2.reduce(), true)
215}
216
217// Fraction divide using operator overloading.
218pub fn (f1 Rational[T]) / (f2 Rational[T]) Rational[T] {
219 if is_zero_value[T](f2.n) {
220 panic('Cannot divide by zero')
221 }
222 // If the second fraction is negative, it will mess up the sign.
223 // We need a positive denominator.
224 if is_negative_value[T](f2.n) {
225 return f1.negate() / f2.negate()
226 }
227 return general_multiplication_result[T](f1.reduce(), f2.reduce(), false)
228}
229
230// negate returns the additive inverse of the Rational.
231pub fn (f Rational[T]) negate() Rational[T] {
232 return Rational[T]{
233 n: negate_value[T](f.n)
234 d: f.d
235 is_reduced: f.is_reduced
236 }
237}
238
239// reciprocal returns the reciprocal of the Rational.
240pub fn (f Rational[T]) reciprocal() Rational[T] {
241 if is_zero_value[T](f.n) {
242 panic('Denominator cannot be zero')
243 }
244 return rational[T](f.d, f.n)
245}
246
247// reduce returns the Rational reduced to lowest terms.
248pub fn (f Rational[T]) reduce() Rational[T] {
249 if f.is_reduced {
250 return f
251 }
252 cf := gcd_values[T](f.n, f.d)
253 return Rational[T]{
254 n: f.n / cf
255 d: f.d / cf
256 is_reduced: true
257 }
258}
259
260// f64 converts the Rational to 64-bit floating point.
261pub fn (f Rational[T]) f64() f64 {
262 return to_f64_value[T](f.n) / to_f64_value[T](f.d)
263}
264
265//
266// + ------------------+
267// | Utility functions.|
268// + ------------------+
269//
270
271fn ensure_supported_backend[T]() {
272 $if T is big.Integer || T is i8 || T is i16 || T is i32 || T is i64 || T is int {
273 } $else {
274 $compile_error('fractions: Rational only supports signed builtin integers and math.big.Integer backends')
275 }
276}
277
278@[inline]
279fn (f Fraction) to_rational() Rational[i64] {
280 return Rational[i64]{
281 n: f.n
282 d: f.d
283 is_reduced: f.is_reduced
284 }
285}
286
287@[inline]
288fn rational_to_fraction(r Rational[i64]) Fraction {
289 return Fraction{
290 n: r.n
291 d: r.d
292 is_reduced: r.is_reduced
293 }
294}
295
296fn cmp_fraction(f1 Fraction, f2 Fraction) int {
297 return cmp_values[big.Integer](to_big_integer[i64](f1.n) * to_big_integer[i64](f2.d),
298 to_big_integer[i64](f2.n) * to_big_integer[i64](f1.d))
299}
300
301@[inline]
302fn zero_value[T]() T {
303 $if T is big.Integer {
304 return big.zero_int
305 } $else {
306 return T(0)
307 }
308}
309
310@[inline]
311fn one_value[T]() T {
312 $if T is big.Integer {
313 return big.one_int
314 } $else {
315 return T(1)
316 }
317}
318
319@[inline]
320fn is_zero_value[T](value T) bool {
321 return value == zero_value[T]()
322}
323
324@[inline]
325fn is_negative_value[T](value T) bool {
326 $if T is big.Integer {
327 return value < big.zero_int
328 } $else {
329 return value < 0
330 }
331}
332
333@[inline]
334fn negate_value[T](value T) T {
335 $if T is big.Integer {
336 return value.neg()
337 } $else {
338 return -value
339 }
340}
341
342@[inline]
343fn abs_value[T](value T) T {
344 $if T is big.Integer {
345 return value.abs()
346 } $else {
347 return if value < 0 { -value } else { value }
348 }
349}
350
351fn gcd_values[T](a T, b T) T {
352 $if T is big.Integer {
353 return a.gcd(b)
354 } $else {
355 mut x := abs_value[T](a)
356 mut y := abs_value[T](b)
357 for y != zero_value[T]() {
358 x %= y
359 if x == zero_value[T]() {
360 return y
361 }
362 y %= x
363 }
364 return x
365 }
366}
367
368@[inline]
369fn to_f64_value[T](value T) f64 {
370 $if T is big.Integer {
371 return value.str().f64()
372 } $else {
373 return f64(value)
374 }
375}
376
377@[inline]
378fn to_big_integer[T](value T) big.Integer {
379 $if T is big.Integer {
380 return value
381 } $else {
382 return big.integer_from_i64(i64(value))
383 }
384}
385
386@[inline]
387fn add_values[T](a T, b T) T {
388 return a + b
389}
390
391@[inline]
392fn sub_values[T](a T, b T) T {
393 return a - b
394}
395
396// cmp compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise.
397fn cmp[T](f1 Rational[T], f2 Rational[T]) int {
398 $if T is big.Integer {
399 return cmp_values[T](f1.n * f2.d, f2.n * f1.d)
400 } $else {
401 return cmp_values[big.Integer](to_big_integer[T](f1.n) * to_big_integer[T](f2.d),
402 to_big_integer[T](f2.n) * to_big_integer[T](f1.d))
403 }
404}
405
406fn cmp_values[T](a T, b T) int {
407 if a == b {
408 return 0
409 }
410 return if a > b { 1 } else { -1 }
411}
412
413// return true if f1 == f2.
414pub fn (f1 Rational[T]) == (f2 Rational[T]) bool {
415 return cmp[T](f1, f2) == 0
416}
417
418// return true if f1 < f2.
419pub fn (f1 Rational[T]) < (f2 Rational[T]) bool {
420 return cmp[T](f1, f2) < 0
421}
422