dakujem/two-floats

此包已被弃用,不再维护。没有建议的替代包。

轻松比较两个浮点数。

dev-main 2020-10-09 08:41 UTC

This package is auto-updated.

Last update: 2023-09-04 15:54:36 UTC


README

🚧 等待 目前完全无法使用。

我试了。我失败了。当我恢复平衡时,我会修复它。 🤷‍♂️

PHP from Packagist Build Status

浮点数比较 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::sameTwofloats::compare 方法使用。

算法在"浮点数指南"网站上进行了详细描述。

固定epsilon

当有意将精度限制为固定的小数位数时,第二个算法更为实用。

当显式传递epsilon时,由 TwoFloats::sameTwofloats::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或提交问题。