提取 Javascript 数字的指数和尾数
有没有一种相当快速的方法可以从 JavaScript 中的数字中提取指数和尾数?
AFAIK 没有办法获取 Javascript 中数字后面的位,这让我觉得我正在研究因式分解问题:找到 m
和 n
这样对于给定的k
,2^n * m = k
。由于整数分解属于 NP 范畴,我只能假设这将是一个相当困难的问题。
我正在实现一个用于生成 Javascript 的 GHC 插件,并且需要实现 decodeFloat_Int#
和 decodeDouble_2Int#
原始操作;我想我可以重写基础库的部分,这些部分使用该操作以其他方式执行任何操作(这应该不会太难,因为所有数字类型都以 Number 作为其表示形式,)但是它'如果我不需要的话就好了。
有没有办法以一种甚至远程性能的方式做到这一点,通过一些黑暗的Javascript巫毒,聪明的数学或其他一些手段,或者我应该埋头苦干并在基础库中使用?
编辑 根据 ruakh 和 Louis Wasserman 的出色回答,我提出了以下实现,它似乎运行得很好:
function getNumberParts(x) {
if(isNaN(x)) {
return {mantissa: -6755399441055744, exponent: 972};
}
var sig = x > 0 ? 1 : -1;
if(!isFinite(x)) {
return {mantissa: sig * 4503599627370496, exponent: 972};
}
x = Math.abs(x);
var exp = Math.floor(Math.log(x)*Math.LOG2E)-52;
var man = x/Math.pow(2, exp);
return {mantissa: sig*man, exponent: exp};
}
Is there a reasonably fast way to extract the exponent and mantissa from a Number in Javascript?
AFAIK there's no way to get at the bits behind a Number in Javascript, which makes it seem to me that I'm looking at a factorization problem: finding m
and n
such that 2^n * m = k
for a given k
. Since integer factorization is in NP, I can only assume that this would be a fairly hard problem.
I'm implementing a GHC plugin for generating Javascript and need to implement the decodeFloat_Int#
and decodeDouble_2Int#
primitive operations; I guess I could just rewrite the parts of the base library that uses the operation to do wahtever they're doing in some other way (which shouldn't be too hard since all numeric types have Number as their representation anyway,) but it'd be nice if I didn't have to.
Is there any way to do this in an even remotely performant way, by some dark Javascript voodoo, clever mathematics or some other means, or should I just buckle down and have at the base library?
EDIT
Based on ruakh's and Louis Wasserman's excellent answers, I came up with the following implementation, which seems to work well enough:
function getNumberParts(x) {
if(isNaN(x)) {
return {mantissa: -6755399441055744, exponent: 972};
}
var sig = x > 0 ? 1 : -1;
if(!isFinite(x)) {
return {mantissa: sig * 4503599627370496, exponent: 972};
}
x = Math.abs(x);
var exp = Math.floor(Math.log(x)*Math.LOG2E)-52;
var man = x/Math.pow(2, exp);
return {mantissa: sig*man, exponent: exp};
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
使用新的
ArrayBuffer
访问数组,实际上可以通过从Uint8Array
。如果您需要更快的速度,请考虑重用Float64Array
。我还创建了一些测试用例。
0
失败,因为 2^-1023 有另一种表示形式。Using the new
ArrayBuffer
access arrays, it is actually possible to retrieve the exact mantissa and exponent, by extracting them from theUint8Array
. If you need more speed, consider reusing theFloat64Array
.I've also created some test cases.
0
fails, since there is another representation for 2^-1023.ECMAScript 没有定义任何直接的方法来做到这一点;但就其价值而言,这并不是与质因数分解相同意义上的“因式分解问题”。
理论上,您可以通过首先处理符号,然后使用二叉树方法(或对数)来查找指数,最后除以 2 的相关幂以获得尾数来快速完成您想要的操作;但不幸的是,在实践中实现这一点可能有点棘手(对于特殊情况,例如非规范化数字)。我建议您通读 ECMAScript 规范的第 6.1.6.1 节:数字类型,以了解您必须处理的情况。
ECMAScript doesn't define any straightforward way to do this; but for what it's worth, this isn't a "factorization problem" in the same sense as prime factorization.
What you want can theoretically be done very quickly by first handling the sign, then using a binary-tree approach (or logarithm) to find the exponent, and lastly dividing by the relevant power of two to get the mantissa; but unfortunately, it can be somewhat tricky to implement this in practice (what with special cases such as denormalized numbers). I recommend you read through section 6.1.6.1: The Number Type of the ECMAScript specification to get a sense of what cases you'll have to handle.
为此,整数分解几乎没有必要。
指数基本上就是以 2 为底的对数的底数,这并不难计算。
以下代码通过了 QuickCheck 测试,以及对无穷大和负无穷大的测试:
我使用
quickCheck (\ d ->decodeFloat d ==decode d)
对其进行了测试,并在正无穷大上单独对其进行了显式测试和负无穷大。这里使用的唯一原始操作是左移、双乘、双除以及无穷大和 NaN 测试,据我所知,Javascript 支持这些操作。
Integer factorization is nowhere near necessary for this.
The exponent is basically going to be the floor of the base-2 logarithm, which isn't that hard to compute.
The following code passes QuickCheck tests, as well as tests on infinity and negative infinity:
I tested it using
quickCheck (\ d -> decodeFloat d == decode d)
, and explicitly tested it separately on positive and negative infinities.The only primitive operations used here are left-shift, double multiplication, double division, and infinity and NaN testing, which Javascript supports to the best of my knowledge.
对于以 10 为基数的情况,您可以使用以下方法在数组中获取尾数和指数:
如果您不关心结果部分是否是文本而不是数字,并且如果指数部分前面可能有一个加号,那么您可以跳过 < code>parseFloat 和
parseInt
步骤,直接从数组 [0] 和 [1] 处获取部分。For base 10 you can get mantissa and exponent in an array with
If you don't care if the result parts are text instead of numbers and if there might be a plus sign on the front of the exponent part, then you can skip the
parseFloat
andparseInt
steps and just take the parts directly from the array at [0] and [1].虽然我喜欢接受的解决方案,但使用它在任意基础上工作会重新引入由
Math.log
和Math.pow
引起的所有错误。下面是任何基数的一个小实现: x = mantisse * b^exponentNaN 和 Infinite 情况可以轻松添加。如果
+0
和-0
之间的区别很重要:While I liked the accepted solution, using it to work on arbitrary base re-introduced all of the errors caused by
Math.log
andMath.pow
. Here is a small implementation for any base:x = mantisse * b^exponent
The NaN and Infinite cases can easily be added. If the distinction between
+0
and-0
is important:下面来获取指数怎么样?:
1000 将导致 exp = 3
What about following to get the exponent?:
1000 will result in exp = 3
我迟到了十年,但我对文案对此问题的回答并不满意。虽然它非常好,但事实是尾数被 JavaScript 解释为浮点数(准确地说是双精度),其所有数字都在小数部分,这有点违背了获取原始尾数值。我们希望获得原始尾数值,就像它是一个整数一样。
所以,我决定解决这个问题。尾数有 52 位,前面有一个隐含的 1(表示非正规数)。因此,诀窍是将 52 加到指数上,这会将小数部分中的所有尾数位移出,并减去 252 以消除隐式前导 1。此外,由于我们已经覆盖了在读取尾数之前先计算指数,我们永远不会得到非正规数,所以我们不必为此烦恼。
如下:
在 原始副本的答案中,前两个字节被设置为 0x3ff_ 因为这会清除符号位并设置0x3ff 作为指数(_ 是尾数的一部分,无论它是什么,都会被保留)。 0x3ff 是十进制的 1023。添加 52,则得到 0x433_。
由于 double 的未缩放值将是一个 53 位的整数(由于隐式前导 1),因此直接处理会太长 1 位,因此我简单地使用 BigInt 来避免溢出在去掉多余的部分之前。如果由于某种原因您不能或不想使用 BigInt,还有其他方法可以处理它。
此外,虽然有些人可能更喜欢获取原始指数,但其他人可能希望获取带有偏差解释的指数值。为了让双方都满意,我添加了指数的两种解释。
另外,我将变量重命名为
asDouble
和asBytes
而不是float
和bytes
因为我认为这更好地反映了预期的行为。否则,这只是表面上的改变。由于我们可以通过这种方式分解 double ,因此最好使用相反的操作来组装它。如下:
理想情况下,我会在尾数上使用位移位,但由于 JavaScript 在位移之前会减少到 32 位,因此会丢弃 20 位来破坏它。因此,我使用除法和 Math.floor 来模拟位移。
最后,测试所有这些:
请看这里,所有这些都在一起并正在运行:
I am ten years late to the party, but I was not satisfied with copy's answer on this question. Although it is very good, the fact that the mantissa is interpreted by JavaScript as a floating point number (a
double
to be precise) on its own with all its digits in the fractional part kinda defeats the purpose of getting the raw mantissa value. We would like to get the raw mantissa value as if it was an integer.So, I decided to fix that. There are 52 bits in the mantissa, and a implicit 1 before it for non-denormal numbers. So, the trick is to add 52 to the exponent, which shifts all mantissa's bits out of the fractional part and subtract 252 to get rid of the implicit leading 1. Further, since we are already overwriting the exponent before reading the mantissa, we'll never get a denormal number, so we don't have to bother with that.
Here it is:
In the original copy's answer, the first two bytes were set to 0x3ff_ because this clear the sign bit and set 0x3ff as the exponent (the _ is part of the mantissa and is preserved whatever it was). 0x3ff is 1023 in decimal. Adding 52 to that, it gives 0x433_ instead.
Since the unscaled value of the double would be an integer with 53 bits due to the implicit leading 1, it would be 1 bit too long to handle directly, so I am briefly using a
BigInt
to avoid overflowing it before getting rid of that extra bit. There are other ways to handle it if for some reason you can't or don't want to useBigInt
.Further, while some people might prefer to get the raw exponent as the bits are, others might want to get the exponent value as it is interpreted with the bias. To please both sides, I add both interpretations of the exponent.
Also, I renamed the variables to
asDouble
andasBytes
instead offloat
andbytes
because I think that this better reflects the intended behavior. Otherwise, it is just a cosmetic change.And since we can disassemble a
double
in this way, it would be desirable to have the opposite operation to assemble it. Here it is:Ideally, I would use bitshifts on the mantissa, but since JavaScript reduces to 32 bits before shifting, that would break it by discarding 20 bits. So, I simulate a bitshift using division and
Math.floor
instead.Finally, to test all of that:
See here is it all together and running:
我的 Haskell 不存在。这是 JavaScript 中的解决方案。正如其他人所指出的,关键是计算二进制对数以获得指数。
来自 http:// /blog.coolmuse.com/2012/06/21/getting-the-exponent-and-mantissa-from-a-javascript-number/
My Haskell is non-existent. Here's a solution in JavaScript. As others have noted, the key is to calculate the binary logarithm to get the exponent.
From http://blog.coolmuse.com/2012/06/21/getting-the-exponent-and-mantissa-from-a-javascript-number/
如果您只需要尾数长度,
If you only need mantissa length,