PHP 浮点数计算精度问题

近日计算价格时,0.91 + 0.1 = 0.91999999;
查看了各种论坛后,发现是浮点数计算精度问题造成的

浮点数运算精度丢失的产生原因

​ 在计算机中,只有二进制的数据才能被识别和处理。所以无论是哪种编程语言,在什么编译环境下工作,都要先把源程序(编译)转换成二进制的机器码后才能被计算机识别。二进制的方式可以准确表示一个整数,但不能准确表示一个浮点数。和十进制无法精确表示分数的1/3同样,二进制也无法精确表示十进制的小数。我们可以看下面的例子:

// 十进制数 134 表示为二进制
echo decbin(134); //输出:10000110
// 十进制数 184656 表示为二进制
echo decbin(184656); //输出:101101000101010000

// 但是对于浮点数来说,二进制并不能完整地表示一个浮点数。
// 例如,我们将浮点数 2.4 表示为二进制,此时不能使用 decbin(), bindec()等类似的php系统函数。这里我是用在线转换工具(https://hexconvert.vvcha.cn/),来实现对浮点数二进制表示

//浮点数 2.4 表示为二进制
2.4 = 10.011001100110011001100110011001100110011001100110011;

0.8 = 0.1100110011001100110011001100110011001100110011001101;

// 2.4 的二进制表示与 0.8 的二进制表示之和
	10.011001100110011001100110011001100110011001100110011
+	 0.1100110011001100110011001100110011001100110011001101;
--------------------------------------------------------------------------------
	11.0011001100110011001100110011001100110011001100110011 
// 将计算结果 11.0011001100110011001100110011001100110011001100110011 
//转为十进制表示
11.0011001100110011001100110011001100110011001100110011 = 3.19999999999999996

// 那么实际上来说,通过计算机计算的出来的结果是
2.4 + 0.8 = 3.19999999999999996
// 而不是我们常识上所认为
2.4 + 0.8 = 3.2

从上面的例子可以看出,小数表示为二进制,通过二进制运算后,在转换为十进制,会造成精度丢失的问题。

​ 我们再来看一个例子。

var_dump(intval(0.58 * 100)); //输出:57
var_dump(0.58 * 100); //输出:58
var_dump((0.1 + 0.7) == 0.8); //输出:false

2. PHP 中解决浮点数计算精度丢失的方案

方案一:将小数转为整数去计算,最后在对运行结果进行处理。

function precison_restore($num1, $num2, $type) {
    $ten = pow(10, maxLenOfDecimal($num1, $num2));

    switch ($type) {
        case '+':
            $res = ($num1*$ten + $num2*$ten) / $ten;
            break;
        case '-':
            $res = ($num1*$ten - $num2*$ten) / $ten;
            break;
        case '*':
            $res = ($num1*$ten * $num2*$ten) / pow($ten,2);
            break;
        case '/':
            $res = ($num1*$ten) / ($num2*$ten);
            break;
        default:
            
    }
    return $res;
}

function maxLenOfDecimal($num1 , $num2) {
    return max(decimalLen($num1), decimalLen($num2));
}

function decimalLen($num) {
    $len = 0;
    if ($decimal = ltrim(strrchr($num, '.'), '.')) {
        $len = strlen($decimal);
    }
    return $len;
}

$time_s = microtime(true);
var_dump(floor(precison_restore(2.1, 0.7, '-')*10)); // 输出:14
$time_e = microtime(true);
echo $time_e - $time_s; // 输出:7.1048736572266E-5

方案二:使用 php 官方提供的 BC Math 高精确度的数学扩展。(个人认为是最佳实践)

function simple($num1, $num2, $type) {
    bcscale(maxLenOfDecimal($num1, $num2));
    switch ($type) {
        case '+':
            $res = bcadd($num1, $num2);
            break;
        case '-':
            $res = bcsub($num1, $num2);
            break;
        case '*':
            $res = bcmul($num1, $num2);
            break;
        case '/':
            $res = bcdiv($num1, $num2);
            break;
        default:
            
    }
    return $res;
}

function maxLenOfDecimal($num1 , $num2) {
    return max(decimalLen($num1), decimalLen($num2));
}

function decimalLen($num) {
    $len = 0;
    if ($decimal = ltrim(strrchr($num, '.'), '.')) {
        $len = strlen($decimal);
    }
    return $len;
}

$time_s = microtime(true);
var_dump(floor(simple(2.1, 0.7, '-')*10)); // 输出:14
$time_e = microtime(true); 
echo $time_e - $time_s;// 输出:1.4066696166992E-5

​ 对比来看
​ 使用 bcmath 扩展的效率要比 将浮点数转换为整数进行计算,在对结果进行处理的方式要高效。
————————————————
版权声明:本文为CSDN博主「北林无迹」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_40103297/article/details/118855672

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
离散化计算面积是通过将一个连续的曲线分成若干个小区间,使用数值计算的方法来近似计算曲线下面的面积。在 PHP 中,可以使用以下代码实现: ```php // 定义离散化区间数 $N = 100; // 定义曲线函数 y=f(x) function f($x) { return 2 * pow($x, 2) - 3 * $x + 1; } // 定义计算面积的函数 function area($a, $b, $N) { $dx = ($b - $a) / $N; $sum = 0; for ($i = 0; $i < $N; $i++) { $x1 = $a + $i * $dx; $x2 = $a + ($i + 1) * $dx; $y1 = f($x1); $y2 = f($x2); $sum += min($y1, $y2) * $dx; } return $sum; } // 调用计算面积的函数 $A = area(0, 2, $N); echo "曲线下面的面积是:$A"; ``` 在这段代码中,我们首先定义了离散化区间数 `$N`,然后定义了曲线函数 `f($x)`,这里使用了一个二次函数作为例子。接着,我们定义了计算面积的函数 `area($a, $b, $N)`,其中 `$a` 和 `$b` 分别是积分区间的起点和终点,`$N` 是离散化区间数。在计算面积的过程中,我们将积分区间分成了 `$N` 个小区间,然后计算每个小区间内的面积,最后将所有小区间的面积相加,得到曲线下面的面积。最后,我们调用计算面积的函数,并输出结果。 需要注意的是,离散化计算面积是一种近似计算方法,其精度取决于离散化区间数 `$N` 的大小。当 `$N` 越大时,计算结果越精确,但计算时间也会越长。因此,在使用离散化计算面积时,需要根据实际情况选择合适的离散化区间数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值