@@ -4,24 +4,62 @@ import BigNumber from 'bignumber.js';
44const MAX_INT = ( ( 1 << 31 ) >>> 0 ) - 1 ;
55
66/**
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+ *
810 * @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.
1222 */
1323export 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 ) ;
1529 let a ;
1630 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)
1734 const fractions = [
1835 [ new BigNumber ( 0 ) , new BigNumber ( 1 ) ] ,
1936 [ new BigNumber ( 1 ) , new BigNumber ( 0 ) ]
2037 ] ;
2138 let i = 2 ;
2239
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+ */
2359 // eslint-disable-next-line no-constant-condition
2460 while ( true ) {
61+ // Compare the delta between the rational `number` and its truncated integer
62+ // equivalent: `f` is everything after the decimal point.
2563 if ( number . gt ( MAX_INT ) ) {
2664 break ;
2765 }
@@ -39,8 +77,8 @@ export function best_r(rawNumber) {
3977 number = new BigNumber ( 1 ) . div ( f ) ;
4078 i += 1 ;
4179 }
42- const [ n , d ] = fractions [ fractions . length - 1 ] ;
4380
81+ const [ n , d ] = fractions [ fractions . length - 1 ] ;
4482 if ( n . isZero ( ) || d . isZero ( ) ) {
4583 throw new Error ( "Couldn't find approximation" ) ;
4684 }
0 commit comments