Created
May 13, 2018 14:35
-
-
Save Jancat/1a17722e58a9aa65c7deba6d55fb536e to your computer and use it in GitHub Desktop.
JavaScript 浮点数精确运算
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 浮点数精确运算,解决浮点数运算误差问题。比如: | |
* 0.1 + 0.2 = 0.30000000000000004、 | |
* 0.3 - 0.2 = 0.09999999999999998、 | |
* 0.7 * 0.1 = 0.06999999999999999、 | |
* 0.3 / 0.1 = 2.9999999999999996 | |
* 不支持科学计数法表示的浮点数 | |
* 原理:浮点数由于有限的存储位数,无法精确表示超出存储位数的浮点数,所以浮点数运算结果会有误差; | |
* 而整数没有浮点数运算误差的问题,所以在浮点数运算过程中先转成整数再计算,计算结果再转换为浮点数 | |
**/ | |
// 获取小数位数 | |
function digitLength(num) { | |
return (num.toString().split('.')[1] || '').length | |
} | |
// 获取让乘积为整数的最小乘数 | |
function minMultiplier(num1, num2) { | |
return 10 ** Math.max(digitLength(num1), digitLength(num2)) | |
} | |
// 浮点数转整数(字符串处理) | |
function float2Integer(num) { | |
return Number(num.toString().replace('.', '')) | |
} | |
// 数值超过安全整数 warning | |
function checkBoundary(num) { | |
if (!Number.isSafeInteger(num)) { | |
console.warn( | |
`${num} is beyond boundary when transfer to integer, the results may not be accurate`, | |
) | |
} | |
} | |
// 精确乘法 | |
function times(num1, num2, ...others) { | |
if (others.length > 0) { | |
return times(times(num1, num2), others[0], ...others.slice(1)) | |
} | |
const product = float2Integer(num1) * float2Integer(num2) | |
checkBoundary(product) | |
const digitLengthSum = digitLength(num1) + digitLength(num2) | |
return product / 10 ** digitLengthSum | |
} | |
// 精确除法 | |
function divide(num1, num2, ...others) { | |
if (others.length > 0) { | |
return divide(divide(num1, num2), others[0], ...others.slice(1)) | |
} | |
const num1Changed = float2Integer(num1) | |
const num2Changed = float2Integer(num2) | |
checkBoundary(num1Changed) | |
checkBoundary(num2Changed) | |
// 最后需要调整小数点 | |
const lastOffset = 10 ** (digitLength(num2) - digitLength(num1)) | |
return times(num1Changed / num2Changed, lastOffset) | |
} | |
// 精确加法 | |
function plus(num1, num2, ...others) { | |
if (others.length > 0) { | |
return plus(plus(num1, num2), others[0], ...others.slice(1)) | |
} | |
const baseNum = minMultiplier(num1, num2) | |
return (times(num1, baseNum) + times(num2, baseNum)) / baseNum | |
} | |
// 精确减法 | |
function minus(num1, num2, ...others) { | |
if (others.length > 0) { | |
return minus(minus(num1, num2), others[0], ...others.slice(1)) | |
} | |
const baseNum = minMultiplier(num1, num2) | |
return (times(num1, baseNum) - times(num2, baseNum)) / baseNum | |
} | |
// 测试 | |
// console.log(plus(0.1, 0.2) === 0.3) | |
// console.log(plus(2.3, 2.4) === 4.7) | |
// console.log(plus(-1.6, -1) === -2.6) | |
// console.log(plus(-2.0, 63) === 61) | |
// console.log(plus(-3, 7) === 4) | |
// console.log(plus(-221, 38) === -183) | |
// console.log(plus(-1, 0) === -1) | |
// console.log(plus(2.018, 0.001) === 2.019) | |
// console.log(plus(0.1, 0.2, 0.3) === 0.6) | |
// console.log(minus(0.07, 0.01) === 0.06) | |
// console.log(minus(0.7, 0.1) === 0.6) | |
// console.log(minus(1.0, 0.9) === 0.1) | |
// console.log(minus(1, 0) === 1) | |
// console.log(minus(1, -0) === 1) | |
// console.log(minus(-1, 0) === -1) | |
// console.log(minus(-1, -0) === -1) | |
// console.log(minus(1, 22) === -21) | |
// console.log(minus(8893568.397103781249, -7.2967405955) === 8893575.693844376749) | |
// console.log(minus(105468873, 0) === 105468873) | |
// console.log(minus(1.7e-30, 0.1e-30) === 1.6e-30) | |
// console.log(minus(6, 3, 2) === 1) | |
// console.log(minus(6, 3, 2, 1, 2, 3) === -5) | |
// console.log(times(0.07, 100) === 7) | |
// console.log(times(0.7, 0.1) === 0.07) | |
// console.log(times(3, 0.3) === 0.9) | |
// console.log(times(118762317358.75, 1e-8) === 1187.6231735875) | |
// console.log(times(0.362, 100) === 36.2) | |
// console.log(times(1.1, 1.1) === 1.21) | |
// console.log(times(2.018, 1000) === 2018) | |
// console.log(times(5.2, -3.8461538461538462) === -20) | |
// console.log(times(1.22, -1.639344262295082) === -2) | |
// console.log(times(2.5, -0.92) === -2.3) | |
// console.log(times(-2.2, 0.6363636363636364) === -1.4) | |
// console.log(times(-3, 2.3333333333333335) === 7) | |
// console.log(times(-0.076, -92.10526315789471) === 7) | |
// console.log(times(8.0, -0.3625) === -2.9) | |
// console.log(times(6.6, 0.30303030303030304) === 2) | |
// console.log(times(10.0, -0.8) === -8) | |
// console.log(times(-1.1, -7.272727272727273) === 8) | |
// console.log(times(2, 2, 3) === 12) | |
// console.log(times(2, 2, 3, 0.1) === 1.2) | |
// console.log(divide(1.21, 1.1) === 1.1) | |
// console.log(divide(4750.49269435, 4) === 1187.6231735875) | |
// console.log(divide(0.9, 3) === 0.3) | |
// console.log(divide(36.2, 0.362) === 100) | |
// console.log(divide(-20, 5.2) === -3.8461538461538462) | |
// console.log(divide(-2, 1.22) === -1.639344262295082) | |
// console.log(divide(-2.3, 2.5) === -0.92) | |
// console.log(divide(-1.4, -2.2) === 0.6363636363636364) | |
// console.log(divide(7, -3) === -2.3333333333333335) | |
// console.log(divide(7, -0.076) === -92.10526315789471) | |
// console.log(divide(-2.9, 8.0) === -0.3625) | |
// console.log(divide(2, 6.6) === 0.30303030303030304) | |
// console.log(divide(-8, 10.0) === -0.8) | |
// console.log(divide(8, -1.1) === -7.272727272727273) | |
// console.log(divide(12, 3, 2) === 2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment