0.1+0.2不等于0.3?

 


一、JavaScript中的数字如何在内存中表示?

1.IEEE 754标准中双精度64位浮点数

  JavaScript只有一种数据类型Number来表示数字,它是双精度64位浮点类型,遵循 IEEE 754  标准。格式表示如下图:

 

 

 

  规约形式浮点数表示(默认尾数高位为1.):

 

 

 

  • 符号位(sign):1位,'0'表示正数,'1'表示负数;
  • 指数位(exponent):11位,为了方便表示,这里引入了偏置值 bias (双精度64位浮点数的偏置值为1023),这样就可以使用一个非负整数表示负数到正数范围内的所有指数值了,即:实际指数值 = 指数位数值e - 偏置值bias 。11位的指数位可以表示的数值范围是:0~2047(211-1),由于指数位全0 (0-1023=-1023) 和全1 (2047-1023=1024) 是用来表示特定数值,因此指数可用范围为[-1022,+1023]。特殊数值如下:
    • 当e=000000000002时,若尾数位全0则表示'符号0',根据符号位可分为+0或-0;若尾数位不全为0则表示Subnormal number(非规约形式浮点数)。
    • 当e=111111111112时,若尾数位全0则用来表示'无穷∞';若尾数位不全为0则表示NaN。
  • 尾数位(fraction):52位,但因为浮点数表示法的规范中默认格式为 1.b51b50...b0,也即默认尾数部分最高位为1,因此52位能够全部用来表示小数点后的数位,因此数值范围为:0~253-1。

   

补充:

  非规约形式浮点数(Subnormal Number),一般是某个数字相当接近零时才会使用非规约型式来表示,用来解决填补绝对值意义下最小规格数与零的距离(博主没有往下深究- -!)。 IEEE 754标准规定:非规约形式的浮点数的指数偏移值比规约形式的浮点数的指数偏移值小1。其表示形式如下:

 

 

 

2.关于Number的"最大"/"最小"值与安全整数范围

  Number.MAX_VALUE是JavaScript中所能表示的最大浮点数,大于Max_Value的值代表'Infinity'。其计算结果如下:

0 11111111110 11111111111111111111111111111111111111111111111111112 ≙ 7FEF FFFF FFFF FFFF16 ≙ +21023 × (1 + (1 − 2−52)) ≈ 1.7976931348623157 × 10308 

 

  

  值得注意的是,Number.MIN_VALUE是所能表示的最小浮点数(也可以理解为最小正值,最接近0的正值),而不是最小数值。

console.log(Math.min(0,Number.MIN_VALUE)); //最小值是0
console.log(Number.MIN_VALUE);//5e-324

  

  而Number规定的“安全”整数范围就是 -(2^53-1) ~2^53-1,所谓“安全”就是指在这个数值范围内的算术运算是可以保证精度的,不会出现精度丢失的问题(这里个人推测是因为不使用指数位进行整数的存储,可以保证精度。至于为什么使用52位作为尾数位存储整数却能达到53次方呢?因为在浮点数表示法的规范中默认第一位是1,且这个第一位可以省略,因此就节省了一位,这样就可以达到53次方)。而超出这个范围的算术运算则会出现精度丢失的情况,如下图所示:

 

let num = 2**53;
console.log(num);//9007199254740992
console.log(num+1); //9007199254740992,出现精度丢失

 

 

 

二、回到0.1+0.2!==0.3

  由于计算机中数字都是以二进制存储的,我们首先进行十进制到二进制的转换:整数部分除2取余,逆序排列;小数部分乘2取整,顺序排列。计算过程如下:

 

 

  可见,0.1的二进制形式为:0.0 0011 0011 ...(0011循环) 

 

  同理,0.2的二进制形式为:0.0011 0011 ...(0011循环) 

 

  而按照(一)中所描述的IEEE 754标准中双精度64位浮点数表示形式,0.1和0.2在计算机存储形式分别为:

 

复制代码
//0.1
e = -4;
f = 1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 ;(52位小数) 后面使用了浮点数的舍入规则使得位数刚好为52位
完整形式:0 01111111011 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010;

//0.2
e = -3;
f = 1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 ;
完整形式:0 01111111100 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010;
复制代码

 

  下面计算0.1+0.2,首先两者指数不一致,需要右移进行对齐。对于溢出的位,需要遵循浮点数的舍入规则进行舍入(这里博主没有去细究,可以参考IEEE 754 - Wikipedia

 

 

  

 

  可以验证一下: 

复制代码
let val = 0.1+0.2;
console.log(val.toString(2)); //0.0100110011001100110011001100110011001100110011001101
console.log(val);//0.30000000000000004

//使用等比数列的求和公式进行验证
let str = ((0.1+0.2).toString(2)).split('.')[1];//取小数点后面的位数
let base = 2;
let ans = 0;
for(let i=0;i<str.length;i++){
    ans += parseInt(str.charAt(i))*(1/base);
    base *= 2;
}
console.log(ans);//0.30000000000000004
复制代码

 

  可以看到计算结果为0.30000000000000004 !== 0.3 。 

 

参考资料

  1. IEEE 754 - Wikipedia
  2. Double-precision floating-point format - Wikipedia
  3. Number - JavaScript | MDN (mozilla.org)
posted @   ˙鲨鱼辣椒ゝ  阅读(57)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示