dakujem / two-floats
轻松比较两个浮点数。
Requires
- php: ^7.2 || ^8.0
- phpunit/phpunit: ^9.4
Requires (Dev)
- ext-bcmath: *
This package is auto-updated.
Last update: 2023-09-04 15:54:36 UTC
README
🚧 等待 目前完全无法使用。
我试了。我失败了。当我恢复平衡时,我会修复它。 🤷♂️
浮点数比较 PHP 辅助函数。
无框架依赖。无要求。
💿
composer require dakujem/two-floats
比较浮点数
让我们面对现实。比较浮点数在任何计算语言中都是一件痛苦的事情。(最好是最多的。)
如果你曾经遇到过以下类似的情况,你就知道。
var_dump( 0.1 + 0.2 === 0.3 ); // bool(false) ... wait ... WTF?!
让我们解决这个问题
var_dump( Dakujem\TwoFloats::same(0.1 + 0.2, 0.3) ); // bool(true) ... now we are talking
用法
use Dakujem\TwoFloats;
比较相等
$num1 = 0.17; $num2 = 1 - 0.83; // 0.17 if(TwoFloats::same($num1, $num2)) { // ... }
可选,可以使用自定义 精度 设置,如果需要的话
$num1 = 0.0095; $num2 = 0.0094; TwoFloats::same($num1, $num2); // false TwoFloats::same($num1, $num2, TwoFloats::epsilon(3)); // true, precision limited to 3 frac. digits
对于 < = >
比较
$c = TwoFloats::compare($num1, $num2); if( $c > 0 ) { // $num1 > $num2 } elseif( $c < 0 ) { // $num1 < $num2 } else { // $num1 == $num2 } // or with limited precision... $c = TwoFloats::compare($num1, $num2, TwoFloats::epsilon($decimals) );
可以使用方法 TwoFloats::epsilon
根据所需的小数位数计算比较方法使用的 epsilon (ε)。
两种比较算法
内部有两种不同的比较算法。
- 一个用于最大精度
- 另一个用于有意限制精度时的便利性
相对epsilon算法
此算法在使用浮点数运算时提供 最大可能的平台精度。
当未显式传递epsilon时,默认由 TwoFloats::same
和 Twofloats::compare
方法使用。
算法在"浮点数指南"网站上进行了详细描述。
固定epsilon
当有意将精度限制为固定的小数位数时,第二个算法更为实用。
当显式传递epsilon时,由 TwoFloats::same
和 Twofloats::compare
方法使用。
🚧 TODO
比较 | Epsilon (ε) | 相对ε算法 | 绝对ε算法 |
---|---|---|---|
0.1 == 0.1 |
1 |
true |
true |
发现
$f1 = 1.2432345436354879e-42; // will print unchanged $f2 = 9.2432345436354879e-42; // will loose precision in the last digit
精度保证为15位小数(小数位数或小数),根据维基百科为 ~15.9
。换句话说,数字 1.2345
有5位,同样 12345
,它们需要相同的精度来存储。
可以使用sprintf在PHP中打印数字n,双精度为“无损”
sprintf('%.17g', $number);
请注意,float/double本身不是无损的,这种打印只能确保打印浮点数时不会丢失精度,即字符串转换回浮点数应得到完全相同的数字。
使用字符串进行比较不可靠,只有将数字转换为字符串然后使用BC Math等工具进行处理。但是,科学记数法(例如 1e-3
)是BC Math无法处理的。
$f1 = 1_000_000.000_000; $f2 = 1e6; $f3 = 0.1+0.2; $f4 = 0.3; // thee two are equal in string var_dump(sprintf('%.17g', $f1)); var_dump(sprintf('%.17g', $f2)); // this won't be equal var_dump(sprintf('%.17g', $f3)); var_dump(sprintf('%.17g', $f4)); // this works, except for the scientific notation ... var_dump(bcadd('0.1', '0.2', PHP_FLOAT_DIG)); var_dump(bccomp(bcadd('0.1', '0.2', PHP_FLOAT_DIG), '0.3', PHP_FLOAT_DIG));
原因
我没有找到可靠、经过测试且易于使用的库可以集成到我的解决方案中。
我发现了一些使用字符串比较的算法,但对我来说,我认为BC Math可能更可靠。
我还发现了一些基于epsilon的数值算法,但它们在处理边缘情况时表现不佳。
我还发现了一些任意精度库和扩展,它们使用起来太重或者太慢。
我想实现数值算法作为BC Math包装器的后备方案,但使用BC Math证明是一个头疼的问题(不支持科学记数法1e-8
,并且需要正确转换为字符串而不丢失精度,这本身就是一个问题)。所以,我放弃了这个想法。
最后,我决定实现PHP中的“浮点数指南”算法。它看起来性能合理且精度高。然而,对于故意忽略特定固定(绝对)精度后的小数的场景,它不太方便,因为其epsilon是以相对方式工作的。因此,我最终实现了两个数值算法;一个使用相对epsilon以实现最大精度,另一个使用固定精度以提高实际可用性和便利性。
我希望这能成为PHP中比较浮点数的最终可靠、经过测试的开源插件解决方案。
欢迎任何形式的贡献。
其他选项
- BC Math
- 存在多个问题(缩放,仅支持字符串,难以避免精度丢失)
- PHP Decimal
- 只能作为PECL扩展安装
致谢
这个包中实现的一个算法发布在
贡献
欢迎提出想法或贡献。请发送PR或提交问题。