学习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; // 58
echo intval($a * 100), PHP_EOL; // 57
echo (int) ($a * 100), PHP_EOL; // 57
echo 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; // -1
echo bccomp(1.00001, 1, 3), PHP_EOL; // 0
echo bccomp(1.00001, 1, 5), PHP_EOL; // 1
bccomp() 函数就是用来根据小数点位数进行精度比较的函数。它的返回结果是如果参数1小于参数2返回 -1 ,大于返回 1,等于则返回 0 。第三个参数用户确定比较到哪一位。在这个例子中,我们可以看到,如果只比较到第三位小数的话,1.00001 和 1 的结果是相等的。而如果比较到第五位小数的话,它们的差异就体现出来了。
设置小数点及 bcpowmod 函数
最后我们再看两个函数。
bcscale(30);
echo bcmod(bcpow(5, 2), 2), PHP_EOL; // 1.000000000000000000000000000000
echo bcpowmod(5, 2, 2), PHP_EOL; // 1.000000000000000000000000000000
bcscale() 是在全局设置小数点的位数。设置这个函数后,上面介绍过的所有函数如果不写第三个小数点位数函数的话,都会以 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
关注公众号:【硬核项目经理】获取最新文章
添加微信/QQ好友:【xiaoyuezigonggong/149844827】免费得PHP、项目管理学习资料
知乎、公众号、抖音、头条搜索【硬核项目经理】
B站ID:482780532
学习PHP中的任意精度扩展函数的更多相关文章
- PHP中操作任意精度大小的GMP扩展学习
对于各类开发语言来说,整数都有一个最大的位数,如果超过位数就无法显示或者操作了.其实,这也是一种精度越界之后产生的精度丢失问题.在我们的 PHP 代码中,最大的整数非常大,我们可以通过 PHP_INT ...
- 学习PHP中统计扩展函数的使用
做统计相关系统的朋友一定都会学习过什么正态分布.方差.标准差之类的概念,在 PHP 中,也有相应的扩展函数是专门为这些统计相关的功能所开发的.我们今天要学习的 stats 扩展函数库就是这类操作函数. ...
- 【Python学习】Python中的数据类型精度问题
Python真的很神奇...神奇到没有直接的数据类型概念,并且精度可以是任意精度.想当初,第一次接触OI算法时,写得第一个算法就是高精度加法,捣鼓了半天.一切在Python看来,仅仅三行代码即可完成. ...
- LINUX任意精度计算器BC用法
[用途说明] Bash内置了对整数四则运算的支持,但是并不支持浮点运算,而bc命令可以很方便的进行浮点运算,当然整数运算也不再话下.手册页上说bc是An arbitrary precision cal ...
- bcpow — 任意精度数字的乘方
bcpow — 任意精度数字的乘方 说明 string bcpow ( string $left_operand , string $right_operand [, int $scale ] ) 左 ...
- 学习PHP中的高精度计时器HRTime扩展
不知道大家还记得在学校的时候体育测试时老师带的秒表吗?当枪声想起时,我们开始跑步,这时秒表启动,当我们跑过终点后,老师会按下按扭记录我们的成绩,这就是一个典型的定时器的应用.今天我们要学习的内容其实就 ...
- 学习sql中的排列组合,在园子里搜着看于是。。。
学习sql中的排列组合,在园子里搜着看,看到篇文章,于是自己(新手)用了最最原始的sql去写出来: --需求----B, C, F, M and S住在一座房子的不同楼层.--B 不住顶层.C 不住底 ...
- [转]学习Nop中Routes的使用
本文转自:http://www.cnblogs.com/miku/archive/2012/09/27/2706276.html 1. 映射路由 大型MVC项目为了扩展性,可维护性不能像一般项目在Gl ...
- SGU 275 To xor or not to xor 高斯消元求N个数中选择任意数XORmax
275. To xor or not to xor The sequence of non-negative integers A1, A2, ..., AN is given. You are ...
随机推荐
- Mina的JMX支持
以下是一个增加了JMX支持的Mina Echo Server. package org.apache.mina.echoServer; import java.lang.management.Mana ...
- Linux平台上转换文件编码
Linux系统的iconv指令是一个很好的文件编码转换工具,支持的编码范围广,使用方便,例如将一个utf-8编码的文件(名为tic)转换为gbk编码: iconv -f utf-8 -t gbk ti ...
- 我写一篇文章就是要批评CSDN! 因为蓝湖3.0的更新
对于开发者,经常会使用,学习到各种环境语言和工具 我们不只是在不断的在搬运知识,更多时候我们也是在 分享我们学到的"新知识", 当我们有幸觉得自己发现了新的知识与技术时,作为分享者 ...
- Docker的使用笔记
介绍Docker Docker为了解决依赖的兼容问题的,采用了两个手段: 将应用的Libs(函数库).Deps(依赖).配置与应用一起打包 将每个应用放到一个隔离容器去运行,避免互相干扰 Docker ...
- DG:11.2.0.4 RAC在线duplicate恢复DG
1.环境介绍 测试环境, 在一个双节点的RAC上使用duplicate搭建DG,使用在线的方式搭建 主机 IP 操作系统 实例 db_name db_unique_name db_version 配置 ...
- 如何在WPF中定义窗体模板
参考网址:https://www.cnblogs.com/chenxizhang/archive/2010/01/10/1643676.html可以在app.xaml中定义一个ControlTempl ...
- element ui loading加载开启与关闭
参考:https://blog.csdn.net/qq_41877107/article/details/87690555 Vue项目引入element-ui,之后,将以下代码写入 mounted() ...
- c# button Command
internal class DelegateCommand : ICommand { private readonly Action _execute; private readonly Func& ...
- call bind apply的区别
call() 和apply()的第一个参数相同,就是指定的对象.这个对象就是该函数的执行上下文. call()和apply()的区别就在于,两者之间的参数. call()在第一个参数之后的 后续所有参 ...
- python操作图片
时间:2018-11-30 记录:byzqy 标题:python实现图片操作 地址:https://blog.csdn.net/baidu_34045013/article/details/79187 ...