zoukankan      html  css  js  c++  java
  • php关于金额比较引发的问题(转)

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

    $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, 2));
    // -1
      
     /**
      * 两个高精度数相加
      * 
      * @access global
      * @param float $left
      * @param float $right
      * @param int $scale 精确到的小数点位数
      * 
      * @return string 
      */
    var_dump(bcadd($left=1.0321456, $right=0.0243456, 2));
    //1.04
     
      /**
      * 两个高精度数相减
      * 
      * @access global
      * @param float $left
      * @param float $right
      * @param int $scale 精确到的小数点位数
      * 
      * @return string 
      */
    var_dump(bcsub($left=1.0321456, $right=3.0123456, 2));
    //-1.98
      
     /**
      * 两个高精度数相除
      * 
      * @access global
      * @param float $left
      * @param float $right
      * @param int $scale 精确到的小数点位数
      * 
      * @return string 
      */
    var_dump(bcdiv($left=6, $right=5, 2));
    //1.20
     
     /**
      * 两个高精度数相乘
      * 
      * @access global
      * @param float $left
      * @param float $right
      * @param int $scale 精确到的小数点位数
      * 
      * @return string 
      */
    var_dump(bcmul($left=3.1415926, $right=2.4569874566, 2));
    //7.71
     
     /**
      * 设置bc函数的小数点位数
      * 
      * @access global
      * @param int $scale 精确到的小数点位数
      * 
      * @return void 
      */ 
    bcscale(3);
    var_dump(bcdiv('105', '6.55957')); 
    // 16.007
  • 相关阅读:
    检测.netcore环境是否安装
    vc获取特殊路径
    vc 获取系统版本
    vs2019集成vcpkg
    vc 打开外部线程并等待结束
    cmd常用命令
    nodejs mongodb3.6.2 insertOne callback问题
    Effective Python Ver2.0_StudyNotes___getattr__、__getattribute__及__setattr__的一些知识点
    Python数据类型--集合简介
    Effective Python Ver2.0_StudyNotes_用描述符来改写需要复用的@property方法
  • 原文地址:https://www.cnblogs.com/jackluo/p/5549003.html
Copyright © 2011-2022 走看看