Code Copied

JavaScript 小数加减乘除精确计算

1. 不精确的JavaScript加减乘除操作符

在JavaScript中小数的运算经常会损失精度,当通过JavaScript的四则计算一些金额时,可能会得不到预期的效果。

就如下面的4个加减陈处运算,得到的结果都不够精确。

// js小数相加,精确的结果应当是10090.701
var numAdd = 9856.556 + 234.145;
// js小数相减,精确的结果应当是422.441
var numSub = 656.55 - 234.109;
// js小数相乘,精确的结果应当是51.285
var numMul = 34.19 * 0.03 * 50;
// js小数相除,精确的结果应当是40.0936
var numDiv = 100.234 / 2.5;
document.getElementById('result1').innerHTML = "js小数相加:" + numAdd;
document.getElementById('result2').innerHTML = "js小数相减:" + numSub;
document.getElementById('result3').innerHTML = "js小数相乘:" + numMul;
document.getElementById('result4').innerHTML = "js小数相除:" + numDiv;

运行这段脚本后,得到的结果都非我们所预期的,这几个结果都带了一串长长的小尾巴~~~。

image

在JavaScript中整数的加减乘除是精确的,如果将上面的加减乘除全部转换成整数,则能得到精确的结果。

// js小数相加,精确的结果应当是10090.701
var numAdd = (9856.556 * 1000 + 234.145 * 1000) / 1000;
// js小数相减,精确的结果应当是422.441
var numSub = (656.55 * 1000 - 234.109 * 1000) / 1000;
// js小数相乘,精确的结果应当是51.285
var numMul = (34.19 * 100) * (0.03 * 100) * 50 / 10000;
// js小数相除,精确的结果应当是40.0936
var numDiv = (100.234 * 1000) / (2.5 * 10) / 100;
document.getElementById('result1').innerHTML = "js小数相加:" + numAdd;
document.getElementById('result2').innerHTML = "js小数相减:" + numSub;
document.getElementById('result3').innerHTML = "js小数相乘:" + numMul;
document.getElementById('result4').innerHTML = "js小数相除:" + numDiv;

image

2. 精确的加减乘除计算方式

通过上面的小数转整数运算方式,可以总结出精确的加减乘除计算方式:将小数都转成整数后做加减乘除操作,然后再转成小数。
(假设两个小数分别是a和b,以下公式图片在http://www.forkosh.com/mathtextutorial.html生成)

2.1 精确的加法计算方式

获取a和b中最长的小数位数n,将a,b乘以10n转换为整数相加后再除以10n,公式如下:

对应的JavaScript方法:

/**
 * 加法运算,避免数据相加小数点后产生多位数和计算精度损失。
 * 
 * @param a加数1 | b加数2
 */
function Add(a, b) {
    var n, n1, n2;
    try {
        n1 = a.toString().split(".")[1].length;
    } catch (e) {
        n1 = 0;
    }
    try {
        n2 = b.toString().split(".")[1].length;
    } catch (e) {
        n2 = 0;
    }
    n = Math.pow(10, Math.max(n1, n2));
    return (a * n + b * n) / n;
}

2.2 精确的减法计算方式

获取a和b中最长的小数位数n,将a,b乘以10n转换为整数相加后再除以10n,公式如下:

对应的JavaScript方法:

/**
 * 减法运算,避免数据相减小数点后产生多位数和计算精度损失。
 * 
 * @param a被减数  |  b减数
 */
function Sub(a, b) {
    var n, n1, n2;
    var precision; // 精度
    try {
        n1 = a.toString().split(".")[1].length;
    } catch (e) {
        n1 = 0;
    }
    try {
        n2 = b.toString().split(".")[1].length;
    } catch (e) {
        n2 = 0;
    }
    baseNum = Math.pow(10, Math.max(n1, n2));
    n = Math.pow(10, Math.max(n1, n2));
    return (a * n - b * n) / n;
}

2.3 精确的乘法计算方式

获取a和b对应的小数位m和n,将a乘以10m、b乘以10n转换为整数后再相乘,然后再除以10(m+n),公式如下:

/**
 * 乘法运算,避免数据相乘小数点后产生多位数和计算精度损失。
 * 
 * @param a被乘数 | b乘数
 */
function Mul(a, b) {
    var n1 = 0,
        n2 = 0;
    try {
        n1 += a.toString().split(".")[1].length;
    } catch (e) {}
    try {
        n2 += b.toString().split(".")[1].length;
    } catch (e) {}
    return (a * Math.pow(10, n1)) * (b * Math.pow(10, n2)) / Math.pow(10, n1 + n2);
}

2.4精确的除法计算方式

获取a和b对应的小数位m和n,将a乘以10m、b乘以10n转换为整数后再相除,然后再除以10(m-n),公式如下:

/**
 * 除法运算,避免数据相除小数点后产生多位数和计算精度损失。
 * 
 * @param a被除数 | b除数
 */
function Div(a, b) {
    var n1 = 0,
        n2 = 0;
    var baseNum3, baseNum4;
    try {
        n1 += a.toString().split(".")[1].length;
    } catch (e) {}
    try {
        n2 += b.toString().split(".")[1].length;
    } catch (e) {}
    return (a * Math.pow(10, n1)) / (b * Math.pow(10, n2)) / Math.pow(10, n1 - n2);
}

2.5 验证结果

以上完整的代码,请看jsfiddle的链接:http://jsfiddle.net/SunnyPeng/8SSEc/

/**
 * 加法运算,避免数据相加小数点后产生多位数和计算精度损失。
 * 
 * @param a加数1 | b加数2
 */
function Add(a, b) {
    var n, n1, n2;
    try {
        n1 = a.toString().split(".")[1].length;
    } catch (e) {
        n1 = 0;
    }
    try {
        n2 = b.toString().split(".")[1].length;
    } catch (e) {
        n2 = 0;
    }
    n = Math.pow(10, Math.max(n1, n2));
    return (a * n + b * n) / n;
}
/**
 * 减法运算,避免数据相减小数点后产生多位数和计算精度损失。
 * 
 * @param a被减数  |  b减数
 */
function Sub(a, b) {
    var n, n1, n2;
    var precision; // 精度
    try {
        n1 = a.toString().split(".")[1].length;
    } catch (e) {
        n1 = 0;
    }
    try {
        n2 = b.toString().split(".")[1].length;
    } catch (e) {
        n2 = 0;
    }
    baseNum = Math.pow(10, Math.max(n1, n2));
    n = Math.pow(10, Math.max(n1, n2));
    return (a * n - b * n) / n;
}
/**
 * 乘法运算,避免数据相乘小数点后产生多位数和计算精度损失。
 * 
 * @param a被乘数 | b乘数
 */
function Mul(a, b) {
    var n1 = 0,
        n2 = 0;
    try {
        n1 += a.toString().split(".")[1].length;
    } catch (e) {}
    try {
        n2 += b.toString().split(".")[1].length;
    } catch (e) {}
    return (a * Math.pow(10, n1)) * (b * Math.pow(10, n2)) / Math.pow(10, n1 + n2);
}
/**
 * 除法运算,避免数据相除小数点后产生多位数和计算精度损失。
 * 
 * @param a被除数 | b除数
 */
function Div(a, b) {
    var n1 = 0,
        n2 = 0;
    var baseNum3, baseNum4;
    try {
        n1 += a.toString().split(".")[1].length;
    } catch (e) {}
    try {
        n2 += b.toString().split(".")[1].length;
    } catch (e) {}
    return (a * Math.pow(10, n1)) / (b * Math.pow(10, n2)) / Math.pow(10, n1 - n2);
}

// js小数相加,精确的结果应当是10090.701
var numAdd = Add(9856.556, 234.145);
// js小数相减,精确的结果应当是422.441
var numSub = Sub(656.55, 234.109);
// js小数相乘,精确的结果应当是51.285
var numMul = Mul(Mul(34.19, 0.03), 50);
// js小数相除,精确的结果应当是40.0936
var numDiv = Div(100.234, 2.5);
document.getElementById('result1').innerHTML = "js小数相加:" + numAdd;
document.getElementById('result2').innerHTML = "js小数相减:" + numSub;
document.getElementById('result3').innerHTML = "js小数相乘:" + numMul;
document.getElementById('result4').innerHTML = "js小数相除:" + numDiv;

image