mehr-it / php-decimals
PHP 中十进制数运算的简单易用库(使用 BCMath)
Requires
- php: >=7.2
- ext-bcmath: *
Requires (Dev)
- phpunit/phpunit: ^6.5|^7.4|^8.5
README
此库是 PHP 的 BCMath 扩展的一个小型包装。BCMath 实现任意精度数学,其使用并不舒适。
此库旨在通过内部使用 BCMath 实现常见的数学运算,提供一个易于使用的接口。
安装
使用 composer 安装此包
composer require mehr-it/php-decimals
介绍
使用 PHP 内置数字类型(浮点数和整数)进行数学运算有两个缺点:
- 范围有限
- 浮点数精度不准确
在处理超出内置数字类型范围或浮点数精度可能成为问题的情况(例如货币计算)时,需要替代解决方案。
与 BCMath 扩展一样,此库使用字符串来表示数字。与 PHP 的浮点数到字符串转换(使用区域设置依赖的十进制分隔符)不同,十进制分隔符始终为 "."
。
在处理任意精度数学时,你需要关注的是所需的精度。你必须告诉结果需要多少精度。尽可能的情况下,此库会为你完成这件事。但对于某些操作,你可能需要手动设置所需的精度以获得更高的精度。
用法
可以通过 Decimals
类的静态方法访问任何操作
Decimals::add('5.78', '7.1');
基本数学运算
// add: 5.78 + 7.1
Decimals::add('5.78', '7.1');
// substract: 5.32 - 1.08
Decimals::sub('5.32', '1.08');
// multiply: 1.1 * 2.87
Decimals::mul('1.1', '2.87');
// divide: 5 / 2.5
Decimals::div('5', '2.5');
// modulus: 5 % 2
Decimals::mod('5', '2');
// pow: 5 ** 2
Decimals::pow('5', '2');
对于 add()
和 sub()
,结果精度自动设置为操作数中十进制数最多的那个。这对于任何操作都足够了。
对于 mul()
、div()
和 mod()
,结果精度自动设置为操作数中十进制数最多的那个的两倍,但至少为 16 位。如果您需要更高的精度,可以将其指定为第三个参数。以下示例输出 32 位十进制数
Decimals::div('1', '3', 32);
sum()
函数将所有给定操作数相加
Decimals::sum('1', '2.1', '-0.3');
转换和解析
有时你需要将浮点数转换为字符串。如上所述,将浮点数转换为字符串使用的是区域设置依赖的十进制分隔符(例如,“en”中的“.”和“de”中的“,”)。当无法控制 PHP 解释器使用的区域设置时(尤其是编写库时),这可能会引起很多问题。
将本地类型转换为数字字符串
幸运的是,此库提供了一个函数,将这些本地字符串转换为 BCMath 兼容的数字字符串(使用“.”作为十进制分隔符)。你可以简单地将你的数字传递给 parse()
函数
$float = 1.5;
$sNumber = Decimals::parse($float);
始终使用 parse()
函数将本地数字类型转换为字符串,以编写与平台无关的代码!
解析具有任何十进制分隔符的字符串
parse()
函数在处理具有(可能不同)十进制分隔符的区域设置的数字时也非常有用。你可以安全地将这些字符串转换为通过将十进制分隔符作为第二个参数传递
$sNumber = Decimals::parse('1,67', ',');
// $sNumber: '1.67'
解析函数还验证输入,如果提供的是无效数字,则抛出 InvalidArguemntException
。如果有效数字字符串周围有空格,则优雅地忽略,并解析数字而不抛出异常。
将数字字符串转换为浮点数或整数
在需要将值转换回原生数据类型的情况下,您可以简单地将字符串强制转换为该类型或使用toNative()
函数,该函数将自动返回正确的类型(浮点数或整数)
$float = Decimals::toNative('1.56');
$int = Decimals::toNative('167');
规范化数字
通常,一个数字可以以多种形式表示
- 0可以表示为"0.0"、"0"、"0." ".0"、"-0"等等
- 5.8可以表示为"005.8"、"5.8000"、"+5.8"等等
norm()
函数会移除数字前后不必要的零,移除任何"+"符号,并将0始终表示为"0"
Decimals::norm('+045.89');
四舍五入和截断数字
这些函数相当直观。 round()
将数字四舍五入到指定的位数,而truncate()
则简单地移除小数部分
Decimals::round('5.4591', 0); // = '5'
Decimals::round('5.4591', 1); // = '5.5'
Decimals::round('5.4591', 2); // = '5.46'
Decimals::truncate('5.4591', 0); // = '5'
Decimals::truncate('5.4591', 1); // = '5.4'
Decimals::truncate('5.4591', 2); // = '5.45'
比较
要比较数字,可以使用comp()
函数。它类似于<=>
运算符,如果左操作数较小,则返回-1
;如果两个操作数相等,则返回0
;如果右操作数较大,则返回1
Decimals::comp('1.5', '6'); // = -1
Decimals::comp('1.5', '1.5'); // = 0
Decimals::comp('6', '1.5'); // = 1
为了使代码更易读,以下函数处理特定的比较情况
Decimals::isEqual('1.5', '6'); // = false
Decimals::isNotEqual('1.5', '6'); // = true
Decimals::isGreaterThan('1.5', '6'); // = false
Decimals::isGreaterThanOrEqual('1.5', '6'); // = false
Decimals::isLessThan('1.5', '6'); // = true
Decimals::isLessThanOrEqual('1.5', '6'); // = true
要找到给定值集中的最大值或最小值,存在max()
和min()
函数
Decimals::max('1', '2', '3'); // '3'
Decimals::min('1', '2', '3'); // '1'
数学函数
// returns the absolute value
Decimals::abs('-1.5'); // = '1.5'
Decimals::abs('1.5'); // = '1.5'
其他函数
// returns the number of decimals of the given number
Decimals::decimals('78.8'); // = 1
Decimals::decimals('78.889'); // = 3
Decimals::decimals('78.800'); // = 3
使用表达式
应用多个操作可能导致代码难以阅读
$result = Decimals::add(Decimals::mul($a, $b), $c);
在这里,Decimals::expr()
方法(或辅助函数expr()
)可以有所帮助。上述相同的操作可以写为以下形式
$result = Decimals::expr($a, '*', $b, '+', $c);
// or using helper
$result = expr($a, '*', $b, '+', $c);
这的缺点是会有轻微的性能开销,但代码非常易于阅读。
表达式始终从左到右进行评估。 如果给定的表达式会破坏数学或逻辑运算符的优先级,则会抛出异常。
TODO
- 为
bcpowmod()
和bcsqrt()
添加包装器