我正在尝试编写一个函数来返回一个正整数的位数,小于(2 ^ 53)-1的Javascript限制。但是我遇到了精度问题,并希望避免使用大整数库。
方法1:
function bitSize(num)
{
return Math.floor( Math.log(num) / Math.log(2) ) + 1;
}
Pass: bitSize( Math.pow(2, 16) -1 ) = 16
Pass: bitSize( Math.pow(2, 16) ) = 17
Fail (Should be 48): bitSize( Math.pow(2, 48) -1 ) = 49
Pass: bitSize( Math.pow(2, 48) ) = 49
方法2:
function bitSize(num)
{
var count = 0;
while(num > 0)
{
num = num >> 1;
count++;
}
return count;
}
Pass: bitSize( Math.pow(2, 16) -1 ) = 16
Pass: bitSize( Math.pow(2, 16) ) = 17
Fail (Should be 48): bitSize( Math.pow(2, 48) -1 ) = 1
Fail (Should be 49): bitSize( Math.pow(2, 48) ) = 1
我认为这两种方法都不会出现精确问题。
任何人都可以提出一种替代方法,该方法适用于0 - >之间的数字。 2 ^ 53-1
感谢。
答案 0 :(得分:10)
按位操作只能在Javascript中可靠地运行,最多可达32位“整数”。引用The Complete JavaScript Number Reference:
按位操作有点像黑客攻击 在Javascript中。因为所有号码都在 Javascript是浮点数,而且 按位运算符只能工作 整数,Javascript做了一点 在幕后魔术制作它 出现按位运算 应用于32位有符号整数。
具体来说,Javascript需要 你正在努力的数字 数字的整数部分。它 然后将整数转换为最大值 数字代表的位数, 最多31位(符号为1位)。所以 0将创建一个两位数(1为 符号,1位为0),同样为1 会产生两位。 2会创造 一个3位数字,4将创建一个4位 号码等......
认识到你是很重要的 不能保证32位数字 实例运行不是零应该, 在理论上,将0转换为4,294,967,295, 相反,它将返回-1为两 原因,首先是所有 数字是用Javascript签名的 “不”总是反转标志,并且 第二个Javascript无法生成更多 比数字零和一点多 不是零变成一个。因此〜0 = -1。
所以Javascript中的按位符号都在上升 到32位。
正如Anurag所说,在这种情况下你应该简单地使用内置的num.toString(2)
,它输出一个最小长度的ASCII '1'
和'0'
s字符串,你可以只需要长度。
答案 1 :(得分:9)
你可以这样做:
function bitSize(num) {
return num.toString(2).length;
}
Number
的{{3}}方法将基数作为可选参数。
以下是一些toString()
。适用于Chrome,Safari,Opera和Firefox。不能访问IE,抱歉。
答案 2 :(得分:2)
ES6标准带来Math.clz32()
,因此对于32位范围内的数字,您可以写:
num_bits = 32 - Math.clz32(0b1000000);
在此代码段中测试:
var input = document.querySelector('input');
var bits = document.querySelector('#bits');
input.oninput = function() {
var num = parseInt(input.value);
bits.textContent = 32 - Math.clz32(num);
};
Number (Decimal): <input type="text"><br>
Number of bits: <span id="bits"></span>
在MDN's documentation of Math.clz32上提供了一个polyfill:
Math.imul = Math.imul || function(a, b) {
var ah = (a >>> 16) & 0xffff;
var al = a & 0xffff;
var bh = (b >>> 16) & 0xffff;
var bl = b & 0xffff;
// the shift by 0 fixes the sign on the high part
// the final |0 converts the unsigned value into a signed value
return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0)|0);
};
Math.clz32 = Math.clz32 || (function () {
'use strict';
var table = [
32, 31, 0, 16, 0, 30, 3, 0, 15, 0, 0, 0, 29, 10, 2, 0,
0, 0, 12, 14, 21, 0, 19, 0, 0, 28, 0, 25, 0, 9, 1, 0,
17, 0, 4, , 0, 0, 11, 0, 13, 22, 20, 0, 26, 0, 0, 18,
5, 0, 0, 23, 0, 27, 0, 6, 0, 24, 7, 0, 8, 0, 0, 0]
// Adapted from an algorithm in Hacker's Delight, page 103.
return function (x) {
// Note that the variables may not necessarily be the same.
// 1. Let n = ToUint32(x).
var v = Number(x) >>> 0
// 2. Let p be the number of leading zero bits in the 32-bit binary representation of n.
v |= v >>> 1
v |= v >>> 2
v |= v >>> 4
v |= v >>> 8
v |= v >>> 16
v = table[Math.imul(v, 0x06EB14F9) >>> 26]
// Return p.
return v
}
})();
document.body.textContent = 32 - Math.clz32(0b1000000);
答案 3 :(得分:1)
使用位改变的相应边界构建查找表。您可以仅为较大的值执行此操作,并通过对数仍然执行较小的值。它似乎通常与浮点相关,因为我也可以在PowerShell中重现它。
答案 4 :(得分:1)
晚会,但我想赞成trincot的32位answer更快,更简单,更好支持全53位方法。
以下两个示例将只读取/解析并返回float的exponent-value。
对于支持ES6 ArrayBuffer
和DataView
的现代浏览器(不关心平台的endianness,但遗留兼容性较低):
reqBits4Int = (function(d){ 'use strict';
return function(n){
return n && ( // return 0 if n===0 (instead of 1)
d.setFloat64(0, n), // OR set float to buffer and
d.getUint16(0) - 16352 >> 4 & 2047 // return float's parsed exponent
); // Offset 1022<<4=16352; 0b1111111=2047
}; // DataView methods default to big-endian
})( new DataView(new ArrayBuffer(8)) ); // Pass a Buffer's DataView to IFFE
支持Float64Array
和Uint16Array
但没有DataView
的略微较旧的浏览器示例,因此字节顺序取决于平台,此代码段假定为“标准”小端:
reqBits4Int = (function(){ 'use strict';
var f = new Float64Array(1), // one 8-byte element (64bits)
w = new Uint16Array(f.buffer, 6); // one 2-byte element (start at byte 6)
return function(n){
return ( f[0] = n // update float array buffer element
// and return 0 if n===0 (instead of 1)
) && w[0] - 16352 >> 4 & 2047; // or return float's parsed exponent
}; //NOTE : assumes little-endian platform
})(); //end IIFE
上述两个版本都返回一个正整数Number
,表示保存作为参数传递的整数Number
所需的最大位。
它们在[-2 53 ,2 53 ] 的整个范围内无误差地工作,并且超出此范围,覆盖整个浮动范围的正值浮点指数除了,其中输入Number
上已经发生舍入(例如2 55 -1) 存储为2 < sup> 55 (显然, 等于56位)。
解释IEEE 754浮点格式实际上超出了本答案的范围,但是对于那些基本了解的人我已经在下面包含了折叠片段,其中包含表格形式的计算,从中可以看到/解释逻辑:实际上我们只是抓住浮动的第一个字(16个MSB包含符号和全指数),减去4位移位偏移和zeroing_offset(保存2个操作),移位和屏蔽结果作为输出。 0
在函数中得到了处理。
<xmp> PREVIEW of data to be generated:
Float value : S_exponent__MMMM : # -(1022<<4)#### : # >> 4 : & 2047 : Result integer
-9 : 1100000000100010 : 1000000001000010 : 100000000100 : 100 : 4
-8 : 1100000000100000 : 1000000001000000 : 100000000100 : 100 : 4
-7 : 1100000000011100 : 1000000000111100 : 100000000011 : 11 : 3
-6 : 1100000000011000 : 1000000000111000 : 100000000011 : 11 : 3
-5 : 1100000000010100 : 1000000000110100 : 100000000011 : 11 : 3
-4 : 1100000000010000 : 1000000000110000 : 100000000011 : 11 : 3
-3 : 1100000000001000 : 1000000000101000 : 100000000010 : 10 : 2
-2 : 1100000000000000 : 1000000000100000 : 100000000010 : 10 : 2
-1 : 1011111111110000 : 1000000000010000 : 100000000001 : 1 : 1
0 : 0 : -11111111100000 : -1111111110 : 10000000010 : 1026
1 : 11111111110000 : 10000 : 1 : 1 : 1
2 : 100000000000000 : 100000 : 10 : 10 : 2
3 : 100000000001000 : 101000 : 10 : 10 : 2
4 : 100000000010000 : 110000 : 11 : 11 : 3
5 : 100000000010100 : 110100 : 11 : 11 : 3
6 : 100000000011000 : 111000 : 11 : 11 : 3
7 : 100000000011100 : 111100 : 11 : 11 : 3
8 : 100000000100000 : 1000000 : 100 : 100 : 4
9 : 100000000100010 : 1000010 : 100 : 100 : 4
after 18 the generated list will only show 3 values before and after the exponent change
</xmp>
<script> //requires dataview, if not available see post how to rewrite or just examine example above
firstFloatWord = (function(d){
return function(n){
return d.setFloat64(0, n), d.getUint16(0);
};
})( new DataView(new ArrayBuffer(8)) );
function pad(v, p){
return (' '+v).slice(-p);
}
for( var r= '', i=-18, c=0, t
; i < 18014398509481984
; i= i>17 && c>=5
? (r+='\n', c=0, (i-2)*2-3)
: (++c, i+1)
){
r+= pad(i, 19) + ' : '
+ pad((t=firstFloatWord(i)).toString(2), 17) + ' : '
+ pad((t-=16352).toString(2), 17) + ' : '
+ pad((t>>=4).toString(2), 13) + ' : '
+ pad((t&=2047).toString(2), 12) + ' : '
+ pad(t, 5) + '\n';
}
document.body.innerHTML='<xmp> Float value : S_exponent__MMMM : # -(1022<<4)#### : '
+ ' # >> 4 : & 2047 : Result integer\n' + r + '</xmp>';
</script>
后备选项:
ECMAScript(javascript)让实施者可以自由选择如何实现该语言。因此,在野外的x浏览器世界中,不仅需要处理舍入差异,还需要处理不同的算法,例如Math.log
和Math.log2
等。
正如您已经注意到的那样(您的方法1),log2
(polyfill)可能不起作用的常见示例是2 48 (= 49,当它被覆盖时是一对多),但那是不是唯一的例子
例如,某些版本的chrome甚至会将显着更小的数字搞砸,例如:Math.log2(8) = 2.9999999999999996
(当被覆盖时减少一个)。
在此stackoverflow中了解更多相关内容Q / A:Math.log2 precision has changed in Chrome
这意味着我们无法知道何时落地或细化我们的对数结果(或者在舍入之前我们已经离开时很容易预测)。
因此,您可以计算在循环中小于1之前将输入数除以2的频率(非常类似于您计算的32位移位方法2):
function reqBits4Int(n){ for(var c=0; n>=1; ++c, n/=2); return c }
但这是相当蛮力(也可能让你陷入四舍五入的问题)。你可以通过一些划分和征服来改善这一点,当你在它时,展开循环:
function reqBits4Int(n){ 'use strict';
var r= 4294967295 < n ? ( n= n/4294967296 >>> 0, 32 ) : 0 ;
65535 < n && ( n >>>= 16, r+= 16 );
255 < n && ( n >>= 8, r+= 8 );
15 < n && ( n >>= 4, r+= 4 );
// 3 < n && ( n >>= 2, r+= 2 );
// 1 < n && ( n >>= 1, r+= 1 );
// return r + n;
// OR using a lookup number instead of the lines comented out above
// position: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 = 16 values
// binary: 11 11 11 11 11 11 11 11 10 10 10 10 01 01 00 00 = 4294945360 dec
return (n && (4294945360 >>> n * 2 & 3) + 1) + r;
}
格式化旨在帮助理解算法。它会变得非常坚韧!
这有一个正整数范围[0,2 53 ] 没有错误(最多2 64 具有相同的可预测舍入 - '错误“)。
或者,您可以尝试其他一些(重复,对于大于32位的输入值)bithacks。
与上面的计算片段相比,最简单和最短(但可能更慢)是将数字字符串化并计算得到的字符串长度,如Anurag的answer,基本上是:return n && n.toString(2).length;
(假设浏览器)可以得到最多(至少)53位的结果。