大数和浮点数在JavaScript中的表示方式

huangapple go评论86阅读模式
英文:

Big numbers and floating point numbers in JavaScript

问题

我有这段Java代码:

nextDouble = 0.2515933907977884;

long numerator = (long) (nextDouble * (1L << 53));

我想要在JavaScript中生成与此行在Java中产生的相同输出。

nextDouble = 0.2515933907977884;
const numerator = nextDouble * (1 << 53);

有人有办法在JavaScript中复制一个长整型(long)吗?我知道JavaScript中有BigInt,但问题是它不支持浮点数,所以我有点困惑该怎么做。有人知道任何能解决这个问题的有趣库吗?

提前谢谢!

英文:

I have this Java code:

nextDouble = 0.2515933907977884;

long numerator = (long) (nextDouble * (1L &lt;&lt; 53));

I would like to be able to produce the same output this line produces in Java but within JavaScript.

nextDouble = 0.2515933907977884;
const numerator = nextDouble * (1 &lt;&lt; 53);

Has anybody got an idea for how to replicate a long within JavaScript ? I know there is BigInt in JavaScript but thing is, it doesn't support floating point numbers, so I am a bit stuck on what to do. Does anybody know any interesting libraries, that could solve this issue ?

Thank you in advance !

答案1

得分: 2

问题是在JavaScript中,1&lt;&lt;53是什么意思。它并不像你想的那样执行。

在控制台中尝试一下:

1&lt;&lt;30
1073741824
// 好的...看起来不错

1&lt;&lt;31
&gt; -2147483648
// 一个负数..什么??

1&lt;&lt;32
&gt; 1
// 什么????????

1&lt;&lt;53
&gt; 2097152
// 这对我来说太低了

1&lt;&lt;21
&gt; 2097152
// 为什么和1&lt;&lt;53一样???

JavaScript中的数字被强制转换为双精度浮点数,对双精度浮点数进行位移操作是完全荒谬的。尽管如此,JavaScript仍然允许您这样做,因为嗯,JavaScript。当您在JavaScript中做傻事时,JavaScript会给您傻乎乎的答案,从这个角度来看,JavaScript相当没有价值 - 程序员在做一些无法合理解释为具有任何特定含义的疯狂事情时,应该得到明确的错误提示,而不是胡乱猜测。但这就是JavaScript的特性。处理这种疯狂行为的通常方法是不要问JavaScript一些愚蠢的问题,因为它会给你愚蠢的答案。比如 1&lt;&lt;32 得到的是1*。

也许你会想,“但是将1左移53位有什么疯狂的?” - 答案是,位移操作在双精度浮点数上没有意义,因此被解释为:“您希望模拟32位有符号整数的行为”,这正是JavaScript所做的,特别是包括奇怪的Java/C风格,即右侧数字只有底部的5位有效。换句话说,&lt;&lt;32&lt;&lt;0 是一样的 - 毕竟,32的底部5位是0。换句话说,将右侧的数字除以32,舍弃结果,保留余数(模运算)。32除以32的余数是0。53除以32的余数是21,这就是为什么在JavaScript中 1&lt;&lt;53 的结果是 2097152。

因此,在JavaScript中,您的代码实际上是将双精度浮点数乘以2的21次方,或者 theDouble * 2097152,而在Java中,它是将双精度浮点数乘以2的53次方,或者 theDouble * 9007199254740992

很明显,你得到的答案是非常__不同__的。

修复方法似乎很简单。1&lt;&lt;53 可能看起来像是表示 2的53次方用于位表示的1后面跟着53个零 的好方法,但在JavaScript中,语法__并不是这样工作__的。您不能在这个目的上使用这种语法。直接使用 9007199254740992 试试。

var d = 0.2515933907977884;
const n = d * 9007199254740992;
n
&gt; 2266151802091599

所以这个方法有效。

如果您想从值53中得出值9007199254740992:

Math.pow(2, 53)
&gt; 9007199254740992

请注意,您正在接近灾难的边缘。标准的IEEE双精度浮点数在指数中使用了53位,因此您正处在非常边缘的地方。很快,您将进入“x + 1”等于“x”的领域,因为可表示数字之间的间隔大于1。如果您想远离这个危险的边缘,您需要开始使用BigInt。

*) 这是规范行为。但是你肯定同意这非常令人惊讶。你认识多少人对JavaScript的&lt;&lt;进行位移操作会被规范为将其转换为32位有符号整数,然后取右侧的数字并对32取模,然后进行操作,最后再转换回双精度浮点数呢?

英文:

The problem is what 1&lt;&lt;53 does in javascript. It doesn't do what you think it does.

Here, try this in the console:

1&lt;&lt;30
1073741824
// okay.. looks good

1&lt;&lt;31
&gt; -2147483648
// a negative num.. wha??

1&lt;&lt;32
&gt; 1
// WHAA????????

1&lt;&lt;53
&gt; 2097152
// That seems VERY low to me

1&lt;&lt;21
&gt; 2097152
// How is that the same? as 1&lt;&lt;53??

numbers in javascript are forced into being doubles, and doing bit shifts on a double is utterly ridiculous. Javascript nevertheless lets you, because, well, javascript. When you do silly things in javascript, javascript will give you silly answers, and in that way javascript is rather worthless - programmers doing crazy stuff that cannot reasonably be interpreted as having any particular meaning should be answered with a clear error and not a wild stab in the dark. But that's just how javascript is. The usual way to deal with this crazy behaviour is to never ask javascript silly things, as it will give you silly answers. Such as 1&lt;&lt;32 being 1*.

You may be wondering 'but how is asking to bit shift 1 by 53 positions 'crazy'? - and the answer is, that bit shifts, given that they make no sense on doubles, are interpreted as: "You wish to emulate 32-bit signed int behaviour", and that is exactly what javascript does, notably including the weirdish java/C-ism that only the bottom 5 bits of the number on the RHS count. In other words, &lt;&lt;32 is the same thing as &lt;&lt;0 - after all, the bottom 5 bits of 32.. is 0. Said differently, take the right hand side number, divide it by 32, toss the result, keep the remainder ('modulo'). 32 divided by 32 leaves a remainder of 0. 53 divided by 32 leaves a remainder of 21, and that's why 1&lt;&lt;53 in javascript prints 2097152.

So, in javascript your code is effectively doing the double multiplied by 2 to the 21st power, or theDouble * 2097152, whereas in java it is doing the double multiplied by 2 to the 53rd power, or theDouble * 9007199254740992.

Rather obviously then your answers are wildly different.

The fix seems trivial. 1&lt;&lt;53 may look like a nice way to convey the notion of 2 to 53rd power or in bits, a 1 bit, followed by 53 zeroes, but as syntax goes it just does not work that way in javascript. You can't use that syntax for this purpose. Try literally 9007199254740992.

var d = 0.2515933907977884;
const n = d * 9007199254740992;
n
&gt; 2266151802091599

so that works.

If you have a need to derive the value 9007199254740992 from the value 53:

Math.pow(2, 53)
&gt; 9007199254740992

note that you're dancing on the edge of disaster here. standard IEEE doubles use 53 bits for the exponent, so you're at the very edges. Soon you'll get into the territory where 'x + 1' is equal to 'x' because the gap between representable numbers is larger than 1. You'll need to get cracking on BigInt if you want to move away from the precipice.

*) It is specced behaviour. But surely you agree this is highly surprising. How many people do you know that just know off-hand that javascripts &lt;&lt; is specced to convert to a 32-bit signed integer, take the RHS and modulo 32 it, and then operate, and then convert back to double afterwards?

huangapple
  • 本文由 发表于 2020年9月22日 22:00:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/64011400.html