0.1 + 0.2 !== 0.3

406 阅读3分钟

出现的原因

0.1 + 0.2 = 0.30000000000000004

计算机内部转成二进制去计算

十进制整数转成二进制 ,除以2取余数,倒叙输出 十进制小数转成二进制, 乘以2取整数,一直乘以输出结果是整数,正序输出

如 十进制 10 转二进制

10/2 = 5 ....0
5/2 = 2 .....1
2/2 = 1 ....0

结果就是10>1010 ,反过来转换就是1x2^3+0x2^2+1x2^1+0x2^0 = 8+2 = 10

十进制小数0.125 转二进制

0.125*2 = 0.25....0
0.25*2  = 0.5 ...0
0.5*2   = 1 ....1 

结果就是0.125->0.001,反过来转换就是 0x2^-1+ 0x^-2 + 1x2^-3= 0.125

进制转换工具

而十进制0.1 转换成二进制

image.png

而十进制0.2 转换成二进制

image.png

进制相加得(对阶运算,如1+1=>10)

0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1111

无限循环,只能取一个近似值,接下来就由IEE 754标准(后面会提到)

image.png

转换成10进制

image.png

IEE754 标准

根据IEEE 754标准,单精度float类型使用32bit储,其中1位表示符号,8位表示指数,23位表示尾数;双精度double类型使用64bit存储,1位符号位,11位指数位,52位尾数位。

维基百科 IEEE 754标准(二进制浮点数算术标准)

image.png

解决方法

乘以10除以10 并不是万能的,比如场景满足不了,数字我是随便举的,我没有不知道有啥规律

(18.9910 + 0.210)/10 // 19.189999999999998

(1.2110-1.110)/10 // 0.10999999999999996

推荐库

big.js

number-precision

bigdecimal.js

number-precision

下面我选一个number-precision,看下到底是怎么写的

其根本原理是挪动小数点,使其变成整数,然后再进行运算

  • 加法
function plus(num1,num2){  
    // 求出两个数字之间小数点最长的长度
    const len  = Math.max(digitLength(num1),digitLength(num2))
    console.log("len",len)
    const baseNum  = Math.pow(10,len)
    return (times(num1,baseNum) + times(num2,baseNum)) / baseNum
 
}

function times(num,baseNum){
    const num1Changed  = float2Fixed(num1)
    const baseNumChanged  = float2Fixed(baseNum)
    // 变成整数之后,求出小数点的长度,也就是说小数点移动了多少位,然后除以多少位,
    // 举例18.99->1899,移动了两位,1899*100/10^2 = 1899
    const len = digitLength(num)
    return  (num1Changed * baseNumChanged) / Math.pow(10,len)
}

function float2Fixed(num){
    // 这个方法很关键,就是将小数点去掉,变成整数运算
    return  num.toString().replace('.','')
}

function digitLength(num){
    const  nSplit =  num.toString().split('.')
    const  len = nSplit[1] ? nSplit[1].length : 0
    return len
}

console.log(plus(18.99,0.2))
  • 减法
1.21-1.1 = 0.10999999999999988
 function minus(num1,num2){  
    // 求出两个数字之间小数点最长的长度
    const len  = Math.max(digitLength(num1),digitLength(num2))
    console.log("len",len)
    const baseNum  = Math.pow(10,len)
    return (times(num1,baseNum) - times(num2,baseNum)) / baseNum
 // 最后能正确输出结果
}
  • 乘法

0.9*1.1 ->0.9900000000000001

在函数times改动下代码

function times(num1,num2){
    const num1Changed  = float2Fixed(num1)
    const num2Changed  = float2Fixed(num2)
    // 变成整数之后,求出小数点的长度,也就是说小数点移动了多少位,然后除以多少位,
    const len = digitLength(num1) + digitLength(num2)
    return  (num1Changed * num2Changed) / Math.pow(10,len)
}
  • 除法

1.21/1.1 = 1.0999999999999999

function divide(num1, num2) {
  const num1Changed = float2Fixed(num1);
  const num2Changed = float2Fixed(num2);
  return times(num1Changed / num2Changed, strip(Math.pow(10, digitLength(num2) - digitLength(num1))));
}

修正数字

function  strip(num){
  return  +praseFloat(Number(num).toPrecision(15))
}

toPrecision()与toFixed()

let d = 12.1356
d.toFixed(3)  -> 2.136
d.toPrecision(3)->2.14

toPrecision() 包括了整数位

toFixed() 只是小数位

整数边界问题

JS支持的整数的有效范围是-2^53~2^53次方如果超出这个值返回的数值会发生混乱


function checkBoundary(num) {
  if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
      console.warn(`${num} is beyond boundary when transfer to integer, the results may not be accurate`);
    }
}

image.png

其他

java用BigDecimal这个