精度计算问题

50 篇文章 0 订阅

如果用PHP的算术计算浮点数的时候,可能会遇到一些计算结果错误的问题

<?php
$a = 0.1;
$b = 0.7;
var_dump(($a + $b) == 0.8); //false
$f = 0.58;
var_dump(intval($f * 100)); //为啥输出57

解决方案:安装bcmath这个扩展

bc是Binary Calculator的缩写。bc*函数的参数都是操作数加上一个可选的 [int scale],比如string bcadd(string $left_operand, string $right_operand[, int $scale]),如果scale没有提供,就用bcscale的缺省值。这里大数直接用一个由0-9组成的string表示,计算结果返回的也是一个string,计算结果不会四舍五入

<?php
var_dump(bcmul('6978', '0.0816', 4)); //569.4048
var_dump(bcmul('6978', '0.0816', 3)); //569.404
echo bcadd('1.266', '1', 2);          //2.26

bcadd — 将两个高精度数字相加
bccomp — 比较两个高精度数字,返回-1, 0, 1
bcdiv — 将两个高精度数字相除
bcmod — 求高精度数字余数
bcmul — 将两个高精度数字相乘
bcpow — 求高精度数字乘方
bcpowmod — 求高精度数字乘方求模,数论里非常常用
bcscale — 配置默认小数点位数
bcsqrt — 求高精度数字平方根
bcsub — 将两个高精度数字相减

/**
 * 精度计算四舍五入结果.
 *
 * @param string  $number1   数字1
 * @param string  $number2   数字2
 * @param string  $bcMethod  精度计算方法
 * @param int     $precision 精度
 *
 * @return float
 */
public function bcCalculateRound($number1, $number2, $bcMethod, $precision = 3)
{
    if (!in_array($bcMethod, [ 'bcadd', 'bcsub', 'bcmul', 'bcdiv' ])) {
        throw new Exception('unknown bcmath method');
    }
    $result = call_user_func($bcMethod, $number1, $number2, ++$precision);

    return round($result, $precision);
}

/**
 * 保留小数点后几位,并且不四舍五入
 * $bcscale 保留位数,默认2位
 */
function number_float_format($number, $bcscale = 2) {
    $tmp_bcscale = $bcscale + 1;
    return sprintf("%.{$bcscale}f", substr(sprintf("%.{$tmp_bcscale}f", $number), 0, -1));
}

/**
 * 保留两位小数并向上取整
 * $number 保留位数,默认2位
 */
function numberCeilHundred($number) {
    return ceil($number * 10000 / 100) / 100;
}

/**
 * 多数安全相加函数.
 *
 * @param array $arr
 * @param int   $scale
 *
 * @return bool|string
 */
public static function addFormat(array $arr, int $scale = 3)
{
    $result = false;
    if (is_array($arr) && count($arr) > 1 && is_int($scale)) {
        $result = 0;
        foreach ($arr as $value) {
            if (is_numeric($value)) {
                $result = bcadd($result, $value, $scale);
            }
        }
    }

    return $result;
}

/**
 * 多数安全相乘法函数.
 *
 * @param array $arr
 * @param int   $scale
 *
 * @return bool|string
 */
public static function mulFormat(array $arr, int $scale = 3)
{
    $result = false;
    if (is_array($arr) && count($arr) > 1 && is_int($scale)) {
        $result = 1;
        foreach ($arr as $value) {
            if (is_numeric($value)) {
                $result = bcmul($result, $value, $scale);
            }
        }
    }

    return $result;
}

/**
 * 多数安全相除法函数.
 *
 * @param array $arr
 * @param int   $scale
 *
 * @return bool|string
 */
public static function divFormat(array $arr, int $scale = 3)
{
    $result = false;
    if (is_array($arr) && count($arr) > 1 && is_int($scale)) {
        foreach ($arr as $key => $value) {
            if (is_numeric($value)) {
                if (0 === $key) {
                    $result = $value;
                } elseif (0 != $value) {
                    $result = bcdiv($result, $value, $scale);
                }
            }
        }
    }

    return $result;
}

JS浮点数精确计算函数(加,减,乘,除)

<script type="text/javascript">
    var Digit = {};
    /**
     * 四舍五入法截取一个小数
     * @param float digit 要格式化的数字
     * @param integer length 要保留的小数位数
     * @return float
     */
    Digit.round = function (digit, length) {
        length = length ? parseInt(length) : 0;
        if (length <= 0) return Math.round(digit);
        digit = Math.round(digit * Math.pow(10, length)) / Math.pow(10, length);
        return digit;
    };
    /**
     * 舍去法截取一个小数Digit.floor(1.653,2)
     * @param float digit 要格式化的数字
     * @param integer length 要保留的小数位数
     * @return float
     */
    Digit.floor = function (digit, length) {
        length = length ? parseInt(length) : 0;
        if (length <= 0) return Math.floor(digit);
        digit = Math.floor(digit * Math.pow(10, length)) / Math.pow(10, length);
        return digit;
    };
    /**
     * 进一法截取一个小数
     * @param float digit 要格式化的数字
     * @param integer length 要保留的小数位数
     * @return float
     */
    Digit.ceil = function (digit, length) {
        length = length ? parseInt(length) : 0;
        if (length <= 0) return Math.ceil(digit);
        digit = Math.ceil(digit * Math.pow(10, length)) / Math.pow(10, length);
        return digit;
    }

    //浮点数加法运算
    Digit.add = function (arg1, arg2) {
        var r1, r2, m;
        try{r1=arg1.toString().split(".")[1].length}catch(e){r1=0}
        try{r2=arg2.toString().split(".")[1].length}catch(e){r2=0}
        m = Math.pow(10, Math.max(r1, r2))
        return (arg1 * m + arg2 * m) / m
    }

    //浮点数减法运算
    Digit.sub = function (arg1, arg2) {
        var r1, r2, m, n;
        try{r1=arg1.toString().split(".")[1].length}catch(e){r1=0}
        try{r2=arg2.toString().split(".")[1].length}catch(e){r2=0}
        m = Math.pow(10, Math.max(r1, r2));
        //动态控制精度长度
        n = (r1 >= r2) ? r1 : r2;
        return ((arg1 * m - arg2 * m) / m).toFixed(n);
    }

    //浮点数乘法运算
    Digit.mul = function (arg1, arg2) {
        var m = 0, s1 = arg1.toString(), s2 = arg2.toString();
        try{m+=s1.split(".")[1].length}catch(e){}
        try{m+=s2.split(".")[1].length}catch(e){}
        return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m)
    }

    //浮点数除法运算
    Digit.div = function (arg1, arg2) {
        var t1=0,t2=0,r1,r2;
        try{t1=arg1.toString().split(".")[1].length}catch(e){}
        try{t2=arg2.toString().split(".")[1].length}catch(e){}
        with(Math){
            r1 = Number(arg1.toString().replace(".", ""))
            r2 = Number(arg2.toString().replace(".", ""))
            return (r1 / r2) * pow(10, t2 - t1);
        }
    }

    /** 这个函数可以添加分隔逗号或者进行四舍五入。
     *   Usage:  (123456.789).number_format(2, '.', ',');
     *   result: 123,456.79
     **/
    Number.prototype.number_format = function (decimals, decimal_sep, thousands_sep) {
        var n = this,
                c = isNaN(decimals) ? 2 : Math.abs(decimals),
                d = decimal_sep || '.',
                t = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
                sign = (n < 0) ? '-' : '',
                i = parseInt(n = Math.abs(n).toFixed(c)) + '',
                j = ((j = i.length) > 3) ? j % 3 : 0;

        return sign + (j ? i.substr(0, j) + t : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
    }

    /*  给数字添加"st, nd, rd, th"等序数词。
     *   Usage:
     *   var myNumOld = 23
     *   var myNumNew = myNumOld.toOrdinal()
     *   Result: 23rd
     */
    Number.prototype.toOrdinal = function () {
        var n = this % 100;
        var suffix = ['th', 'st', 'nd', 'rd', 'th'];
        var ord = n < 21 ? (n < 4 ? suffix[n] : suffix[0]) : (n % 10 > 4 ? suffix[0] : suffix[n % 10]);
        return this + ord;
    }
</script>

MySQL浮点计算存在的问题与解决方案

SELECT .1E0 + .2E0 = .3E0;

1.不要使用float、double以及其等价类型做精确计算。如果要在MySQL中做精确计算,推荐使用decimal或者将相关计算任务交给应用。

2、基于应用的解决方案:使用最小单位做字段。比如商品价格涉及到小数,用20.95元表示,那么可以把价格变成以分为单位,即变成2095。

3.查询做转化 cast('字段名' as decimal(18,4)))

select sum(cast(item_value as decimal(18,4))) from items 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值