Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第二十二题:0.1 + 0.2 !== 0.3? #28

Closed
KieSun opened this issue Apr 7, 2021 · 20 comments
Closed

第二十二题:0.1 + 0.2 !== 0.3? #28

KieSun opened this issue Apr 7, 2021 · 20 comments

Comments

@KieSun
Copy link
Owner

KieSun commented Apr 7, 2021

为什么 0.1 + 0.2 !== 0.3,请描述原因并手写解决该问题的函数。

去答题

新建了一个大厂真题每日打卡群,有意愿学习打卡的再进,请备注打卡

@goldEli
Copy link

goldEli commented Apr 7, 2021

原因

JavaScript 在数字相加时会将数字转成二进制再运算

0.1 
转成二进制:
0.0001100110011001100110011001100110011001100110011001101....

无限循环在截断时丢失精度,导致 0.1 + 0.2 不等于 0.3

解决

利用 toFix 方法来解决精度丢失的问题

function add(a, b) {
  return parseFloat((a + b).toFixed(2))
}
console.log(add(0.1, 0.2) === 0.3) //true

@keien411
Copy link

keien411 commented Apr 7, 2021

原因

因为 JS 采用 IEEE 754 双精度版本(64位)
浮点数用二进制表示的时候是无穷的,因为精度的问题,两个浮点数相加会造成截断丢失精度,因此再转换为十进制就出了问题

解决

export const addNum = (num1: number, num2: number) => {
  let sq1;
  let sq2;
  let m;
  try {
    sq1 = num1.toString().split('.')[1].length;
  } catch (e) {
    sq1 = 0;
  }
  try {
    sq2 = num2.toString().split('.')[1].length;
  } catch (e) {
    sq2 = 0;
  }
  m = Math.pow(10, Math.max(sq1, sq2));
  return (Math.round(num1 * m) + Math.round(num2 * m)) / m;
};

不是原创,是作者的解决方案

@niices
Copy link

niices commented Apr 7, 2021

原因

由于计算机中所有数据都是以二进制储存的,计算时需要把数据转换成二进制进行计算,再把结果转换成十进制。
而大多数小数的二进制都是无限循环的,根据IEEE 754标准,Number类型使用64位固定长度来表示。
其中符号位为占1位,指数位占11位,尾数位占52位。
此时0.1+0.2的二进制会丢失精度从而不等于0.3的二进制。

解决

ES6

使用ES6提供的Number.EPSILON方法

function numbersequal(a,b){ 
  return Math.abs(a-b)<Number.EPSILON;
} 
var a=0.1+0.2;
var b=0.3;
console.log(numbersequal(a,b));  //true

ES6之前

把计算数字 提升 10 的N次方倍再除以 10 的N次方。N>1.

(0.1*1000+0.2*1000)/1000===0.3 //true

@zhangyingcai
Copy link

zhangyingcai commented Apr 7, 2021

原因:js使用的 IEEE 754 双精度问题
小数通过二进制表示是以无限循环存储的,浮点数标准会裁剪掉数字。

解决:
toFixed函数

const toFixed = (a,b,ratio=2)=>parseFloat((a+b).toFixed(ratio))
toFixed(0.1,0.2)

@turkyden
Copy link

turkyden commented Apr 7, 2021

0.1 + 0.2 !== 0.3 ?

原因:

计算机数值计算采用二进制,而 JS 采用 IEEE 754 双精度版本 (64),在 0.1 转成二进制过程中,
会生成无限循环并且在截断时丢失精度,从而导致 0.1 + 0.2 不等于 0.3

image

解决:

  1. parseFloat((0.1 + 0.2).toFixed(2)) 利用 toFix 方法来解决精度丢失的问题
  2. Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON 使用 ES6 语法中的 Number.EPSILON 新属性判断

二进制运算

+ 0.00011001100110011001100110011001100110011001100110011010
- 0.0011001100110011001100110011001100110011001100110011010

二进制转十进制

0.0100110011001100110011001100110011001100110011001100111 -> 0.30000000000000004

@louwenqiang
Copy link

浮点数双精度的问题 ,大数相加 ,也是同样的道理

一、分割字符串 单个相加 再拼起来
二、都扩大到整数 ,再相加 ,再除回去

@DaisyX-BOT
Copy link

原因: JS采用 IEEE 754 双精度版本 (64),并且只要采用 IEEE 754 语言的都有该问题
计算机十进制采用的是二进制表示,
0.1转化为二进制是: 0.00011001100110011001100110011001100110011001100110011010
0.2转化为二进制是:0.0011001100110011001100110011001100110011001100110011010

0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111

image
转换之后结果正好为:0.30000000000000004

解决办法:
原生: parseFloat((0.1 + 0.2).toFixed(10))

ES6: 在es6中,Number有个新的属性EPSILON,在计算机科学技术里面,这个单词的意思为极小值

0.3 - (0.1 + 0.2 ) < Number.EPSILON // true

@huangpingcode
Copy link

因为JavaScript的Number类型为双精度IEEE 754 64位浮点类型,以下答案参考MDN

Math.abs((0.1+0.2) - 0.3) < Number.EPSILON

@makejun168
Copy link

小数相加

为什么 0.1 + 0.2 !== 0.3,请描述原因并手写解决该问题的函数

JavaScript 实现数字相加的时候会将数字转化为二进制再进行运算
所以结果是
image

解决方法

思路是将数字转化为字符串然后再进行相加

function addition(a, b) {
    var aStr = a.toString(); // 先转化为字符串
    var bStr = b.toString();
    var arr1 = aStr.split('').reverse().map(Number).filter(item => !isNaN(item))
    var arr2 = bStr.split('').reverse().map(Number).filter(item => !isNaN(item)) // 反转数组 准备加法运算
    var maxLen = Math.max(arr1.length, arr2.length) // 然后比较两者长度
    var nextNum = 0, result = [];
    
    for (var i = 0; i < maxLen; i++) {
        var value = (arr1[i] || 0) + (arr2[i] || 0) + nextNum
        result.push(value % 10)
        nextNum = value > 9 ? 1 : 0 
    } 
    if (nextNum === 1) {
        result.push(nextNum)
    }
    return Number(result.reverse().join('').replace(/^0/, "0."))
}
console.log(addition(0.1, 0.2));

@muzishuiji
Copy link

JS采用 IEEE 754 双精度版本 (64), 0.1 + 0.2 会被计算机转成二进制,转换过程中发生了截取,导致计算后的结果再转成 十进制时发生了精度丢失.
计算机十进制采用的是二进制表示,
0.1转化为二进制是: 0.00011001100110011001100110011001100110011001100110011010
0.2转化为二进制是:0.0011001100110011001100110011001100110011001100110011010
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111

转换之后结果正好为:0.30000000000000004

解决方法:

原生:parseFloat((0.1 + 0.2).toFixed(10))

ES6: 在ES6 中,Number有个新的属性,Number.EPSILON 属性表示 1 与Number可表示的大于 1 的最小的浮点数之间的差值。

(0.1 + 0.2) - 0.3 < Number.EPSILON; // true

@Chrisyjs
Copy link

Chrisyjs commented Apr 7, 2021

原因:JS 采用 IEEE 754 双精度版本(64位),计算机二进制存储值,循环的数字被裁剪了,就会出现精度丢失的问题

解决方法:

  • toFixed:parseFloat((0.1 + 0.2).toFixed(1)) === 0.3 // true
  • 先乘后除:(0.1 * 10 + 0.2 * 10) / 10 === 0.3 // true
          function add(num1, num2) {
            const num1Digits = (num1.toString().split('.')[1] || '').length;
            const num2Digits = (num2.toString().split('.')[1] || '').length;
            const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
            return (num1 * baseNum + num2 * baseNum) / baseNum;
          }

@dorothyLee56
Copy link

dorothyLee56 commented Apr 7, 2021

计算机中的数字都是以二进制存储的,需要将数字先转为二进制,像0.1、0.2这样的数字在转换为二进制数的时候会出现无限循环。
而js存储数据的采用的IEEE 754 双精度版本(64位),能表示并进行精确算术运算的整数范围为:[-2^53-1,2^53-1]。
对超出这个长度的数字会进行0舍1入的操作。并且浮点数运算的二进制结果也会进行舍入操作。因此得到结果会准确。
解决方法:

  1. 先转换为整数计算结果,在转换为小数
  2. (0.1 + 0.2 - 0.3) < Number.EPSILON
    参考资料

@chen870370470
Copy link

原因

在JS中使用Number类型表示数字(整数和浮点数),遵循 IEEE-754 标准,通过64位二进制值来表示一个数字。JS中的数值是十进制的,但是存储到计算机底层以及进行运算的时候,都是先转换为二进制,再进行运算,再转换成十进制。如果数字转换成二进制会存在循环,裁剪后运算会导致精度缺失。

解决方式

1.将数字转成整数计算,再转换为小数
2.Math.js 、decimal.js、big.js等
3.(0.1 + 0.2 - 0.3) < Number.EPSILON

const queryDigits = function queryDigits(num) {
    num += '';
    let [, char = ''] = num.split('.');
    return char.length;
};
const plus = function plus(num1, num2) {
    num1 = +num1;
    num2 = +num2;
    if (isNaN(num1) || isNaN(num2)) throw new TypeError('num1/num2 must be an number!');
    let num1Digits = queryDigits(num1),
        num2Digits = queryDigits(num2),
        baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
    return (num1 * baseNum + num2 * baseNum) / baseNum;
};
console.log(plus(0.1, 0.2));

@tony0511
Copy link

tony0511 commented Apr 7, 2021

原因在于64位双精度问题

处理方式自己整理了下

const mathFuns = {
  // 加法
  add(num1, num2) {
    let r1, r2, m;
    try { r1 = num1.toString().split('.')[1].length; } catch (e) { r1 = 0; }
    try { r2 = num2.toString().split('.')[1].length; } catch (e) { r2 = 0; }
    m = Math.pow(10, Math.max(r1, r2));
    return (Math.round(num1 * m) + Math.round(num2 * m)) / m;
  },
  // 减法 
  sub(num1, num2) {
    return mathFuns.add(num1, -num2);
  },
  // 乘法
  mul(num1, num2) {
    let m = 0;
    const s1 = num1.toString(), s2 = num2.toString();
    try { m += s1.split('.')[1].length; } catch (e) {}
    try { m += s2.split('.')[1].length; } catch (e) {}
    return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m);
  },
  // 除法
  div(num1, num2) {
    let t1 = 0, t2 = 0, r1, r2;
    try { t1 += num1.toString().split('.')[1].length; } catch (e) {}
    try { t2 += num2.toString().split('.')[1].length; } catch (e) {}
    r1 = Number(num1.toString().replace('.', ''));
    r2 = Number(num2.toString().replace('.', ''));
    if (t1 > t2) r2 = r2 * Math.pow(10, t1 - t2);
    if (t2 > t1) r1 = r1 * Math.pow(10, t2 - t1);
    return r1 / r2;
  },
};

@Leexuetao
Copy link

原因:1)、JavaScript中数字的存储机制:采用的是IEEE754 双精度64位浮点数

双精度(64位)浮点数的结构:(s) * (2 ^ e) * ( m )

s: sign 符号位: 1bit

e: exponent 指数位: 11bit

m: mantissa 尾数位: 52bit

排列规则为:符号位S(1位,0为正数,1为负数) + 阶码E(8位)  + 尾数M(52位)

2)、0.1和0.2都会先转成二进制再相加,相加之后再转成十进制

0.1转成二进制为
0. 0 0011 0011 0011 ....循环
0.2转成二进制为
0. 0011 0011 0011 ....循环

然后用IEEE754 双精度64位浮点数
0.1=> m = 1.1 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010
e= -4

0.2=> m = 1.1 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010
e= -3

0.1 + 0.2 //(先变成相同的指数再相加)
// 指数不一致时,一般是往右移,因为即使右边溢出了,损失的精度远远小于左移时的溢出

0.1=> m = 0. 11 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010
e= -3

0.2=> m = 1.1 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010
e= -3

0.1 + 0.2 =
m = 10. 011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 1(52位)
e= -3

   m = 1. 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 1(53位)
   e= -2
   // 最终取52位,保留成偶数

最后转换成十进制为:0.30000000000000004

解决方法
function numTofixed(num) {
if (typeof num == 'number') {
num = parseFloat(num.toFixed(10))
}
return num;
}
numTofixed(0.1+0.2)

1 similar comment
@Leexuetao
Copy link

原因:1)、JavaScript中数字的存储机制:采用的是IEEE754 双精度64位浮点数

双精度(64位)浮点数的结构:(s) * (2 ^ e) * ( m )

s: sign 符号位: 1bit

e: exponent 指数位: 11bit

m: mantissa 尾数位: 52bit

排列规则为:符号位S(1位,0为正数,1为负数) + 阶码E(8位)  + 尾数M(52位)

2)、0.1和0.2都会先转成二进制再相加,相加之后再转成十进制

0.1转成二进制为
0. 0 0011 0011 0011 ....循环
0.2转成二进制为
0. 0011 0011 0011 ....循环

然后用IEEE754 双精度64位浮点数
0.1=> m = 1.1 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010
e= -4

0.2=> m = 1.1 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010
e= -3

0.1 + 0.2 //(先变成相同的指数再相加)
// 指数不一致时,一般是往右移,因为即使右边溢出了,损失的精度远远小于左移时的溢出

0.1=> m = 0. 11 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010
e= -3

0.2=> m = 1.1 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010
e= -3

0.1 + 0.2 =
m = 10. 011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 1(52位)
e= -3

   m = 1. 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 1(53位)
   e= -2
   // 最终取52位,保留成偶数

最后转换成十进制为:0.30000000000000004

解决方法
function numTofixed(num) {
if (typeof num == 'number') {
num = parseFloat(num.toFixed(10))
}
return num;
}
numTofixed(0.1+0.2)

@yiyu66
Copy link

yiyu66 commented Apr 7, 2021

JS使用IEEE 754 双精度,采用二进制表示
使用parseFloat或者Math.abs((0.1+0.2) - 0.3) < Number.EPSILON

@liuestc
Copy link

liuestc commented Apr 8, 2021

keyword: IEEE754 、二进制、精度损失
how to solve?

(0.1+0.2-0.3)<Number.EPSILON
//or

toFixed截取多少位

parseFloat(num.toFixed(10))

@lucas270
Copy link

原因:js在计算时会转换成二进制去运算,这换算过程出现了精度丢失

(0.1100+0.2100)/100

@KieSun KieSun closed this as completed Sep 16, 2021
@kiritosan
Copy link

原因:js中小数运算会先将小数转成64位二进制数再进行运算,这样就会导致再转换的时候发生数据的截取导致精度的丢失,导致运算后的结果与预期不符。

解决:parseFloat((0.1+0.2).toFixed(2)) 保留两位小数,有零的话去掉多余的0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests