首页 > 解决方案 > 选择什么常数来比较 fmod 函数的结果

问题描述

我需要检查一个浮点数/双精度数是否是另一个浮点数/双精度数的倍数。整数很容易

$isMultiple = $x % $y == 0;

但在浮点数/双打中却没有。第一个问题是浮点数没有 % 运算符,所以我们必须使用 fmod 函数,第二个更大的问题是我们不能与零进行比较,但我们必须比较该数字小于某个常数,我不知道如何选择正确的常数,因为我总是可以选择它不起作用的数字。例如,如果我选择 0.00001,那么它仍然不适用于某些数字:

$C = 0.00001;
$isMultiple1 = fmod(3.0, 2.0) < $C; // = false, which is correct
$isMultiple2 = fmod(1.39, 0.0001) < $C; // = false, which is not correct

事实上,问题是 (0.000099999999999836) 的结果fmod(1.39, 0.0001)对于9.9999999999836E-5小常数来说太高了,但是如果我选择高常数,它对于其他一些数字就不起作用了。

如何$C正确选择或如何以不同的方式解决该问题,这将普遍适用于 PHP 中的任何数字?

标签: phpfloating-point

解决方案


此函数检查浮点值是否是浮点因子的整数倍。

function isMultiple($product, $factor){
  $eps = 1.E-12;
  $rest = abs(fmod($product, $factor));
  if($rest < $eps) return true;
  return abs($rest-$factor) < $eps;
}

var_dump( isMultiple(3.0,2.0) );  //false
var_dump( isMultiple(1.39, 0.0001) );  //true
var_dump( isMultiple(1.39, 0.02) ); //false

由于浮点值计算的准确性有限,该函数只能在一定的范围内工作。例如,因子必须是浮点精度和变量 $eps 的倍数。

结果错误的例子

var_dump( isMultiple(1.1e-11, 0.2e-11) );  //bool(true)
var_dump( isMultiple(1.39, 0.00010000000000001) ); //bool(true)

更新

以下函数不使用 fmod,因此可以更好地利用 float 的准确性。

function isMultiple($value, $factor){
  $quot = $value/$factor;
  return abs(round($quot)-$quot)/$quot < 1.e-14;
}

所有这些测试都给出了正确的结果

var_dump( isMultiple(3.0,2.0) );  //false
var_dump( isMultiple(1.39, 0.0001) );  //true
var_dump( isMultiple(1.39, 0.02) ); //false

var_dump( isMultiple(1.39, 0.0001000000000001) );  //bool(false)
var_dump( isMultiple(56185.047, 0.123) );  //bool(true)
var_dump( isMultiple(56185.04701, 0.123) );  //bool(false)

推荐阅读