做电子商务的时候一般会涉及到金额的比较,按正常的思路来看用><=这些个符号就可以了。可是要是到程序上来搞这个的话就出大事了。现在看下这段代码:

$f = 0.07;
var_dump($f * 100 == 7);//输出false

输出结果会出乎大家意料,输出false,为什么会这样呢?其实这个和电脑中存储小数的原理有关。大家都知道计算机只能存储0和1,我们日常生活习惯使用的是10进制的数据,像0.07这个小数在计算机中存储时会有精度损失,以至于计算出来的结果会有偏差。

那么怎么解决这个问题?虽然计算机存储小数有偏差,但是偏差还是非常小,像上例中0.07 * 100如果显示出小数点后面20位的话,最终的值如下

$f = 0.07;
//输出7.00000000000000088818
echo number_format($f * 100, 20)
 
可以看到已经在小数点10多位之后了。在实际中我们通常也不需要精确到后面这么多位数字,在金额方面通常精确到后面3位就好了。如果精确到小数点后面三位的话,0.07*100和7就会相等了。在php中提供了一个bccomp函数用来处理这方面的比较。
$f = 0.07;
var_dump($f * 100 == 7);
//输出0,表示两个数字精度为小数点后3位的时候相等
var_dump(bccomp($f * 100, 7, 3));
虽然最终解决了问题,但是还是想搞明白为什么0.07这样的浮点数会有精度损失,经过一段时间的研究,发现产生误差的原因:就在于浮点数的小数位在转换成二进制的时候产生的。
浮点数小数部分转换成二进制规则:乘2取整法,即每一步将十进制小数部分乘以2,所得积的小数点左边的数字(0或1)作为二进制表示法中的数字,直到满足精确度为止。
经过计算发现0.07即使计算到60位后,依然还没有结束。在计算机中,32位的计算机中浮点数尾数部分是23位,64位的是52位。所以后面多出来的部分就会被舍弃掉。
测试都是在64位机器上进行的。
$bin  = "";
$int  = 7;
$base = 100;
echo "<table border='1'>";
echo "<td width='50'>位数</td>";
echo "<td width='50'>x2</td>";
echo "<td width='50'>位值</td>";
for ($i = 0; $i <= 60; $i++) {
    echo "<tr>";
    echo "<td>$i</td>";
    $int = $int * 2;
    echo "<td>$int</td>";
    if ($int == 100) {
        $bin.="1";
        echo "<td>1</td>";
        break;
    }
    if ($int > 100) {
        $bin.="1";
        $int = $int - $base;
        echo "<td>1</td>";
    } else {
        $bin .= "0";
        echo "<td>0</td>";
    }
    echo "</td>";
    echo "</tr>";
}
echo "</table>";
echo $bin;
对上例转换的二进制进行反推:
/*
  输出内容
  0.070000000000000006661338147751
  0.070000000000000006661338147751
 */
$f   = 0.0;
$bin = "0001000111101011100001010001111010111000010100011110101110000";
$l   = strlen($bin);
for ($i = 0; $i < $l; $i++) {
    if ($bin[$i] > 0) {
        $f = $f + pow(2, -($i + 1));
    }
}
echo number_format($f, 30);
 
 
$f = 0.07;
echo "<br />";
echo number_format($f, 30);
 

bcadd — 将两个高精度数字相加

  bccomp — 比较两个高精度数字,返回-1, 0, 1

  bcdiv — 将两个高精度数字相除

  bcmod — 求高精度数字余数

  bcmul — 将两个高精度数字相乘

  bcpow — 求高精度数字乘方

  bcpowmod — 求高精度数字乘方求模,数论里非常常用

  bcscale — 配置默认小数点位数,相当于就是Linux bc中的”scale=”

  bcsqrt — 求高精度数字平方根

  bcsub — 将两个高精度数字相减

  整理了一些实例

  php BC高精确度函数库包含了:相加,比较,相除,相减,求余,相乘,n次方,配置默认小数点数目,求平方。这些函数在涉及到有关金钱计算时比较有用,比如电商的价格计算。

/**
* 两个高精度数比较
*
* @access global
* @param float $left
* @param float $right
* @param int $scale 精确到的小数点位数
*
* @return int $left==$right 返回 0 | $left<$right 返回 -1 | $left>$right 返回 1
*/
var_dump(bccomp($left=4.45, $right=5.54, ));
// -1 /**
* 两个高精度数相加
*
* @access global
* @param float $left
* @param float $right
* @param int $scale 精确到的小数点位数
*
* @return string
*/
var_dump(bcadd($left=1.0321456, $right=0.0243456, ));
//1.04 /**
* 两个高精度数相减
*
* @access global
* @param float $left
* @param float $right
* @param int $scale 精确到的小数点位数
*
* @return string
*/
var_dump(bcsub($left=1.0321456, $right=3.0123456, ));
//-1.98 /**
* 两个高精度数相除
*
* @access global
* @param float $left
* @param float $right
* @param int $scale 精确到的小数点位数
*
* @return string
*/
var_dump(bcdiv($left=, $right=, ));
//1.20 /**
* 两个高精度数相乘
*
* @access global
* @param float $left
* @param float $right
* @param int $scale 精确到的小数点位数
*
* @return string
*/
var_dump(bcmul($left=3.1415926, $right=2.4569874566, ));
//7.71 /**
* 设置bc函数的小数点位数
*
* @access global
* @param int $scale 精确到的小数点位数
*
* @return void
*/
bcscale();
var_dump(bcdiv('', '6.55957'));
// 16.007

php关于金额比较引发的问题(转)的更多相关文章

  1. 数据库 SQL :有关 NULL 值引发 TRUE、FALSE、UNKNOW 三值逻辑

    在 Java.C# 中,相信如果是 boolean 类型值,只有两种选择 true.false.然而,在 SQL 查询中,NULL 值的引入,使得新增了 UNKNOW ,因此,就产生了 TRUE.FA ...

  2. [WCF]缺少一行代码引发的血案

    这是今天作项目支持的发现的一个关于WCF的问题,虽然最终我只是添加了一行代码就解决了这个问题,但是整个纠错过程是痛苦的,甚至最终发现这个问题都具有偶然性.具体来说,这是一个关于如何自动为服务接口(契约 ...

  3. dubbox微服务实例及引发的“血案”

    Dubbo 是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成. 主要核心部件: Remoting: 网络通信框架 ...

  4. 安全防范:nginx下git引发的隐私泄露问题

    安全防范:nginx下git引发的隐私泄露问题 1   安全事件 最近阿里云服务器后台管理系统中收到一条安全提示消息,系统配置信息泄露: http://my.domain.com/.git/confi ...

  5. Integer.parseInt 引发的血案

    Integer.parseInt 处理一个空字符串, 结果出错了, 程序没有注意到,搞了很久, 引发了血案啊!! 最后,终于 观察到了, 最后的部分: Caused by: java.lang.NoC ...

  6. 选择目录,选择文件夹的COM组件问题。在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式。请确保您的 Main 函数带有 STAThreadAttribute 标记。 只有将调试器附加到该进程才会引发此异常。

    异常: 在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式.请确保您的 Main 函数带有 STAThreadAttribute 标记. 只有将调试器附加到该进程才会引发此异常. ...

  7. 【手记】调用Process.EnterDebugMode引发异常:并非所有引用的特权或组都分配给呼叫方

    刚上线一个新版本,其中有台电脑打开软件就报[xx的类型初始值设定项引发异常](还好不是一大波电脑,新东西上线就怕哀鸿遍野),如图: 显然是该类型的静态构造函数中抛异常了(红线处就是类名),遂打开该类, ...

  8. BPM实例分享——金额规则大写

    金额规则大写 在涉及金额的流程中经常会遇到需要大写金额数据与小写金额匹配,如何实现输入数字后自动转换呢? 初级用法: 1.在默认表单基本属性javascript 中增加如下金额转换方法 /** 数字金 ...

  9. jquery 金额转换成大写

    <script language="javascript" type="text/javascript">         function Ara ...

随机推荐

  1. Installing Hadoop on Mac OSX Yosemite Tutorial Part 1.

    Installing Hadoop on Mac OSX Yosemite Tutorial Part 1. September 23, 2014 Marek 68 Comments Install ...

  2. 数据结构之图 Part2 - 1

    邻接矩阵 网上很少有C# 写图的数据结构的例子,实际的项目中也从来没用过Array 这坨东西,随手写个,勿喷. namespace LH.GraphConsole { public struct Gr ...

  3. 关于RTP负载类型及时间戳介绍

    转自:http://www.360doc.com/content/11/1018/13/1016783_157133781.shtml 首 先,看RTP协议包头的格式: 前12个字节在每一个RTP p ...

  4. SSH Key连接github提示Permission denied (publickey).错误

    root@debian64:/home/xiaoliuzi/.ssh/key_backup# ssh -T git@github.com The authenticity of host 'githu ...

  5. 在Salesforce中通过 Debug Log 方式 跟踪逻辑流程

    在Salesforce中通过 Debug Log方式 跟踪逻辑流程 具体位置如下所示: Setup ---> Logs ---> Debug Logs ---> Monitored ...

  6. Codeforces Round #248 (Div. 2) C. Ryouko's Memory Note

    题目链接:http://codeforces.com/contest/433/problem/C 思路:可以想到,要把某一个数字变成他的相邻中的数字的其中一个,这样总和才会减少,于是我们可以把每个数的 ...

  7. codeforces724-B. Batch Sort

    想着想着就忘了有什么问题没解决,坑啊 一开始读错题意了,而且一着急写了两大段差不多的代码,冗余度啊,不说了.. 显然的一点,给的数据是绝对离散的,每行都是1~m的排列 难点一.如何移动能使未排序的数组 ...

  8. error: bad symbolic reference. A signature in HiveContext.class refers to term hive

    在spark-shell中执行val sqlContext = new org.apache.spark.sql.hive.HiveContext(sc)报错: error: bad symbolic ...

  9. SpringMyBatis解析3-MapperFactoryBean

    在使用mybatis的时候,我们获取dao的方式一般是这样: SqlSession session=sessionFactory.openSession(); PersonDao personDao= ...

  10. mongodb学习04 操作详解(2)

    查找文档 筛选查找 db.collection.find(); 返回一个集合中文档的子集,子集的 范围从 0 个文档到整个集合; db.collection.findOne(); 返回筛选的一个文档; ...