Skip to content

Instantly share code, notes, and snippets.

@Jancat
Created May 13, 2018 14:35
Show Gist options
  • Save Jancat/1a17722e58a9aa65c7deba6d55fb536e to your computer and use it in GitHub Desktop.
Save Jancat/1a17722e58a9aa65c7deba6d55fb536e to your computer and use it in GitHub Desktop.
JavaScript 浮点数精确运算
/**
* 浮点数精确运算,解决浮点数运算误差问题。比如:
* 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