fab2s / math
基于10的基高精度数学助手
Requires
- php: ^8.1
- ext-bcmath: *
- fab2s/context-exception: ^2.0|^3.0
Requires (Dev)
- laravel/pint: ^1.11
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
Suggests
- ext-gmp: For faster Math::baseConvert up to base62
README
一个基于 bcmath 的流畅的 助手,用于以相当严格的方法处理10进制中的高精度计算(对精度有要求吗?)。它不会试图变得聪明,如果没有 bcmath
,就直接失败,但它会自动检测 GMP 以实现更快的基转换。
Bcmath
支持任何大小和精度的数字,精度最高可达 2,147,483,647(或 0x7FFFFFFF)位十进制,如果内存足够,则表示为字符串。
安装
可以使用 composer 安装 Math
composer require "fab2s/math"
Math
也包含在 OpinHelper 中,该包包含几个“瑞士军刀”级别以下的小助手,涵盖了PHP编程中一些最令人烦恼的方面,例如UTF8字符串操作、高精度数学或正确锁定文件
先决条件
Math
需要 bcmath,GMP 会自动检测并在可用时用于更快的基础转换(最多 62)。
实际使用
由于 Math
的目的是用于精度要求高的场合,因此它在输入数字方面非常严格:在通过 trim()
后,如果输入数字不匹配 ^[+-]?([0-9]+(\.[0-9]+)?|\.[0-9]+)$
,它将抛出异常。
实际上这意味着“-.0051”和“00028.34”是可接受的,但“1E12”、“3,14”或“1.1.1”将抛出异常。这样做的原因是,在 bcmath
的世界里,“1E12”、“1.1.1”和“abc”都是“0”,如果你不做任何事情,这可能会导致一些灾难。
一个 Math
实例仅初始化为有效的10进制数字。从那里你可以进行数学计算,并将实例强制转换为字符串以在任何阶段获取当前结果。
// instance way $number = new Math('42'); // fluent grammar $result = (string) $number->add('1')->sub(2)->div(1)->add(1)->mul(-1); // '-42' // factory way: number $result = (string) Math::number('42')->add('1')->sub(2)->div('1')->add(1)->mul(-1); // '-42' // factory way: fromBase $result = (string) Math::fromBase('LZ', 62); // '1337' $result = (string) Math::fromBase('LZ', 62)->sub(1295); // '42' // combos $number = Math::number('42') ->add(Math::fromBase('LZ', 62), '-42') ->sub('1337', '42') ->mul(3, 4, 1) ->div(4, 3) ->sub('.1') ->abs() ->round(0) ->floor() ->ceil() ->min('512', '256') ->max('8', '16', '32'); // formatting does not mutate internal number $result = (string) $number->format(2); // '42.00' $result = (string) $number; // '42'; // and you can continue calculating after string cast $result = (string) $number->add('1295')->toBase(62); // 'LZ' // toBase does not mutate base 10 internal representation $result = (string) $number; // '1337';
任何此类计算的字符串形式都是归一化的(例如,'-0'、'+.0' 或 '0.00' 转换为 '0'),这意味着您可以准确地比较 Math
实例的结果
$result = (string) Math::number('0000042.000000'); // '42' // raw form $result = Math::number('0000042.000000')->getNumber(); // '0000042.000000' // with some tolerance $result = Math::number(' 42.0000 ')->getNumber(); // '42.0000' // at all time if ((string) $number1 === (string) $number2) { // both instance numbers are equals } // same as (internally using bccomp) if ($number1->eq($number2)) { // both instance numbers are equals }
您可以在计算时直接重用部分 $calculus 作为实例
$number = new Math('42'); // same as $number = Math::number('42'); // in constructor $result = (string) (new Math($number))->div('2'); // '21' // same as $result = (string) Math::number($number)->div('2'); // '21' // in calc method $result = (string) Math::number('42')->add($number)->sub('42')->div('2'); // '21'
这样做实际上比将现有的实例强制转换为字符串要快,因为它不会触发归一化(内部数字状态仅在导出结果时归一化)也不进行数字验证,因为内部 $number 在所有时候都是有效的。
参数应该是字符串或 Math
,但可以使用整数,直到 INT_(32|64)
。
您不应该 使用 floats
,因为将它们转换为 string
可能会导致本地依赖的格式,例如使用逗号而不是点作为小数点或转换为指数表示法,这是 bcmath 不支持的。浮点数在一般处理方式和 PHP 特殊处理方式是 bcmath
存在的原因,因此即使您信任您的区域设置,使用浮点数也会在一定程度上违背使用此类库的目的。
内部精度
精度处理不依赖于 bcscale,因为它在现实生活中并不可靠。由于它是一个全局设置,它可能会影响或被远离/无关的代码影响(在 fpm 中它实际上可以传播到所有 PHP 进程)。
Math
在实例和全局(限于当前PHP进程)精度下处理精度。全局精度存储在静态变量中。当设置后,每个新的实例将以此全局精度作为自己的精度(实例化后仍可设置实例精度)。当未设置全局精度时,初始实例精度默认为 Math::PRECISION
(目前为9,或小数点后9位)
// set global precision Math::setGlobalPrecision(18); $number = (new Math('100'))->div('3'); // uses precision 18 $number->setPrecision(14); // will use precision 14 for any further calculations
Laravel
对于使用 Laravel 的用户,Math
提供了 Laravel 扩展器:MathCaster,您可以使用它直接转换模型属性。
use fab2s\Math\Laravel\MathCast; class MyModel extends Model { protected $casts = [ 'not_nullable' => MathCast::class, 'nullable' => MathCast::class . ':nullable', ]; } $model = new MyModel; $model->not_nullable = 41; $model->not_nullable->add(1)->eq(42); // true $model->not_nullable = null; // throw a NotNullableException $model->nullabe = null; // is ok
需求
Math
已在 php 8.1 和 8.2 上进行了测试。此外,MathCast 已在 Laravel 10 和 11 上进行了测试。
贡献
欢迎贡献,请随时打开问题并提交拉取请求。
许可证
Math
是开源软件,使用 MIT 许可证 许可。