今天来学习的是关于数学方面的第一个扩展。对于数学操作来说,无非就是那些各种各样的数学运算,当然,整个程序软件的开发过程中,数学运算也是最基础最根本的东西之一。不管你是学得什么专业,到最后基本上都会要学习数据结构与算法,而算法其实就是研究的如何利用数学来优化各种排序和查找能力。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中的任意精度扩展函数的更多相关文章

  1. PHP中操作任意精度大小的GMP扩展学习

    对于各类开发语言来说,整数都有一个最大的位数,如果超过位数就无法显示或者操作了.其实,这也是一种精度越界之后产生的精度丢失问题.在我们的 PHP 代码中,最大的整数非常大,我们可以通过 PHP_INT ...

  2. 学习PHP中统计扩展函数的使用

    做统计相关系统的朋友一定都会学习过什么正态分布.方差.标准差之类的概念,在 PHP 中,也有相应的扩展函数是专门为这些统计相关的功能所开发的.我们今天要学习的 stats 扩展函数库就是这类操作函数. ...

  3. 【Python学习】Python中的数据类型精度问题

    Python真的很神奇...神奇到没有直接的数据类型概念,并且精度可以是任意精度.想当初,第一次接触OI算法时,写得第一个算法就是高精度加法,捣鼓了半天.一切在Python看来,仅仅三行代码即可完成. ...

  4. LINUX任意精度计算器BC用法

    [用途说明] Bash内置了对整数四则运算的支持,但是并不支持浮点运算,而bc命令可以很方便的进行浮点运算,当然整数运算也不再话下.手册页上说bc是An arbitrary precision cal ...

  5. bcpow — 任意精度数字的乘方

    bcpow — 任意精度数字的乘方 说明 string bcpow ( string $left_operand , string $right_operand [, int $scale ] ) 左 ...

  6. 学习PHP中的高精度计时器HRTime扩展

    不知道大家还记得在学校的时候体育测试时老师带的秒表吗?当枪声想起时,我们开始跑步,这时秒表启动,当我们跑过终点后,老师会按下按扭记录我们的成绩,这就是一个典型的定时器的应用.今天我们要学习的内容其实就 ...

  7. 学习sql中的排列组合,在园子里搜着看于是。。。

    学习sql中的排列组合,在园子里搜着看,看到篇文章,于是自己(新手)用了最最原始的sql去写出来: --需求----B, C, F, M and S住在一座房子的不同楼层.--B 不住顶层.C 不住底 ...

  8. [转]学习Nop中Routes的使用

    本文转自:http://www.cnblogs.com/miku/archive/2012/09/27/2706276.html 1. 映射路由 大型MVC项目为了扩展性,可维护性不能像一般项目在Gl ...

  9. 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 ...

随机推荐

  1. STP进阶版MSTP

    一.MSTP简介 1.1.MSTP工作原理 mstp是一个公有生成树协议,在实际生产环境中得到了广泛的应用.传统的生成树只运行一个实例,且收敛速度慢,RSTP在传统的STP基础上通过改进达到了加速网络 ...

  2. 绕WAF常见思路整理(一)

    最*被*台的一些事情搞得心态有点崩,很久没写文了 *期想整理一下常见的各种操作中绕过WAF的思路与免杀的思路(这部分之前没整理完以后有机会再说),受限于个人水*因素所以一定是不完全的,而且在WAF日新 ...

  3. Pikachu-Unsafe Filedownload模块

    一.概述 文件下载功能在很多web系统上都会出现,一般我们当点击下载链接,便会向后台发送一个下载请求,一般这个请求会包含一个需要下载的文件名称,后台在收到请求后 会开始执行下载代码,将该文件名对应的文 ...

  4. Django ORM记录的增删改查结合web端

    模版语法分配变量 在views.py文件中定义一个视图函数show_data: def show_data(request): # 定义一个字典 并将它展示在前端HTML文件 user_dic = { ...

  5. IDE快捷键的使用

    ctrl+ait+l,整理代码 ctrl+atl+v,生成等号左边的类型和变量 shift+方向键,选择内容 ctrl+方向键,自己领悟.常常与shift同时使用 ctrl+alt+方向键,光标前进或 ...

  6. 教你使用ApiPost中的全局参数和目录参数

    前面的示例中,我们都是在单一接口中填入不同的请求header.query.body参数.但在实际项目中,对于一批接口,往往具有相同的请求参数.此时,我们可以利用全局参数或者目录参数实现. 例如:常见的 ...

  7. redis数据同步之redis-shake

    redis-shake简介 redis-shake是阿里开源的用于redis数据同步的工具,基本功能有: 恢复restore:将RDB文件恢复到目的redis数据库. 备份dump:将源redis的全 ...

  8. 【JavasScript】折腾一个基础到不能再基础的顺滑抽奖页面

    前言 事情是这样的,作为一个意志力极低的人,最近一直在找寻提高意志力的方法. 然后决定试一试所谓的"建立奖励机制",也就是说,完成一项意志力挑战后给自己一些奖励(具体操作方法不在这 ...

  9. mybatis gengeator一键生成

  10. windows下mysql5.7.17配置

    1.官网下载mysql5.7.17 64位 https://dev.mysql.com/downloads/mysql/ 2.安装完解压到E盘主目录下,改文件名为mysql 3.配置环境变量 我的电脑 ...