创意电子

标题: 学习PHP中的任意精度扩展函数 [打印本页]

作者: 硬核项目经理    时间: 2021-8-25 08:38
标题: 学习PHP中的任意精度扩展函数
今天来学习的是关于数学方面的第一个扩展。对于数学操纵来说,无非就是那些各种各样的数学运算,当然,整个程序软件的开发过程中,数学运算也是最底子最根本的东西之一。不管你是学得什么专业,到最后基本上都会要学习数据布局与算法,而算法实在就是研究的怎样利用数学来优化各种排序和查找本领。PHP 在底层已经帮我们准备好了很多的数学计算函数,就让我们逐一来学习吧。
什么是精度问题

关于精度问题,大概很多做过金融方面的小伙伴都不会陌生。特殊是前端的同砚,假如你在 js 中实行 1.1+2.2 ,获得的结果往往不会如你所愿。这就要说到浮点数的存储问题了。我们都知道,在程序世界中,任何数据实在在底层都是以二进制的形式存在的。而浮点数,则由于小数点的存在,在存储时更为复杂,所以就会经常出现这类精度丢失的问题。
但是很多人会很奇怪,在 PHP 中直接实行 1.1+2.2 的结果是正确的呀,好像并不存在这种精度丢失的问题。呵呵,那只能说您 too young to simple 了。精度丢失的问题并不是哪个语言的问题,基本上所有语言都会存在这样的问题,只是体现的形式不一样。
bc 精度运算

我们先来看一下在 PHP 情况中的精度丢失要怎么显现出来。
$a = 0.58;echo $a * 100, PHP_EOL; // 58echo intval($a * 100), PHP_EOL; // 57echo (int) ($a * 100), PHP_EOL; // 57echo intval(bcmul($a, 100)), PHP_EOL; // 58我们定义了一个变量 $a ,它的内容是 0.58 。这时我们给他直接乘 100 ,结果貌似没什么问题。但是假如我们将它强转为 int 范例的话,就出现问题了,明明是 58 ,为什么变成了 57 ?
实在,在浮点运算后,得到的结果并不是 58 ,而是 57.99999999999999 这样的数,假如我们直接 echo 的话,会经过字符串强转,这个会直接输出 58 ,但假如是经过 int 强转的话,不管是 inval() 还是 (int) ,都会按照 int 强转的舍弃小数的规则举行转换。于是,结果就变成了 57 了。
通过直接的 echo 经常会让我们感觉到 PHP 中貌似不会出现精度丢失的问题,但实在这个问题还真是存在的。在很多情况下,好比存入数据库,或者转换成 json 格式就会发现问题。假如想要准确地计算,就可以使用 bc 扩展相干的函数,也就是我们最后演示的那个 bcmul() 函数。它的作用就是第一个参数乘以第二个参数,获得的结果也是高精度的,也就是精度准确的结果。
接下来我们通过 json 格式的转换来看看加减乘除各类情况下的精度问题。
echo json_encode([    'a1' => $a, // "a1":0.58    'a2' => $a * 100, // "a2":57.99999999999999    'a3' => intval($a * 100), // "a3":57    'a4' => floatval($a * 100), // "a4":57.99999999999999    'a5' => floatval($a), // "a5":0.58    'a6' => intval(bcmul($a, 100)), // "a6":58    'a7' => 1.1 + 2.2, // "a7":3.3000000000000003    'a8' => floatval(bcadd(1.1, 2.2, 10)), // "a8":3.3    'a9' => 2 - 1.1, // "a9":0.8999999999999999    'a10' => floatval(bcsub(2, 1.1, 10)), // "a10":0.9    'a11' => floatval($a * 100 / 10), // "a11":5.799999999999999    'a12' => floatval(bcdiv($a * 100, 10, 10)), // "a12":5.8    'a13' => 10 % 2.1, // "a13":0    'a14' => bcmod(10, 2.1), // "a14":"1"    'a15' => pow(1.1, 2), // "a15":1.2100000000000002    'a16' => bcpow(1.1, 2, 30), // "a16":"1.210000000000000000000000000000"    'a17' => sqrt(1.1), // "a17":1.0488088481701516    'a18' => bcsqrt(1.1, 30), // "a18":"1.048808848170151546991453513679"]), PHP_EOL;通过这段代码大家应该就能清楚地看到 PHP 中的精度丢失问题是否存在了。json_encode() 在转换数据的时候会根据字段的范例举行转换,所以精度问题会比较明显,这也是很多同砚在后端计算的时候明明没有问题,但通过 json 输出到前端就会发现数据发生了精度问题的缘故原由。
a1~a6 就是我们第一段测试代码的内容,可以很明显地看到普通地使用 $a * 100 的结果真的是 57.99999999999999 了吧。
a7、a8 是加法的演示,怎么样,在 PHP 中,1.1+2.2 的结果实在也和 JS 中是一样的吧,通过 bcadd() 就可以处置处罚加法的精度问题。同理,a9、a10 是减法的问题,通过 bcsub() 就可以获得减法的高精度计算结果。bcdiv() 则是用于处置处罚除法。注意,这几个函数都有第三个参数,它表示的是保留小数点的位数,我们都给了保留 10 位小数点,目的是希望假如出现丢失精度的问题可以和原计算比对。
bcmod() 的余数计算,对应的也就是 % 计算符号的作用。正常情况下,10 % 2 的结果为 0 是正常的,但这里我们计算的是 10 % 2.1 结果也是 0 ,而在使用 bcmod() 之后,结果为 1 ,这才是正确的结果。bcpow() 是乘方的计算,对应的是普通函数中的 pow() 函数,同样在这里我们在普通函数的计算中 1.1 的 2 次方出现了精度问题,使用 bcpow() 我们表现 30 位的小数也没有找到精度非常。这里必要注意的是,bcpow() 假如指定了小数位数,是会表现出来的,即使计算结果是没有小数的,也会以 0 全部表现出来。而上面其它的函数则不会这样,只会在确实有小数的情况下才表现出来。
最后则是 bcsqrt() 函数,也就是二次方根,这个没有找到有溢出的数可以供我们测试,假如有使用过并发现过溢出的小伙伴可以留言哦。
比较函数

上面说完了各种精度计算的函数,接下来我们看一下数字比较的问题。
echo bccomp(1, 2), PHP_EOL;   // -1echo bccomp(1.00001, 1, 3), PHP_EOL; // 0echo bccomp(1.00001, 1, 5), PHP_EOL; // 1bccomp() 函数就是用来根据小数点位数举行精度比较的函数。它的返回结果是假如参数1小于参数2返回 -1 ,大于返回 1,等于则返回 0 。第三个参数用户确定比较到哪一位。在这个例子中,我们可以看到,假如只比较到第三位小数的话,1.00001 和 1 的结果是相等的。而假如比较到第五位小数的话,它们的差异就体现出来了。
设置小数点及 bcpowmod 函数

最后我们再看两个函数。
bcscale(30);echo bcmod(bcpow(5, 2), 2), PHP_EOL; // 1.000000000000000000000000000000echo bcpowmod(5, 2, 2), PHP_EOL; // 1.000000000000000000000000000000bcscale() 是在全局设置小数点的位数。设置这个函数后,上面介绍过的所有函数假如不写第三个小数点位数函数的话,都会以 bcscale() 设置的为准。
bcpowmod() 函数的作用就和第二行的测试代码一样,就是先举行一次 bcpow() 再举行一次 bcmod() 。它的使用场景不多,不外写法很方便。
总结

今天的内容除了 bc 相干的计算函数之外,也讲到了精度问题这个各种语言都存在的问题。实在说实话,我们在一样寻常开发中,对于金额这类带小数点的数据,最好都是以分为单位举行存储。也就是说,在背景,保存和计算的数据都是整型的数据,在前端展示的时候,直接除 100 再保留两位小数就可以了。这样就可以极大地保证数据的精度不会丢失。
另外,关于 PHP 中精度问题相干的参考大家可以看看下方第二个链接中鸟哥博客上的说明。我们的例子 0.58 * 100 也是摘自他的博客中的示例。
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202012/source/7.学习PHP中的任意精度扩展函数.php
参考文档:
https://www.php.net/manual/zh/book.bc.php
https://www.laruence.com/2013/03/26/2884.html
作者: 飞翔的狮子不吃人    时间: 2021-8-25 22:41
转发了




欢迎光临 创意电子 (https://wxcydz.cc/) Powered by Discuz! X3.4