Skip to content

Instantly share code, notes, and snippets.

@xuanfeng
Last active November 29, 2019 09:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xuanfeng/0f5c09ed0290290603e828fce170e211 to your computer and use it in GitHub Desktop.
Save xuanfeng/0f5c09ed0290290603e828fce170e211 to your computer and use it in GitHub Desktop.
浮点数运算
/**
* operation 浮点数运算
* https://github.com/nefe/number-precision
* @desc 解决浮动运算问题,避免小数点后产生多位数和计算精度损失。
* 问题示例:2.3 + 2.4 = 4.699999999999999,1.0 - 0.9 = 0.09999999999999998
*/
/**
* 精确加法
*/
function add(num1, num2) {
const others = []
for (let _i = 2; _i < arguments.length; _i++) {
others[_i - 2] = arguments[_i]
}
if (others.length > 0) {
return add.apply(void 0, [add(num1, num2), others[0]].concat(others.slice(1)))
}
const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)))
return (multiply(num1, baseNum) + multiply(num2, baseNum)) / baseNum
}
/**
* 精确减法
*/
function subtract(num1, num2) {
const others = []
for (let _i = 2; _i < arguments.length; _i++) {
others[_i - 2] = arguments[_i]
}
if (others.length > 0) {
return subtract.apply(void 0, [subtract(num1, num2), others[0]].concat(others.slice(1)))
}
const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)))
return (multiply(num1, baseNum) - multiply(num2, baseNum)) / baseNum
}
/**
* 精确乘法
*/
function multiply(num1, num2) {
const others = []
for (let _i = 2; _i < arguments.length; _i++) {
others[_i - 2] = arguments[_i]
}
if (others.length > 0) {
return multiply.apply(void 0, [multiply(num1, num2), others[0]].concat(others.slice(1)))
}
const num1Changed = float2Fixed(num1)
const num2Changed = float2Fixed(num2)
const baseNum = digitLength(num1) + digitLength(num2)
const leftValue = num1Changed * num2Changed
checkBoundary(leftValue)
return leftValue / Math.pow(10, baseNum)
}
/**
* 精确除法
*/
function divide(num1, num2) {
const others = []
for (let _i = 2; _i < arguments.length; _i++) {
others[_i - 2] = arguments[_i]
}
if (others.length > 0) {
return divide.apply(void 0, [divide(num1, num2), others[0]].concat(others.slice(1)))
}
const num1Changed = float2Fixed(num1)
const num2Changed = float2Fixed(num2)
checkBoundary(num1Changed)
checkBoundary(num2Changed)
// fix: 类似 10 ** -4 为 0.00009999999999999999,strip 修正
return multiply((num1Changed / num2Changed), strip(Math.pow(10, digitLength(num2) - digitLength(num1))))
}
/**
* 把错误的数据转正
* strip(0.09999999999999998)=0.1
*/
function strip(num, precision) {
if (!num) return num
if (precision === void 0) { precision = 12 }
return +parseFloat(num.toPrecision(precision))
}
/**
* Return digits length of a number
* @param {*number} num Input number
*/
function digitLength(num) {
if (!num) return num
// Get digit length of e
const eSplit = num.toString().split(/[eE]/)
const len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0))
return len > 0 ? len : 0
}
/**
* 把小数转成整数,支持科学计数法。如果是小数则放大成整数
* @param {*number} num 输入数
*/
function float2Fixed(num) {
if (!num) return num
if (num.toString().indexOf('e') === -1) {
return Number(num.toString().replace('.', ''))
}
const dLen = digitLength(num)
return dLen > 0 ? strip(num * Math.pow(10, dLen)) : num
}
/**
* 检测数字是否越界,如果越界给出提示
* @param {*number} num 输入数
*/
function checkBoundary(num) {
if (!num) return num
if (_boundaryCheckingState) {
if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
global.console.warn(num + " is beyond boundary when transfer to integer, the results may not be accurate")
}
}
}
/**
* 四舍五入
*/
function round(num, ratio) {
const base = Math.pow(10, ratio)
return divide(Math.round(multiply(num, base)), base)
}
/**
* 是否进行边界检查,默认开启
* @param flag 标记开关,true 为开启,false 为关闭,默认为 true
*/
let _boundaryCheckingState = true
function enableBoundaryChecking(flag) {
if (flag === void 0) { flag = true }
_boundaryCheckingState = flag
}
export default {
add,
subtract,
multiply,
divide,
strip,
round,
digitLength,
float2Fixed,
enableBoundaryChecking,
}
// case
console.log(operation.add(0.1, 0.2), 0.1 + 0.2) // 0.3 0.30000000000000004
console.log(operation.subtract(300.73, 70), 300.73 - 70) // 230.73 230.73000000000002
console.log(operation.subtract(2, 0.14), 2 - 0.14) // 1.86 1.8599999999999999
console.log(operation.subtract(1.05, 1), 1.05 - 1) // 0.05 0.050000000000000044
console.log(operation.subtract(300.73, 300), 300.73 - 300) // 0.73 0.7300000000000182
console.log(operation.multiply(1073.4, 100), 1073.4 * 100) // 107340 107340.00000000001
console.log(operation.multiply(7.11, 10), 7.11 * 10) // 71.1 71.10000000000001
console.log(operation.divide(7.11, 10), 7.11 / 10) // 0.711 0.7110000000000001
// 以下乘除不准确
/* operation 浮点数运算
* add / subtract / multiply /divide
* operation.add(0.1, 0.2) >> 0.3
* operation.multiply(19.9, 100) >> 1990
*
*/
import _ from 'lodash'
/*
* 判断obj是否为一个整数
*/
function isInteger(obj) {
return _.isInteger(_.toNumber(obj))
}
/*
* 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100
* @param floatNum {number} 小数
* @return {object}
* {times:100, num: 314}
*/
function toInteger(floatNum) {
if (!floatNum) {
return {
times: 1,
num: floatNum,
}
}
const ret = { times: 1, num: 0 }
if (isInteger(floatNum)) {
ret.num = floatNum
return ret
}
const strfi = floatNum + ''
const dotPos = strfi.indexOf('.')
const len = strfi.substr(dotPos + 1).length
const times = Math.pow(10, len)
const intNum = Number(floatNum.toString().replace('.', ''))
ret.times = times
ret.num = intNum
return ret
}
/*
* 核心方法,实现加减乘除运算,确保不丢失精度
* 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
*
* @param a {number} 运算数1
* @param b {number} 运算数2
* @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数
* @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
*
*/
function operation(a, b, op) {
const o1 = toInteger(a)
const o2 = toInteger(b)
const n1 = o1.num
const n2 = o2.num
const t1 = o1.times
const t2 = o2.times
const max = t1 > t2 ? t1 : t2
let result = null
switch (op) {
case 'add':
if (t1 === t2) { // 两个小数位数相同
result = n1 + n2
} else if (t1 > t2) { // o1 小数位 大于 o2
result = n1 + n2 * (t1 / t2)
} else { // o1 小数位 小于 o2
result = n1 * (t2 / t1) + n2
}
return result / max
case 'subtract':
if (t1 === t2) {
result = n1 - n2
} else if (t1 > t2) {
result = n1 - n2 * (t1 / t2)
} else {
result = n1 * (t2 / t1) - n2
}
return result / max
case 'multiply':
result = (n1 * n2) / (t1 * t2)
return result
case 'divide':
result = (n1 / n2) * t2 / t1
return result
}
}
export default {
add() {
return operation(...arguments, 'add')
},
subtract() {
return operation(...arguments, 'subtract')
},
multiply() {
return operation(...arguments, 'multiply')
},
divide() {
return operation(...arguments, 'divide')
},
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment