aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/math/fractions/fraction.v
blob: 69c8f6343ae6675bd2fa2994a19020a357c819e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module fractions

import math
import math.bits

// Fraction Struct
// ---------------
// A Fraction has a numerator (n) and a denominator (d). If the user uses
// the helper functions in this module, then the following are guaranteed:
// 1. If the user provides n and d with gcd(n, d) > 1, the fraction will
// not be reduced automatically.
// 2. d cannot be set to zero. The factory function will panic.
// 3. If provided d is negative, it will be made positive. n will change as well.
struct Fraction {
pub:
	n          i64
	d          i64
	is_reduced bool
}

// A factory function for creating a Fraction, adds a boundary condition
// to ensure that the denominator is non-zero. It automatically converts
// the negative denominator to positive and adjusts the numerator.
// NOTE: Fractions created are not reduced by default.
pub fn fraction(n i64, d i64) Fraction {
	if d == 0 {
		panic('Denominator cannot be zero')
	}
	// The denominator is always guaranteed to be positive (and non-zero).
	if d < 0 {
		return fraction(-n, -d)
	}
	return Fraction{
		n: n
		d: d
		is_reduced: math.gcd(n, d) == 1
	}
}

// To String method
pub fn (f Fraction) str() string {
	return '$f.n/$f.d'
}

//
// + ---------------------+
// | Arithmetic functions.|
// + ---------------------+
//
// These are implemented from Knuth, TAOCP Vol 2. Section 4.5
//
// Returns a correctly reduced result for both addition and subtraction
// NOTE: requires reduced inputs
fn general_addition_result(f1 Fraction, f2 Fraction, addition bool) Fraction {
	d1 := math.gcd(f1.d, f2.d)
	// d1 happens to be 1 around 600/(pi)^2 or 61 percent of the time (Theorem 4.5.2D)
	if d1 == 1 {
		num1n2d := f1.n * f2.d
		num1d2n := f1.d * f2.n
		n := if addition { num1n2d + num1d2n } else { num1n2d - num1d2n }
		return Fraction{
			n: n
			d: f1.d * f2.d
			is_reduced: true
		}
	}
	// Here d1 > 1.
	f1den := f1.d / d1
	f2den := f2.d / d1
	term1 := f1.n * f2den
	term2 := f2.n * f1den
	t := if addition { term1 + term2 } else { term1 - term2 }
	d2 := math.gcd(t, d1)
	return Fraction{
		n: t / d2
		d: f1den * (f2.d / d2)
		is_reduced: true
	}
}

// Fraction add using operator overloading
pub fn (f1 Fraction) + (f2 Fraction) Fraction {
	return general_addition_result(f1.reduce(), f2.reduce(), true)
}

// Fraction subtract using operator overloading
pub fn (f1 Fraction) - (f2 Fraction) Fraction {
	return general_addition_result(f1.reduce(), f2.reduce(), false)
}

// Returns a correctly reduced result for both multiplication and division
// NOTE: requires reduced inputs
fn general_multiplication_result(f1 Fraction, f2 Fraction, multiplication bool) Fraction {
	// * Theorem: If f1 and f2 are reduced i.e. gcd(f1.n, f1.d) ==  1 and gcd(f2.n, f2.d) == 1,
	// then gcd(f1.n * f2.n, f1.d * f2.d) == gcd(f1.n, f2.d) * gcd(f1.d, f2.n)
	// * Knuth poses this an exercise for 4.5.1. - Exercise 2
	// * Also, note that:
	// The terms are flipped for multiplication and division, so the gcds must be calculated carefully
	// We do multiple divisions in order to prevent any possible overflows.
	// * One more thing:
	// if d = gcd(a, b) for example, then d divides both a and b
	if multiplication {
		d1 := math.gcd(f1.n, f2.d)
		d2 := math.gcd(f1.d, f2.n)
		return Fraction{
			n: (f1.n / d1) * (f2.n / d2)
			d: (f2.d / d1) * (f1.d / d2)
			is_reduced: true
		}
	} else {
		d1 := math.gcd(f1.n, f2.n)
		d2 := math.gcd(f1.d, f2.d)
		return Fraction{
			n: (f1.n / d1) * (f2.d / d2)
			d: (f2.n / d1) * (f1.d / d2)
			is_reduced: true
		}
	}
}

// Fraction multiply using operator overloading
pub fn (f1 Fraction) * (f2 Fraction) Fraction {
	return general_multiplication_result(f1.reduce(), f2.reduce(), true)
}

// Fraction divide using operator overloading
pub fn (f1 Fraction) / (f2 Fraction) Fraction {
	if f2.n == 0 {
		panic('Cannot divide by zero')
	}
	// If the second fraction is negative, it will
	// mess up the sign. We need positive denominator
	if f2.n < 0 {
		return f1.negate() / f2.negate()
	}
	return general_multiplication_result(f1.reduce(), f2.reduce(), false)
}

// Fraction negate method
pub fn (f Fraction) negate() Fraction {
	return Fraction{
		n: -f.n
		d: f.d
		is_reduced: f.is_reduced
	}
}

// Fraction reciprocal method
pub fn (f Fraction) reciprocal() Fraction {
	if f.n == 0 {
		panic('Denominator cannot be zero')
	}
	return Fraction{
		n: f.d
		d: f.n
		is_reduced: f.is_reduced
	}
}

// Fraction method which reduces the fraction
pub fn (f Fraction) reduce() Fraction {
	if f.is_reduced {
		return f
	}
	cf := math.gcd(f.n, f.d)
	return Fraction{
		n: f.n / cf
		d: f.d / cf
		is_reduced: true
	}
}

// f64 converts the Fraction to 64-bit floating point
pub fn (f Fraction) f64() f64 {
	return f64(f.n) / f64(f.d)
}

//
// + ------------------+
// | Utility functions.|
// + ------------------+
//
// Returns the absolute value of an i64
fn abs(num i64) i64 {
	if num < 0 {
		return -num
	} else {
		return num
	}
}

// cmp_i64s compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise
fn cmp_i64s(a i64, b i64) int {
	if a == b {
		return 0
	} else if a > b {
		return 1
	} else {
		return -1
	}
}

// cmp_f64s compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise
fn cmp_f64s(a f64, b f64) int {
	// V uses epsilon comparison internally
	if a == b {
		return 0
	} else if a > b {
		return 1
	} else {
		return -1
	}
}

// Two integers are safe to multiply when their bit lengths
// sum up to less than 64 (conservative estimate).
fn safe_to_multiply(a i64, b i64) bool {
	return (bits.len_64(u64(abs(a))) + bits.len_64(u64(abs(b)))) < 64
}

// cmp compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise
fn cmp(f1 Fraction, f2 Fraction) int {
	if safe_to_multiply(f1.n, f2.d) && safe_to_multiply(f2.n, f1.d) {
		return cmp_i64s(f1.n * f2.d, f2.n * f1.d)
	} else {
		return cmp_f64s(f1.f64(), f2.f64())
	}
}

// +-----------------------------+
// | Public comparison functions |
// +-----------------------------+
// equals returns true if both the Fractions are equal
pub fn (f1 Fraction) equals(f2 Fraction) bool {
	return cmp(f1, f2) == 0
}

// ge returns true if f1 >= f2
pub fn (f1 Fraction) ge(f2 Fraction) bool {
	return cmp(f1, f2) >= 0
}

// gt returns true if f1 > f2
pub fn (f1 Fraction) gt(f2 Fraction) bool {
	return cmp(f1, f2) > 0
}

// le returns true if f1 <= f2
pub fn (f1 Fraction) le(f2 Fraction) bool {
	return cmp(f1, f2) <= 0
}

// lt returns true if f1 < f2
pub fn (f1 Fraction) lt(f2 Fraction) bool {
	return cmp(f1, f2) < 0
}