@@ -4,24 +4,62 @@ import BigNumber from 'bignumber.js';
4
4
const MAX_INT = ( ( 1 << 31 ) >>> 0 ) - 1 ;
5
5
6
6
/**
7
- * Calculates and returns the best rational approximation of the given real number.
7
+ * Calculates and returns the best rational (fractional) approximation of the
8
+ * given real number.
9
+ *
8
10
* @private
9
- * @param {string|number|BigNumber } rawNumber Real number
10
- * @throws Error Throws `Error` when the best rational approximation cannot be found.
11
- * @returns {array } first element is n (numerator), second element is d (denominator)
11
+ *
12
+ * This is used internally to convert real-number-like prices into fractions for
13
+ * XDR to use as part of DEX offer & LP management.
14
+ *
15
+ * @param {string|number|BigInt } rawNumber the "real" number to approximate
16
+ *
17
+ * @returns {number[] } the numerator and denominator of the fractional
18
+ * approximation, respectively, where neither value exceeds `MAX_INT32`
19
+ *
20
+ * @throws {Error } throws an `Error` when no good rational approximation can be
21
+ * found.
12
22
*/
13
23
export function best_r ( rawNumber ) {
14
- let number = new BigNumber ( rawNumber ) ;
24
+ BigNumber . DEBUG = true ; // gives us exceptions on bad constructor values
25
+
26
+ // NOTE: We can't convert this to use BigInt because the rational component is
27
+ // crucial to calculating the approximation.
28
+ let number = BigNumber ( rawNumber ) ;
15
29
let a ;
16
30
let f ;
31
+
32
+ // We start with 0/1 and 1/0 as our approximations (the latter is technically
33
+ // undefined but we need it as a starting point)
17
34
const fractions = [
18
35
[ new BigNumber ( 0 ) , new BigNumber ( 1 ) ] ,
19
36
[ new BigNumber ( 1 ) , new BigNumber ( 0 ) ]
20
37
] ;
21
38
let i = 2 ;
22
39
40
+ /*
41
+ The algorithm is a form of the continued fraction expansion (hinted at by the
42
+ filename):
43
+
44
+ > A continued fraction is an expression obtained through an iterative process
45
+ > of representing a number as the sum of its integer part and the reciprocal
46
+ > of another number, then writing this other number as the sum of its integer
47
+ > part and another reciprocal, and so on.
48
+
49
+ https://en.wikipedia.org/wiki/Continued_fraction
50
+
51
+ We run this loop until either:
52
+
53
+ - any part of the fraction exceeds MAX_INT (though JS can handle bigger
54
+ numbers just fine, the xdr.Price object uses int32 values), OR
55
+
56
+ - the "remainder" (`f` in the below loop) is zero (this means we've gotten a
57
+ perfect approximation)
58
+ */
23
59
// eslint-disable-next-line no-constant-condition
24
60
while ( true ) {
61
+ // Compare the delta between the rational `number` and its truncated integer
62
+ // equivalent: `f` is everything after the decimal point.
25
63
if ( number . gt ( MAX_INT ) ) {
26
64
break ;
27
65
}
@@ -39,8 +77,8 @@ export function best_r(rawNumber) {
39
77
number = new BigNumber ( 1 ) . div ( f ) ;
40
78
i += 1 ;
41
79
}
42
- const [ n , d ] = fractions [ fractions . length - 1 ] ;
43
80
81
+ const [ n , d ] = fractions [ fractions . length - 1 ] ;
44
82
if ( n . isZero ( ) || d . isZero ( ) ) {
45
83
throw new Error ( "Couldn't find approximation" ) ;
46
84
}
0 commit comments