JavaScript中Double类型进行减法出现精度问题

一、问题分析

今天在做一个js实现图书商城购物车的动态添加删除和总计、优惠计算的时候,使用减法计算优惠的时候,会发现我给定的价格都只有小数点后两位,但是做减法的时候会出现小数后面出现多位的情况,但并不是所有的都会出现。

//小计的计算
let smallcount = (price * numinput.value);
//优惠后的小计
let dissmallcount = (disprice * numinput.value);
//累计实际消费总计
total +=dissmallcount;
//累计优惠后节约的金额
distotal += smallcount - dissmallcount;
//累计积分总计
scoretotal += score;
//将计算值添加到页面中
$("total").innerHTML = total;
$("distotal").innerHTML = distotal;
$("scoretotal").innerHTML = scoretotal;

于是上网查找出现问题的原因:

发现类似问题
http://www.iteye.com/problems/26677

public class Change{
    public static void main(String args[]){
        System.out.println(2.00 - 1.10);
    }
}

“你可能会很天真地期望该程序能够打印出0.90,但是它如何才能知道你想要打印小数点后两位小数呢?
如果你对在Double.toString 文档中所设定的将double 类型的值转换为字符串的规则有所了解,你就会知道该程序打印出来的小数,是足以将double 类型的值与最靠近它的临近值区分出来的最短的小数,它在小数点之前和之后都至少有一位。

因此,看起来,该程序应该打印0.9 是合理的。
这么分析可能显得很合理,但是并不正确。如果你运行该程序,你就会发现它打印的是0.8999999999999999问题在于1.1 这个数字不能被精确表示成为一个double,因此它被表示成为最接近它的double 值。该程序从2 中减去的就是这个值。遗憾的是,这个计算的结果并不是最接近0.9 的double 值。表示结果的double 值的最短表示就是你所看到的打印出来的那个可恶的数字。
更一般地说,问题在于并不是所有的小数都可以用二进制浮点数来精确表示的。 ”

二、思考尝试 

终于发现了问题的原因,经过思考之后决定使用toFixed(2)来保留两位小数以此来消除精度问题。

distotal += (smallcount - dissmallcount).toFixed(2);

但是发现toFixed方法会将数据类型转成String类型的,进行减法结果的累加的时候会变成字符串拼接。所以尝试使用乘1.0来将计算后的结果的数据类型再转回Double型,但是尝试后发现精度问题又重新出现。

distotal += (smallcount - dissmallcount).toFixed(2)*1.0;

既然不能在在计算时使用toFixed,我就在计算完的结果上使用,尝试发现,在减法中出现的精度问题会使最终的计算结果出现问题,所以此方案也行不通。

$("total").innerHTML = total.toFixed(2);
$("distotal").innerHTML = distotal.toFixed(2);

三、解决方案

经过反复的摸索,终于找到了一个不错的解决方案。既然Double型的精度有问题,那我就直接用整形来计算。因为我的数据全都是从页面取的,所以取出来全都是小数点后保留两位的字符串类型,使用replace移除¥和小数点再转整型(注:为什么不去除¥符后直接转浮点型而是再去除小数点呢,因为这样放大100倍的时候会去除最后面的零使放大倍数不一。例:25.10 ->25.1,放大后为25100而不是251000),得到放大100倍的数值,再用整型进行减法运算得到计算结果再缩小100倍,这样就消除了Double型的精度问题。为了保证结果为小数点后两位,再对累加的结果使用toFixed(2),这样问题就解决了!

let price = lis[1].textContent.replace('¥', '');
let disprice = (lis[2].textContent.replace('¥', ''));
//小计的计算
let smallcount = (parseInt(price.replace('.','')) * numinput.value);
//优惠后的小计
let dissmallcount = (parseInt(disprice.replace('.','')) * numinput.value);
//累计实际消费总计
total +=dissmallcount/100;
//累计优惠后节约的金额
distotal += (smallcount - dissmallcount)/100;
//累计积分总计
scoretotal += parseInt(score.replace('.',''))/10;
//将计算值添加到页面中
$("total").innerHTML = total.toFixed(2);
$("distotal").innerHTML = distotal.toFixed(2);
$("scoretotal").innerHTML = scoretotal;

四、总结

在解决的过程中使用了toFixed、replace、parseInt等方法,最终围绕着浮点型的精确度的问题,尝试消除小数点后会出现的0000000000001,并使用整型来进行运算,避免使用浮点型来运算,以此来解决问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值