浮点数存储规则
发布于 7 年前 作者 leavesdrift 4052 次浏览 来自 问答

javascript 采取 64位 来存储浮点数,而因为 0.1 化为二进制的无限1100循环导致0.1+0.2!==0.3,存储规则为:

  • 1位存储符号
  • 11位存储指数部分
  • 52位存储尾数部分
    这里的指数,尾数代表什么呢?
(0.1).toString(2)
// '0.0001100110011001100110011001100110011001100110011001101'

(0.1).toString(2).split('.')[1].length
// 55,右边一共55位,并不是52位,那之前的尾数部分代表什么呢?

知乎原文:https://www.zhihu.com/question/28551135

5 回复

是这样的。 知乎上的答案只给出了一半答案。理论上来说在大多数用IEEE754来储存浮点数的语言都像你说的那样,1 sign field | 11 exponent field | 52 significand field. 但是这个’0.0001100110011001100110011001100110011001100110011001101’并不是原始的二进制浮点数,它也是一个“加工”过的二进制浮点数。也就是在toString的过程中rounded了。Javascript中并没有函数直接转换,而且转换过程中要注意是 BigEndian 还是 LittleEndian。大多数操作系统是LittleEndian.但是我发现网上convert的结果用的都是BigEndian. 所以我这里用的是BigEndian.

如果你想得到原始的浮点数,在Nodejs里面可以这样获取:

function numberToBuffer(num) {
  const buf = new Buffer(8)
  buf.writeDoubleBE(num, 0)
  return buf
}

function dec2bin(num){
    var number = num
    var result = []
    while(number >= 1 ){
        result.unshift(Math.floor(number%2))
        number = number/2
    }

    return result.join("")
}

const buf = numberToBuffer(0.1)

let str = ""
for (const value of buf.values()) {
  str += dec2bin(value)
}

str = "0000000000000000000000000000000000000000000000000000000000000000".substr(str.length) + str

let final = str

final = str.substr(0, 1) + " " + str.substr(1, 11) + " " + str.substr(12, str.length)

console.log(final)

这样0.1的原始二进制浮点数是0 01111111011 1001100110011001100110011001100110011001100110011010 这个例子可以在 https://repl.it/NZwk 运行。

由于Javascript中所有的数都是64位浮点数存储,而且又不能直接操作64位整数,所以以上操作比较复杂,如果用 Go 来 写就简单多了: https://play.golang.org/p/byu72vwbhw

这是 IEEE 754 双精度浮点数,不光是 JS,C,C++,Pyhton,Lua 一大片的语言都是这样的,很多前端没有系统学过计算机知识,正规学过的都不会有这些疑问,建议去看 CSAPP,书的最开始就是说的这个内容。

@anonymousnewguy 非常详细!谢谢你的回答!最终我还是看了下 go 语言这方面的写法,因为 javascript 操作确实有点麻烦。

@ianchn go 中并不等于0.3000000000xxxx4呀,不过谢谢你推荐的书籍,我会去详细了解下这方面的知识

链接 Go 也没什么优越的,只是帮你四舍五入了而已。在 JS 里你也可以自己做。

回到顶部