ITADN
rawify/Fraction.js
rawify/Fraction.js · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

Fraction.js - JavaScript 中的 ℚ

NPM Package MIT license

你是否觉得浮点运算的局限性令人沮丧,尤其是当 π 或 √2 这样的有理数和无理数被存储在同一有限精度中时?这可能导致可以避免的不准确,例如:

1 / 98 * 98 // Results in 0.9999999999999999

对于需要更高精度或更倾向于处理分数的应用程序,请考虑将 Fraction.js 集成到您的项目中。

该库有效地解决了精度问题,如下所示:

Fraction(1).div(98).mul(98) // Returns 1

Fraction.js 对分子和分母均采用 BigInt 表示法,在确保最小性能开销的同时最大化精度。其设计针对精度进行了优化,使其成为其他数学工具(如 Polynomial.jsMath.js)的理想基础库。

将小数转换为分数

Fraction.js 的核心功能之一是其能够将小数无缝转换为分数。

let x = new Fraction(1.88);
let res = x.toFraction(true); // Returns "1 22/25" as a string

这在需要精确的分数表示,而非处理浮点算术的局限性时特别有用。如果你允许一定的误差容限呢?

let x = new Fraction(0.33333);
let res = x.simplify(0.001) // Error < 0.001
       .toFraction(); // Returns "1/3" as a string

精度

随着 JavaScript 中原生 BigInt 支持的普及,Fraction.js 等库利用它来处理更高精度的计算。这提升了大数数学运算的速度和准确性,为那些需要比浮点数更高精度的任务提供了更好的解决方案。

示例 / 动机

使用 Fraction.js 的一个简单示例可能如下所示:

var f = new Fraction("9.4'31'"); // 9.4313131313131...
f.mul([-4, 3]).mod("4.'8'"); // 4.88888888888888...

然后,结果可以显示为:

console.log(f.toFraction()); // -4154 / 1485

此外,您可以访问分数的内部属性,例如符号(s)、分子(n)和分母(d)。请记住,这些值以 BigInt 形式存储:

Number(f.s) * Number(f.n) / Number(f.d) = -1 * 4154 / 1485 = -2.797306...

如果你尝试使用浮点运算手动计算,你会得到类似以下内容:

(9.4313131 * (-4 / 3)) % 4.888888 = -2.797308133...

虽然结果相对接近,但不及 Fraction.js 提供的基于分数的方法准确,尤其是在处理循环小数或复杂运算时。这凸显了该库所带来的精度价值。

拉普拉斯概率

下面是一个使用 Fraction.js 计算概率的简单示例。让我们确定在公平骰子上掷出特定结果的概率:

  • P({3}):掷出 3 的概率。
  • P({1, 4}):掷出 1 或 4 的概率。
  • P({2, 4, 6}):掷出 2、4 或 6 的概率。

P({3}):

var p = new Fraction([3].length, 6).toString(); // "0.1(6)"

P({1, 4}):

var p = new Fraction([1, 4].length, 6).toString(); // "0.(3)"

P({2, 4, 6}):

var p = new Fraction([2, 4, 6].length, 6).toString(); // "0.5"

将度/分/秒转换为精确的有理数表示:

57+45/60+17/3600

var deg = 57; // 57°
var min = 45; // 45 Minutes
var sec = 17; // 17 Seconds

new Fraction(deg).add(min, 60).add(sec, 3600).toString() // -> 57.7547(2)

无理数的有理逼近

要用分子和分母来近似像 sqrt(5) - 2 这样的数,你可以将方程重写为:pow(n / d + 2, 2) = 5

然后,以下算法将生成该有理数及其二进制表示。

var x = "/", s = "";

var a = new Fraction(0),
    b = new Fraction(1);
for (var n = 0; n <= 10; n++) {

  var c = a.add(b).div(2);

  console.log(n + "\t" + a + "\t" + b + "\t" + c + "\t" + x);

  if (c.add(2).pow(2).valueOf() < 5) {
    a = c;
    x = "1";
  } else {
    b = c;
    x = "0";
  }
  s+= x;
}
console.log(s)

结果是

n   a[n]        b[n]        c[n]            x[n]
0   0/1         1/1         1/2             /
1   0/1         1/2         1/4             0
2   0/1         1/4         1/8             0
3   1/8         1/4         3/16            1
4   3/16        1/4         7/32            1
5   7/32        1/4         15/64           1
6   15/64       1/4         31/128          1
7   15/64       31/128      61/256          0
8   15/64       61/256      121/512         0
9   15/64       121/512     241/1024        0
10  241/1024    121/512     483/2048        1

因此,二分法经过 11 次迭代后的近似值为 483 / 2048,其二进制表示为 0.00111100011(参见 WolframAlpha)

我在我的 blog 上发布了另一个使用 fraction.js 近似 PI 的示例(虽然近似无理数仍不是最佳方案,但它完美地展示了 Fraction.js 的功能)。

获取数字的精确小数部分

var f = new Fraction("-6.(3416)");
console.log(f.mod(1).abs().toFraction()); // = 3416/9999

数学上正确的取模

对于负数同余的处理方式与计算机科学中大多数取模实现不同。即使是 Fraction.js 的 mod() 函数也遵循典型的处理方式。要解决在 Fraction.js 中获得数学上正确取模的问题,你可以采用以下方法:

var a = -1;
var b = 10.99;

console.log(new Fraction(a)
  .mod(b)); // Not correct, usual Modulo

console.log(new Fraction(a)
  .mod(b).add(b).mod(b)); // Correct! Mathematical Modulo

fmod() 精度问题已规避

事实证明,Fraction.js 的性能几乎优于任何 fmod() 实现,包括 JavaScript 本身、php.js、C++、Python、Java 甚至 Wolframalpha,这是因为诸如 0.05、0.1 等数字在二进制下是无限小数。

方程 fmod(4.55, 0.05) 的结果为 0.04999999999999957,而 wolframalpha 给出的结果是 1/20。正确答案应为 ,因为 0.05 能整除 4.55 且无余数。

解析器

任何函数(见下文)以及 Fraction 类的构造函数都会解析其输入并将其化简为最简分数。

您可以传入数组、对象、整数、双精度浮点数或字符串。

数组 / 对象

new Fraction(numerator, denominator);
new Fraction([numerator, denominator]);
new Fraction({n: numerator, d: denominator});

整数

new Fraction(123);

双打

new Fraction(55.4);

注意: 如果你直接传入 double 类型,Fraction.js 将基于 Farey Sequences 执行数值分析。如果你关注性能,请缓存 Fraction.js 对象并传入数组/对象。

该方法非常精确,但过大的精确数值,例如 1234567.9991829 会导致错误的近似值。如果你想保持数值原样,请将其转换为字符串,因为字符串解析器不会执行任何进一步的观察。如果你在近似方面遇到问题,在文件 examples/approx.js 中有一个不同的近似算法,它可能在某些更具体的用例中表现更好。

字符串

new Fraction("123.45");
new Fraction("123/45"); // A rational number represented as two decimals, separated by a slash
new Fraction("123:45"); // A rational number represented as two decimals, separated by a colon
new Fraction("4 123/45"); // A rational number represented as a whole number and a fraction
new Fraction("123.'456'"); // Note the quotes, see below!
new Fraction("123.(456)"); // Note the brackets, see below!
new Fraction("123.45'6'"); // Note the quotes, see below!
new Fraction("123.45(6)"); // Note the brackets, see below!

两个参数

new Fraction(3, 2); // 3/2 = 1.5

循环小数

Fraction.js 可以轻松处理循环小数。例如 1/30.3333...。只有一个循环数字。正如上面示例所示,你可以将像 1/3 这样的数字作为 "0.'3'" 或 "0.(3)" 传入,二者是同义词。没有测试来解析像 0.166666666 这样的数字为 1/6!如果你确实想处理这个数字,请自行使用下方的函数添加括号,例如:0.1(66666666)

假设你想计算 123.32 / 33.6(567)。WolframAlpha 指出你将得到一个 1776 位的循环节。Fraction.js 得出相同的结果。试一试:

var f = new Fraction("123.32");
console.log("Bam: " + f.div("33.6(567)"));

为了自动将类似 "0.123123123" 的数字转换为对 Fraction.js 更友好的形式,例如 "0.(123)",我在 10 分钟内编写了这个小型暴力算法。欢迎提出改进建议...

function formatDecimal(str) {

  var comma, pre, offset, pad, times, repeat;

  if (-1 === (comma = str.indexOf(".")))
    return str;

  pre = str.substr(0, comma + 1);
  str = str.substr(comma + 1);

  for (var i = 0; i < str.length; i++) {

    offset = str.substr(0, i);

    for (var j = 0; j < 5; j++) {

      pad = str.substr(i, j + 1);

      times = Math.ceil((str.length - offset.length) / pad.length);

      repeat = new Array(times + 1).join(pad); // Silly String.repeat hack

      if (0 === (offset + repeat).indexOf(str)) {
        return pre + offset + "(" + pad + ")";
      }
    }
  }
  return null;
}

var f, x = formatDecimal("13.0123123123"); // = 13.0(123)
if (x !== null) {
  f = new Fraction(x);
}

属性

Fraction 对象允许直接访问分子、分母和符号属性。确保只有符号属性持有符号信息,因此只需针对该属性进行符号比较。

var f = new Fraction('-1/2');
console.log(f.n); // Numerator: 1
console.log(f.d); // Denominator: 2
console.log(f.s); // Sign: -1

函数

Fraction abs()

返回不带任何符号信息的实际数值

Fraction neg()

返回符号翻转后的实际数值,以获取加法逆元

Fraction add(n)

返回实际数值与参数 n 的和

Fraction sub(n)

返回实际数值与参数 n 的差

Fraction mul(n)

返回实际数值与参数 n 的积

Fraction div(n)

返回实际数值与参数 n 的商

Fraction pow(exp)

返回实际数值的幂,指数为可能的有理数。如果结果变为非有理数,则函数返回 null

Fraction log(base)

返回实际数值以给定有理数为底的对数。如果结果变为非有理数,则函数返回 null

Fraction mod(n)

返回实际对象与 n 的模(除法的余数)(this % n)。如果您喜欢,它比 fmod() 精确得多。请注意,mod() 与大多数编程语言的取模运算符相同。如果您想要数学上正确的取模,请参阅 此处

Fraction mod()

返回实际对象的模(分子模分母)

Fraction gcd(n)

返回分数的最大公约数

Fraction lcm(n)

返回分数的最小公倍数

Fraction ceil([places=0-16])

使用 Math.ceil 返回有理数的上取整值

Fraction floor([places=0-16])

返回有理数的向下取整值,使用 Math.floor

Fraction round([places=0-16])

返回使用 Math.round 四舍五入后的有理数

Fraction roundTo(multiple)

将有理数四舍五入到另一个有理数的最近倍数。

Fraction inverse()

返回当前数的乘法逆元(n / d 变为 d / n),以获取倒数

Fraction simplify([eps=0.001])

在特定误差阈值下简化有理数。例如,0.333eps=0.001 下将变为 1/3

boolean equals(n)

检查两个有理数是否相等

boolean lt(n)

检查此有理数是否小于另一个有理数

boolean lte(n)

检查此有理数是否小于或等于另一个有理数

boolean gt(n)

检查此有理数是否大于另一个有理数

boolean gte(n)

检查此有理数是否大于或等于另一个有理数

int compare(n)

比较两个数。

result < 0: n is greater than actual number
result > 0: n is smaller than actual number
result = 0: n is equal to the actual number

boolean divisible(n)

检查两个数是否可整除(n 整除此数)

double valueOf()

返回分数的十进制表示

String toString([decimalPlaces=15])

生成给定对象的精确字符串表示。对于循环小数,循环周期内的数字将用括号括起来,例如 1/3 = "0.(3)"。对于其他数字,字符串将包含最多指定的 decimalPlaces 位有效数字,如果发生截断,则包括末尾的零。例如,1/2 将被表示为 "0.5",没有额外的末尾零。

注意: 由于同时提供了 valueOf()toString()toString() 仅在对象用于字符串上下文时隐式调用。例如,当使用加号运算符如 "123" + new Fraction 时,会先调用 valueOf(),因为 JavaScript 在拼接之前会尝试合并原始值,且字符串类型具有优先权。然而,alert(new Fraction)String(new Fraction) 将按预期工作。为确保特定行为,请显式调用 toString()valueOf()

String toLatex(showMixed=false)

生成实际对象的精确 LaTeX 表示。你可以在我的博客上查看 实时演示

可选的布尔参数指示是否显示带分数。显示 "1 1/3" 而不是 "4/3"

String toFraction(showMixed=false)

获取分数的字符串表示

可选的布尔参数指示是否显示带分数。显示 "1 1/3" 而不是 "4/3"

Array toContinued()

获取表示为连分数的分数的数组。第一个元素始终包含整数部分。

var f = new Fraction('88/33');
var c = f.toContinued(); // [2, 1, 2]

Fraction clone()

创建实际 Fraction 对象的副本

异常

如果发生严重错误(解析错误、除以零),Fraction.js 会抛出异常!请确保你正确处理它们。

安装

你可以通过 npm 安装 Fraction.js

npm install fraction.js

或者使用 yarn:

yarn add fraction.js

或者,下载或克隆该仓库:

git clone https://github.com/rawify/Fraction.js

用法

在你的项目中包含 fraction.min.js 文件:

<script src="path/to/fraction.min.js"></script>
<script>
  var x = new Fraction("13/4");
</script>

或者在 Node.js 项目中:

const Fraction = require('fraction.js');

import Fraction from 'fraction.js';

编码风格

与我所有的库一样,Fraction.js 的编写旨在通过 Google Closure Compiler 的高级模式压缩后最小化体积。代码风格经过优化以最大化可压缩性。如果你扩展该库,请保持此风格。

构建库

克隆 Git 仓库后,运行:

npm install
npm run build

运行测试

针对随附的测试套件测试源代码,操作如下

npm run test

版权与许可

版权所有 (c) 2025, Robert Eisele 依据 MIT 许可证授权。