webgriffe / rational
用于表示有理数的数据类型
Requires
- php: ~8.1
- ext-gmp: *
- ext-intl: *
Requires (Dev)
- phpunit/phpunit: ^9.5
README
功能
此库实现了一个表示有理数的数值数据类型,即可以表示为两个整数之比的数量。这包括循环小数(例如 0.333333...,可以简单地表示为 1/3)以及那些不能由浮点类型精确表示的数(例如 0.1,在二进制表示中是一个循环小数,但可以简单地表示为分数 1/10)。
为了减少大值触发溢出问题的可能性,在分数中添加了一个整数“整数”部分。这意味着值以 a + b/c
形式的混合数存储,其中 a、b 和 c 都是整数。
此库与 https://github.com/markrogoyski/math-php/blob/master/src/Number/Rational.php 基本相同,主要区别在于此实现内部使用 GMP 扩展,以确保中间计算结果不会出现溢出问题。
在每个操作的末尾,经过所有简化和规范化步骤后,如果最终结果仍不适用于 PHP 的标准 integer
类型,则生成溢出异常。
设置
将库添加到您的项目中 composer.json
文件
{
"require": {
"webgriffe/rational": "^2.0"
}
}
使用 composer 安装库
$ php composer.phar install
Composer 会将库安装到您的 vendor 文件夹中。如果您在项目中尚未使用 Composer,可能需要明确包含其自动加载文件,以便 PHP 能够找到库类
require_once __DIR__ . '/vendor/autoload.php';
最低要求
- 安装了 GMP 扩展的 PHP 8.1
使用方法
由于浮点数本身是不精确的,因此故意决定不提供从浮点数初始化有理数的方法。同样,出于相同的原因,也没有提供将有理数转换为浮点数的方法。
从整数或有理数的各个部分创建有理数是可能的。
use Webgriffe\Rational; //Creates a zero value $r0 = Rational::zero(); //Creates a one value $r1 = Rational::one(); //Creates a whole number $r2 = Rational::fromWhole(-2); //Creates a variable that stores exactly ⅔ (two thirds), roughly 0.666666... $r3 = Rational::fromFraction(2, 3); //Creates a variable that stores exactly 4 + ⅑ (one ninth), roughly 7.111111... $r4 = Rational::fromWholeAndFraction(4, 1, 9); //Adds $r1 and $r2 so that $r5 equals -1 $r5 = $r1->add($r2); //Adds $r3 to $r5: -1 + ⅔ = -⅓ $r6 = $r5->add($r3); //Subtracts $r6 from $r2: -2 - (-⅓) = -2 + ⅓ = -1 - ⅔ $r7 = $r2->sub($r6); //Multiply $r7 by $r4: (-1 - ⅔) * (4 + ⅑) //= -4 - 1/9 - 8/3 - 2/27 //= -4 - 3/27 - 72/27 - 2/27 //= -4 - 77/27 //= -4 - 2 - 23/27 //= -6 - 23/27 $r8 = $r7->mul($r4); //Divide $r8 by $r3: (-6 - 23/27) / (2/3) //= (-6 - 23/27) * (3/2) //= -9 - 23/18 //= -9 - 1 - 5/18 //= -10 - 5/18 $r9 = $r8->div($r3); //Compute the reciprocal of $r9: 1/(-10 - 5/18) //= 1/((-180 - 5)/18) //= 1/(-185/18) //= 18/-185 //= -18/185 $r10 = $r9->recip(); //$r11 = $r10 + $r1: -18/185 + 1 //= -18/185 + 185/185 //= 167/185 $r11 = $r10->add($r1); //Prints 0,903 echo $r11->format(3, 0, ',', ''); //Forces 2 decimals and prints 0.90. Useful when dealing with prices echo $r11->format(2, 2, '.', ''); //$r12 = $r11 - $r10: 167/185 - (-18/185) //= 167/185 + 18/185 //= 185/185 //= 1 $r12 = $r11->sub($r10);
在应用程序中使用
扩展
可以将 Rational
类扩展到应用程序特定的上下文中无缝使用。
例如,如果想要使用 Rational
类来表示价格或金钱数量,可以定义一个扩展 Rational
的 Money
类
use Webgriffe\Rational; final class Money extends Rational { }
所有方法都以这样的方式类型提示,即所有结果都将被识别为具有每个操作的第一个操作数的数据类型。例如,如果一个 Money
对象被添加到另一个 Money
,结果也将是 Money
类型。
封装
当然,也可以在其他类中封装 Rational
的实例
use Webgriffe\Rational; class Money { public function __construct( private readonly Rational $value ) { } public function add(self $other): static { return new static($this->value->add($other->value)); } ... }
这的好处是可以精确控制允许和不允许的操作。例如,对于表示金钱数量的类,倒数操作可能没有意义。同样,将金钱数量与另一个金钱数量相乘或相除可能也不合理。
缺点是必须手动重新定义所有期望的算术运算,以便在包含的 Rational
值上工作。
内部工作
该库将有理数的所有组成部分存储为PHP整数。这样做是为了使这些值更容易存储到数据库和其他可能存在存储任意长度整数问题的媒体中。中间值通过PHP GMP库进行处理,以避免在最终结果计算前出现溢出问题。然而,如果每个操作的最终结果超出了PHP整数的范围,库将报告溢出错误。
创建后以及每次操作之后,每个值都会进行标准化。这样做是为了减少内部存储值的幅度,并使比较有理数和提取其他有用信息更容易。
在此库的上下文中,该库以a + b/c
的形式存储值,一个标准化值是满足以下条件的值:c > 0
,其中a * b >= 0
(即它们在符号上不矛盾,尽管一个或两个可以为零),其中GCD(|b|, c) == 1
(即分数b/c
已被化简)以及|a| < b
(即它是一个真分数)。
溢出
在每次操作的末尾,库将中间GMP值转换回整数。如果这些值太大或太小以至于无法适应整数,将抛出OverflowException。用户负责捕获异常并相应地处理。
许可证
Webgriffe/Rational遵循MIT许可证。