> 2n 2n > 2n * 2 Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions > 2n * BigInt(2) 4n > 2n ** 64n 18446744073709551616n > 2n ** 128n 340282366920938463463374607431768211456n
リスト : 簡単な関数 // 絶対値 function abs(n) { return n > 0n ? n : -n; } // 符号 function sign(n) { if (n == 0n) return 0; else if (n > 0n) return 1; else return -1; } // 最大値と最小値 function max(x, ...args) { return args.reduce((a, b) => b > a ? b : a, x); } function min(x, ...args) { return args.reduce((a, b) => b < a ? b : a, x); } // 最大公約数 function gcd(a, b) { while (b > 0n) [a, b] = [b, a % b]; return a; } // 最小公倍数 function lcm(a, b) { return a * b / gcd(a, b); } // 数列の生成 function iota(s, n, fn = x => x) { let buff = new Array(n); let i = 0; while (n-- > 0) buff[i++] = fn(s++); return buff; }
> abs(12345678n) 12345678n > abs(-12345678n) 12345678n > abs(0n) 0n > sign(12345678n) 1 > sign(-12345678n) -1 > sign(0n) 0 > max(3,5,1,2,4) 5 > min(3,5,1,2,4) 1 > gcd(42n, 30n) 6n > gcd(15n, 70n) 5n > gcd(4n ** 64n, 2n ** 64n) 18446744073709551616n > lcm(5n, 7n) 35n > lcm(14n, 35n) 70n > iota(1n, 10) [ 1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 10n ] > iota(1n, 10, x => x * x) [ 1n, 4n, 9n, 16n, 25n, 36n, 49n, 64n, 81n, 100n ] > iota(1n, 20).reduce((a, x) => lcm(a, x), 1n) 232792560n
リスト : 階乗 function fact(n) { let a = 1n; while (n > 0) a *= BigInt(n--); return a; }
> for (let i = 0; i < 20; i++) console.log(fact(i)) 1n 1n 2n 6n 24n 120n 720n 5040n 40320n 362880n 3628800n 39916800n 479001600n 6227020800n 87178291200n 1307674368000n 20922789888000n 355687428096000n 6402373705728000n 121645100408832000n undefined > fact(50) 30414093201713378043612608166064768844377641568960512000000000000n
リスト : フィボナッチ数 (繰り返し) function fibo(n, a = 0n, b = 1n) { while (n-- > 0) [a, b] = [b, a + b]; return a; }
> for (let i = 50; i < 60; i++) console.log(fibo(i)) 12586269025n 20365011074n 32951280099n 53316291173n 86267571272n 139583862445n 225851433717n 365435296162n 591286729879n 956722026041n undefined > fibo(100) 354224848179261915075n > fibo(200) 280571172992510140037611932413038677189525n
次の漸化式で生成される数列をトリボナッチ数といいます。
リスト : トリボナッチ数 function tribo(n, a = 0n, b = 0n, c = 1n) { while (n-- > 0) [a, b, c] = [b, c, a + b + c]; return a; }
> for (let i = 0; i < 20; i++) console.log(tribo(i)) 0n 0n 1n 1n 2n 4n 7n 13n 24n 44n 81n 149n 274n 504n 927n 1705n 3136n 5768n 10609n 19513n undefined > tribo(100) 53324762928098149064722658n
次の漸化式で生成される数列をテトラナッチ数列といいます。
リスト : テトラナッチ数 function tetra(n) { let [a, b, c, d] = [0n, 0n, 0n, 1n]; while (n-- > 0) [a, b, c, d] = [b, c, d, a + b + c + d]; return a; }
> for (let i = 0; i < 20; i++) console.log(tetra(i)) 0n 0n 0n 1n 1n 2n 4n 8n 15n 29n 56n 108n 208n 401n 773n 1490n 2872n 5536n 10671n 20569n undefined > tetra(100) 2505471397838180985096739296n
組み合わせの数 \({}_n \mathrm{C}_r\) を求めるプログラムを作ります。\({}_n \mathrm{C}_r\) を求めるには、次の公式を使えば簡単です。
リスト : 組み合わせの数 function combination(n, r) { return fact(n) / (fact(r) * fact(n - r)); }
> for (let r = 30; r <= 50; r++) console.log([r * 2, r], "=", combination(r * 2, r)) [ 60, 30 ] = 118264581564861424n [ 62, 31 ] = 465428353255261088n [ 64, 32 ] = 1832624140942590534n [ 66, 33 ] = 7219428434016265740n [ 68, 34 ] = 28453041475240576740n [ 70, 35 ] = 112186277816662845432n [ 72, 36 ] = 442512540276836779204n [ 74, 37 ] = 1746130564335626209832n [ 76, 38 ] = 6892620648693261354600n [ 78, 39 ] = 27217014869199032015600n [ 80, 40 ] = 107507208733336176461620n [ 82, 41 ] = 424784580848791721628840n [ 84, 42 ] = 1678910486211891090247320n [ 86, 43 ] = 6637553085023755473070800n [ 88, 44 ] = 26248505381684851188961800n [ 90, 45 ] = 103827421287553411369671120n [ 92, 46 ] = 410795449442059149332177040n [ 94, 47 ] = 1625701140345170250548615520n [ 96, 48 ] = 6435067013866298908421603100n [ 98, 49 ] = 25477612258980856902730428600n [ 100, 50 ] = 100891344545564193334812497256n undefined
点を多角形の形に並べたとき、その総数を多角数 (polygonal number) といいます。
1 3 6 10 15 ● ● ● ● ● ●● ●● ●● ●● ●●● ●●● ●●● ●●●● ●●●● ●●●●● 図 : 三角数 |
1 4 9 16 25 ● ●● ●●● ●●●● ●●●●● ●● ●●● ●●●● ●●●●● ●●● ●●●● ●●●●● ●●●● ●●●●● ●●●●● 図 : 四角数 |
1 5 12 22 ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● 図 : 五角数
三角形に配置したものを三角数 (triangular number)、四角形に配置したものを四角数 (square number)、五角形に配置したものを五角数 (pentagonal number) といいます。三角数、四角数、五角数を上図に示します。多角数の点の増分を表に示すと、次のようになります。
n 三角数 四角数 五角数 ---+----------------------------------------------------------- 1 | 1 1 1 2 | 3 = 1+2 4 = 1+3 5 = 1+4 3 | 6 = 1+2+3 9 = 1+3+5 12 = 1+4+7 4 | 10 = 1+2+3+4 16 = 1+3+5+7 22 = 1+4+7+10 5 | 15 = 1+2+3+4+5 25 = 1+3+5+7+9 35 = 1+4+7+10+13 6 | 21 = 1+2+3+4+5+6 36 = 1+3+5+7+9+11 51 = 1+4+7+10+13+16 ・・・・・・ ・・・・・・・ ・・・・・ n | n(n + 1) / 2 n^2 n(3n - 1) / 2
表を見ればお分かりのように、三角数は公差 1、四角数は公差 2、五角数は公差 3、p 角数は公差 p - 2 の等差数列の和になります。初項を a, 公差を d とすると、等差数列の和 \(S_n\) は次式で求めることができます。
a = 1, d = p - 2 を代入して計算すると、多角数 \(P_{p,n}\) は次式で求めることができます。
この式を JavaScript でプログラムすると、次のようになります。
リスト : 多角数 function polygonalNumber(a, b) { let p = BigInt(a), n = BigInt(b); return ((p - 2n) * n * n - (p - 4n) * n) / 2n; }
それでは実行してみましょう。
> for (let p = 3; p <= 8; p++) { ... console.log(iota(1, 16).map(r => polygonal_number(p, r)).toString()) ... } 1,3,6,10,15,21,28,36,45,55,66,78,91,105,120,136 1,4,9,16,25,36,49,64,81,100,121,144,169,196,225,256 1,5,12,22,35,51,70,92,117,145,176,210,247,287,330,376 1,6,15,28,45,66,91,120,153,190,231,276,325,378,435,496 1,7,18,34,55,81,112,148,189,235,286,342,403,469,540,616 1,8,21,40,65,96,133,176,225,280,341,408,481,560,645,736 undefined
三角数かつ四角数である数を平方三角数 (square triangular number) といいます。JavaScript でナイーブにプログラムすると次のようになります。
リスト : 平方三角数 function square_triangular(n) { let i = 0, j = 0, r = []; let p3 = polygonal_number(3, i++); let p4 = polygonal_number(4, j++); while (p3 <= n && p4 <= n) { if (p3 == p4) { r.push(p3); p3 = polygonal_number(3, i++); p4 = polygonal_number(4, j++); } else if (p3 < p4) { p3 = polygonal_number(3, i++); } else { p4 = polygonal_number(4, j++); } } return r; }
実行結果は次のようになります。
> square_triangular(100000000000n) [ 0n, 1n, 36n, 1225n, 41616n, 1413721n, 48024900n, 1631432881n, 55420693056n ]
ところで、平方三角数 - Wikipedia に掲載されている平方三角数の公式 (一般項と漸化式) を使うと、もっと簡単にプログラムすることができます。
漸化式を繰り返しでプログラムすると次のようになります。
リスト : 平方三角数 (2) function square_triangular1(n) { let a = 0n, b = 1n; while (n-- > 0) [a, b] = [b, 34n * b - a + 2n] return a; }
> for (let i = 0; i <= 20; i++) console.log(square_triangular1(i)) 0n 1n 36n 1225n 41616n 1413721n 48024900n 1631432881n 55420693056n 1882672131025n 63955431761796n 2172602007770041n 73804512832419600n 2507180834294496361n 85170343853180456676n 2893284510173841030625n 98286503002057414584576n 3338847817559778254844961n 113422539294030403250144100n 3853027488179473932250054441n 130889512058808083293251706896n undefined
カタラン数 - Wikipedia によると、バランスの取れたカッコ列の総数や多角形を三角形分割したときの総数がカタラン数になるそうです。カタラン数は次に示す公式で求めることができます。
カタラン数は組み合わせの数を求める関数 combination を使うと簡単に求めることができます。
リスト : カタラン数 function catalan(n) { return combination(2 * n, n) / BigInt(n + 1); }
> for (let i = 0; i <= 20; i++) console.log(catalan(i)) 1n 1n 2n 5n 14n 42n 132n 429n 1430n 4862n 16796n 58786n 208012n 742900n 2674440n 9694845n 35357670n 129644790n 477638700n 1767263190n 6564120420n undefined > catalan(50) 1978261657756160653623774456n > catalan(100) 896519947090131496687170070074100632420837521538745909320n
「完全順列 (derangement)」の総数を「モンモール数 (Montmort number)」といいます。モンモール数は次の漸化式で求めることができます。
リスト : モンモール数 function montmort(n) { let a = 0n, b = 1n; for (let i = 1; i < n; i++) [a, b] = [b, BigInt(i + 1) * (a + b)] return a; }
> for (let i = 1; i <= 20; i++) console.log(montmort(i)) 0n 1n 2n 9n 44n 265n 1854n 14833n 133496n 1334961n 14684570n 176214841n 2290792932n 32071101049n 481066515734n 7697064251745n 130850092279664n 2355301661033953n 44750731559645106n 895014631192902121n undefined > montmort(50) 11188719610782480504630258070757734324011354208865721592720336801n
「集合を分割する方法」の総数を「ベル数 (Bell Number)」といい、次の漸化式で求めることができます。
リスト : ベル数 // 添字付き畳み込み function fold_with_index(f, a, xs) { for (let i = 0; i < xs.length; i++) a = f(i, xs[i], a); return a; } // ベル数 function bell_number(n) { let bs = [1n]; for (let i = 0; i < n; i++) bs.push(fold_with_index((k, x, a) => combination(i, k) * x + a, 0n, bs)); return bs.pop(); }
bellNumber は公式をそのままプログラムするだけです。累積変数 bs にベル数を格納します。\({}_n \mathrm{C}_k\) は関数 combination で求めます。
\({}_n \mathrm{C}_k \times B(k)\) の総和は関数 fold_with_index で計算します。fold_with_index は添字を関数に渡して畳み込みを行います。ラムダ式の引数 k が添字、x がリストの要素、a が累積変数です。
> for (let i = 0; i <= 20; i++) console.log(bell_number(i)) 1n 1n 2n 5n 15n 52n 203n 877n 4140n 21147n 115975n 678570n 4213597n 27644437n 190899322n 1382958545n 10480142147n 82864869804n 682076806159n 5832742205057n 51724158235372n undefined > bell_number(50) 185724268771078270438257767181908917499221852770n
整数 n を 1 以上の自然数の和で表すことを考えます。これを「整数の分割」といいます。整数を分割するとき、同じ自然数を何回使ってもかまいませんが、並べる順序が違うだけのものは同じ分割とします。簡単な例を示しましょう。次の図を見てください。
─┬─ 6 : 6 │ ├─ 5 ─ 1 : 5 + 1 │ ├─ 4 ┬ 2 : 4 + 2 │ │ │ └ 1 ─ 1 : 4 + 1 + 1 │ ├─ 3 ┬ 3 : 3 + 3 │ │ │ ├ 2 ─ 1 : 3 + 2 + 1 │ │ │ └ 1 ─ 1 ─ 1 : 3 + 1 + 1 + 1 │ ├─ 2 ┬ 2 ┬ 2 : 2 + 2 + 2 │ │ │ │ │ └ 1 ─ 1 : 2 + 2 + 1 + 1 │ │ │ └ 1 ─ 1 ─ 1 ─ 1 : 2 + 1 + 1 + 1 + 1 │ └─ 1 ─ 1 ─ 1 ─ 1 ─ 1 ─ 1 : 1 + 1 + 1 + 1 + 1 + 1 図 : 整数 6 の分割
6 の場合、分割の仕方は上図のように 11 通りあります。この数を「分割数」といいます。分割の仕方を列挙する場合、整数 n から k 以下の整数を選んでいくと考えてください。まず、6 から 6 を選びます。すると、残りは 0 になるので、これ以上整数を分割することはできません。次に、6 から 5 を選びます。残りは 1 になるので、1 を選ぶしか方法はありません。
次に、4 を選びます。残りは 2 になるので、2 から 2 以下の整数を分割する方法になります。2 から 2 を選ぶと残りは 0 になるので 2 が得られます。1 を選ぶと残りは 1 になるので、1 + 1 が得られます。したがって、4 + 2, 4 + 1 + 1 となります。同様に、6 から 3 を選ぶと、残りは 3 から 3 以下の整数を選ぶ方法になります。
6 から 2 以下の整数を選ぶ方法は、残り 4 から 2 以下の整数を選ぶ方法になり、そこで 2 を選ぶと 2 から 2 以下の整数を選ぶ方法になります。1 を選ぶと 4 から 1 以下の整数を選ぶ方法になりますが、これは 1 通りしかありません。最後に 6 から 1 を選びますが、これも 1 通りしかありません。これらをすべて足し合わせると 11 通りになります。
整数 n を k 以下の整数で分割する総数を求める関数を p(n, k) とすると、p(n, k) は次のように定義することができます。
たとえば、p(6, 6) は次のように計算することができます。
p(6, 6) => p(0, 6) + p(6, 5) => 1 + p(1, 5) + p(6, 4) => 1 + 1 + p(2, 4) + p(6, 3) => 1 + 1 + 2 + 7 => 11 p(2, 4) => p(-2, 4) + p(2, 3) => 0 + p(-1, 3) + p(2, 2) => 0 + 0 + p(0, 2) + p(2, 1) => 0 + 0 + 1 + 1 => 2
p(6, 3) => p(3, 3) + p(6, 2) => p(0, 3) + p(3, 2) + p(4, 2) + p(6, 1) => 1 + p(1, 2) + p(3, 1) + p(2, 2) + p(4, 1) + 1 => 1 + 1 + 1 + p(0, 2) + p(2, 1) + 1 + 1 => 1 + 1 + 1 + 1 + 1 + 1 + 1 => 7
これをそのままプログラムすると実行時間は遅くなるのですが、動的計画法を使うと、大きな値でも高速に計算することができます。次の図を見てください。
k 1 : [1, 1, 1, 1, 1, 1, 1] 2 : [1, 1, 1+1=2, 1+1=2, 2+1=3, 2+1=3, 3+1=4] => [1, 1, 2, 2, 3, 3, 4] 3: [1, 1, 2, 1+2=3, 1+3=4, 2+3=5, 3+4=7] => [1, 1, 2, 3, 4, 5, 7] 4: [1, 1, 2, 3, 1+4=4, 1+5=6, 2+7=9] => [1, 1, 2, 3, 5, 6, 9 5: [1, 1, 2, 3, 5, 1+6=7, 1+9=10] => [1, 1, 2, 3, 5, 7, 10] 6: [1, 1, 2, 3, 5, 7, 10+1=11] => [1, 1, 2, 3, 5, 7, 11]
大きさ n + 1 の配列を用意します。配列の添字が n を表していて、p(n, 1) から順番に値を求めていきます。p(n, 1) の値は 1 ですから、配列の要素は 1 に初期化します。次に、p(n, 2) の値を求めます。定義により p(n, 2) = p(n - 2, 2) + p(n, 1) なので、2 番目以降の要素に n - 2 番目の要素を加算すれば求めることができます。あとは、k の値をひとつずつ増やして同様の計算を行えば p(n, n) の値を求めることができます。
リスト : 分割数 (動的計画法) function partition_number(n) { let table = new Array(n + 1); table.fill(1n); for (let k = 2; k <= n; k++) { for (let m = k; m <= n; m++) table[m] += table[m - k] } return table[n]; }
> for (let i = 1; i <= 20; i++) console.log(partition_number(i)) 1n 2n 3n 5n 7n 11n 15n 22n 30n 42n 56n 77n 101n 135n 176n 231n 297n 385n 490n 627n undefined > partition_number(100) 190569292n > partition_number(200) 3972999029388n > partition_number(1000) 24061467864032622473692149727991n
ところで、数がもっと大きくなると動的計画法を使ったプログラムでも遅くなります。もっと高速に求める方法があるので、興味のある方は拙作のページ Puzzle DE Programming: 「分割数」をお読みくださいませ。
// // ratio.js : 有理数 (分数) // // Copyright (c) 2025 Makoto Hiroi // // Released under the MIT license // https://opensource.org/license/mit/ // // 絶対値 function abs(n) { return n > 0n ? n : -n; } // 最大公約数 function gcd(a, b) { while (b > 0n) [a, b] = [b, a % b]; return a; } // 符号 function sign(n) { if (n == 0n) return 0; else if (n > 0n) return 1; else return -1; } // 正規化 function normalize(a, b) { let g = gcd(abs(a), abs(b)); return (b < 0n) ? [(-a) / g, (-b) / g] : [a / g, b / g]; } // 有理数の定義 class Ratio { constructor(a, b) { let [x, y] = normalize(a, b); this._numerator = x; this._denominator = y; } get numerator() { return this._numerator; } get denominator() { return this._denominator; } // 整数か? isInteger() { return this.denominator == 1n; } // BigInt に変換 toBigInt() { return this.numerator / this.denominator; } // 数に変換 toNumber() { return Number(this.numerator) / Number(this.denominator); } // 文字列 toString() { if (this.denominator == 1n) { return this.numerator.toString(); } else { return this.numerator.toString() + "/" + this.denominator.toString(); } } // 算術演算 add(other) { let {_numerator: anum, _denominator: aden} = this; let {_numerator: bnum, _denominator: bden} = other; return new Ratio(anum * bden + bnum * aden, aden * bden); } sub(other) { let {_numerator: anum, _denominator: aden} = this; let {_numerator: bnum, _denominator: bden} = other; return new Ratio(anum * bden - bnum * aden, aden * bden); } mul(other) { let {_numerator: anum, _denominator: aden} = this; let {_numerator: bnum, _denominator: bden} = other; return new Ratio(anum * bnum, aden * bden); } div(other) { let {_numerator: anum, _denominator: aden} = this; let {_numerator: bnum, _denominator: bden} = other; return new Ratio(anum * bden, aden * bnum); } inv() { let {_numerator: anum, _denominator: aden} = this; return new Ratio(aden, anum); } // 比較演算 equals(other) { let {_numerator: anum, _denominator: aden} = this; let {_numerator: bnum, _denominator: bden} = other; return anum == bnum && aden == bden; } compare_ratio(other) { let {_numerator: anum, _denominator: aden} = this; let {_numerator: bnum, _denominator: bden} = other; return sign(anum * bden - bnum * aden); } eq(other){ return this.compare_ratio(other) == 0; } lt(other){ return this.compare_ratio(other) < 0; } le(other){ return this.compare_ratio(other) <= 0; } gt(other){ return this.compare_ratio(other) > 0; } ge(other){ return this.compare_ratio(other) >= 0; } // 有理数 -> 循環小数 [[...],[...]] static repeat_decimal(rat) { let m = rat.numerator; let n = rat.denominator; let xs = [], ys = []; while (true) { let p = m / n, q = m % n; if (q == 0n) { ys.push(p); return [ys, [0n]]; } else { let x = xs.indexOf(q); if (x >= 0) { ys.push(p); return [ys.slice(0, x+1), ys.slice(x+1)] } } xs.push(q); ys.push(p); m = q * 10n; } } // 循環小数 -> 有理数 static from_repeat_decimal(xs, ys) { // 有限小数の部分を分数に直す let p0 = xs.reduce((a, x) => a * 10n + x, 0n); let q0 = 10n ** BigInt(xs.length - 1); // 循環節を分数に直す let p1 = ys.reduce((a, x) => a * 10n + x, 0n); let q1 = (10n ** BigInt(ys.length)) - 1n; // 有限小数 + 循環節 return new Ratio(q1 * p0 + p1, q0 * q1); } } export default Ratio;
// // test_ratio.js : Ratio の簡単なテスト // // Copyright (c) 2025 Makoto Hiroi // // Released under the MIT license // https://opensource.org/license/mit/ // import Ratio from './ratio.js'; var a = new Ratio(1n, 2n); var b = new Ratio(1n, 3n); console.log('%s', a); // 1/2 console.log('%s', b); // 1/3 console.log('%s', a.add(b)); // 5/6 console.log('%s', a.sub(b)); // 1/6 console.log('%s', b.sub(a)); // -1/6 console.log('%s', a.mul(b)); // 1/6 console.log('%s', a.div(b)); // 3/2 console.log('%s', b.div(a)); // 2/3 console.log('%s', a.inv()); // 2 console.log(a.equals(b)); // false console.log(a.equals(new Ratio(1n, 2n))); // true console.log(a == a); // true console.log(a == new Ratio(1n, 2n)); // false console.log(a.eq(b)); // false console.log(a.eq(a)); // true console.log(a.lt(b)); // false console.log(a.gt(b)); // true console.log(a.le(a)); // true console.log(a.ge(a)); // true console.log(b.le(a)); // true console.log(b.ge(a)); // false console.log(Ratio.repeat_decimal(new Ratio(1n, 7n))); // [ 0n ], [ 1n, 4n, 2n, 8n, 5n, 7n ] console.log(Ratio.repeat_decimal(new Ratio(1n, 8n))); // [ 0n, 1n, 2n, 5n ], [ 0n ] console.log(Ratio.repeat_decimal(new Ratio(1n, 13n))); // [ 0n ], [ 0n, 7n, 6n, 9n, 2n, 3n ] console.log('%s', Ratio.from_repeat_decimal([ 0n ], [ 1n, 4n, 2n, 8n, 5n, 7n ])); // 1/7 console.log('%s', Ratio.from_repeat_decimal([ 0n, 1n, 2n, 5n ], [ 0n ])); // 1/8 console.log('%s', Ratio.from_repeat_decimal([ 0n ], [ 0n, 7n, 6n, 9n, 2n, 3n ])); // 1/13 var c = new Ratio(355n, 113n); console.log('%s', Ratio.from_repeat_decimal(...Ratio.repeat_decimal(c))); // 355/113
$ node test_ratio.js 1/2 1/3 5/6 1/6 -1/6 1/6 3/2 2/3 2 false true true false false true false true true true true false [ [ 0n ], [ 1n, 4n, 2n, 8n, 5n, 7n ] ] [ [ 0n, 1n, 2n, 5n ], [ 0n ] ] [ [ 0n ], [ 0n, 7n, 6n, 9n, 2n, 3n ] ] 1/7 1/8 1/13 355/113
Node.js の REPL でモジュール (module, ES2015) を読み込むときは、ダイナミックインポート (dynamic import, ES2020) を使います。
> const { default: Ratio } = await import("./ratio.js"); undefined > var a = new Ratio(1n, 2n); undefined > var a = new Ratio(1n, 20n); undefined > var b = new Ratio(1n, 30n); undefined > console.log('%s', a.add(b)) 1/12 undefined > console.log('%s', a.sub(b)) 1/60 undefined > console.log('%s', b.sub(a)) -1/60 undefined > console.log('%s', a.mul(b)) 1/600 undefined > console.log('%s', a.div(b)) 3/2 undefined > console.log('%s', b.div(a)) 2/3 undefined > console.log('%s', a.inv()) 20 undefined > Ratio.repeat_decimal(new Ratio(355n, 113n)) [ [ 3n ], [ 1n, 4n, 1n, 5n, 9n, 2n, 9n, 2n, 0n, 3n, 5n, 3n, 9n, 8n, 2n, 3n, 0n, 0n, 8n, 8n, 4n, 9n, 5n, 5n, 7n, 5n, 2n, 2n, 1n, 2n, 3n, 8n, 9n, 3n, 8n, 0n, 5n, 3n, 0n, 9n, 7n, 3n, 4n, 5n, 1n, 3n, 2n, 7n, 4n, 3n, 3n, 6n, 2n, 8n, 3n, 1n, 8n, 5n, 8n, 4n, 0n, 7n, 0n, 7n, 9n, 6n, 4n, 6n, 0n, 1n, 7n, 6n, 9n, 9n, 1n, 1n, 5n, 0n, 4n, 4n, 2n, 4n, 7n, 7n, 8n, 7n, 6n, 1n, 0n, 6n, 1n, 9n, 4n, 6n, 9n, 0n, 2n, 6n, 5n, 4n, ... 12 more items ] ]