如何最有效地计算C#中整数(日志库2)所需的位数?例如:
int bits = 1 + log2(100);
=> bits == 7
答案 0 :(得分:10)
对Guffa的答案略有改进......由于你添加到结果中的数量总是2的幂,使用位操作可以在某些体系结构上产生轻微的改进。此外,由于我们的上下文是位模式,因此使用十六进制可读性略高一些。在这种情况下,将算术移动2的幂是有用的。
int bits = 0;
if (n > 0xffff) {
n >>= 16;
bits = 0x10;
}
if (n > 0xff) {
n >>= 8;
bits |= 0x8;
}
if (n > 0xf) {
n >>= 4;
bits |= 0x4;
}
if (n > 0x3) {
n >>= 2;
bits |= 0x2;
}
if (n > 0x1) {
bits |= 0x1;
}
此外,应添加对n == 0的检查,因为上面的结果为0,Log(0)未定义(无论基数为何)。
在ARM程序集中,此算法生成非常紧凑的代码,因为可以使用条件指令消除比较后的分支,从而避免管道刷新。例如:
if (n > 0xff) {
n >>= 8;
bits |= 0x8;
}
变为(令R0 = n,R1 =位)
CMP R0, $0xff
MOVHI R0, R0, LSR $8
ORRHI R1, R1, $0x8
答案 1 :(得分:8)
您可以简单地计算在值为零之前删除位的次数:
int bits = 0;
while (n > 0) {
bits++;
n >>= 1;
}
对于大数字更有效,您可以先计算一组位数:
int bits = 0;
while (n > 255) {
bits += 8;
n >>= 8;
}
while (n > 0) {
bits++;
n >>= 1;
}
最有效的方法是使用Flynn1179建议的二进制步骤(赞成灵感:),但将循环扩展为硬编码检查。这至少是上述方法的两倍,但也有更多代码:
int bits = 0;
if (n > 32767) {
n >>= 16;
bits += 16;
}
if (n > 127) {
n >>= 8;
bits += 8;
}
if (n > 7) {
n >>= 4;
bits += 4;
}
if (n > 1) {
n >>= 2;
bits += 2;
}
if (n > 0) {
bits++;
}
答案 2 :(得分:4)
代码行或运行时执行速度方面的效率?
代码很简单:Math.log(n, 2)
。
运行时速度有点棘手,但您可以通过一种“二分搜索”来实现:
int bits = 1;
for (int b = 16; b >=1; b/=2)
{
int s = 1 << b;
if (n >= s) { n>>=b; bits+=b; }
}
我不是100%肯定我已经掌握了逻辑,但希望这个想法很明确。 .NET VM中可能存在一些开销,但原则上它应该更快。
for循环初始化器中的16
基于int所需的位数的一半。如果您正在使用多头,请在32等处开始
答案 3 :(得分:1)
这是在C#中计算整数的log2的最快方法...
[StructLayout(LayoutKind.Explicit)]
private struct ConverterStruct
{
[FieldOffset(0)] public int asInt;
[FieldOffset(0)] public float asFloat;
}
public static int Log2_SunsetQuest3(uint val)
{
ConverterStruct a; a.asInt = 0; a.asFloat = val;
return ((a.asInt >> 23 )+ 1) & 0x1F;
}
注意:
以下是一些基准:(此处的代码:https://github.com/SunsetQuest/Fast-Integer-Log2)
Function Time1 Time2 Errors Full-32-Bit Zero_Support
Log2_SunsetQuest3: 18 18 0 (Y) (N)
Log2_SunsetQuest4: 18 18 0 (Y) (N)
Log2_SPWorley: 18 18 0 (Y) (N)
MostSigBit_spender: 20 19 0 (Y) (Y)
Log2_HarrySvensson: 26 29 0 (Y) (N)
Log2_WiegleyJ: 27 23 0 (Y) (N)
Log2_DanielSig: 28 24 3125 (N) (N)
FloorLog2_Matthew_Watson: 29 25 0 (Y) (Y)
Log2_SunsetQuest1: 31 28 0 (Y) (Y)
HighestBitUnrolled_Kaz: 33 33 3125 (Y) (Y)
Log2_Flynn1179: 58 52 0 (Y) (N)
GetMsb_user3177100: 58 53 0 (Y) (N)
Log2floor_greggo: 89 101 0 (Y) (Y)
FloorLog2_SN17: 102 43 0 (Y) (N)
Log2_SunsetQuest2: 118 140 0 (Y) (Y)
Log2_Papayaved: 125 60 0 (Y) (N)
Msb_Protagonist: 136 118 0 (Y) (N)
Log2_SunsetQuest0: 206 202 0 (Y) (Y)
BitScanReverse2: 228 240 3125 (N) (Y)
UsingStrings_Rob: 2346 1494 0 (Y) (N)
Zero_Support = Supports Neg Return on Zero
Full-32-Bit = Supports full 32-bit (some just support 31 bits)
Time1 = benchmark for sizes up to 32-bit (same number tried for each size)
Time2 = benchmark for sizes up to 16-bit (for measuring perf with small numbers)
Benchmark notes: AMD Ryzen CPU, Release mode, no-debugger attached, .net core 2.1
我真的很喜欢spender in another post创建的那个。该程序没有潜在的体系结构问题,它还支持零(Zero),同时保持与SPWorley的float方法几乎相同的性能。
答案 4 :(得分:0)
直接转换为IEEE754 32位的结果大约在33554431后出现错误
public unsafe static int ByPtr754_32(ulong bits) {
var fp = (float)bits;
return (int)(((*(uint*)&fp >> 23) & 255) - 127);
}
转换为FP64,ILogB在53位之后有错误的结果,大约为18014398509481983
public unsafe static int ByPtr754_64(ulong bits) {
var fp = (double)bits;
return ((int)(*(ulong*)&fp >> 52) & 2047) - 1023;
}
public static int ByILogB(ulong bits) {
return Math.ILogB(bits);
}
lg和ln在大约47位之后出现错误,大约281474976710655
static readonly double ln2 = Math.Log(2.0), divLn2 = 1 / ln2;
public static int ByLn(ulong bits) {
//return (int)(Math.Log(bits) * divLn2);
return (int)(Math.Log(bits) / ln2);
}
lb在48位元后错误,大约562949953421311
public static int ByLog2(ulong bits) {
return (int)Math.Log2(bits);
}
二进制搜索非常慢。
public static int BySearch(ulong bits) {
if (0 == bits) {
return -1;
}
int min = 0, max = 64;
for (; ; ) {
int mid = (max + min) >> 1;
if (mid == min) {
break;
}
if (bits >> mid != 0) {
min = mid;
} else {
max = mid;
}
}
return min;
}
我的建议在这里: 快一个:
public unsafe static int ByPtr754_64(ulong bits) {
var fp = (double)bits;
return ((int)(*(ulong*)&fp >> 52) & 2047) - 1023;
}
const int Fp64Prec = 53;
static int[] CreateTableMix() {
var ret = new int[1 << (64 - Fp64Prec)];
for (int i = ret.Length; --i >= 0;) {
ret[i] = ByPtr754_64((uint)i) + Fp64Prec;
}
return ret;
}
static readonly int[] _TableMix = CreateTableMix();
public static int ByTableMix(ulong bits) {
int r;
return (r = _TableMix[bits >> Fp64Prec]) > 0 ? r : ByPtr754_64(bits);
}
简单的一个:
const int Fp64Prec = 53;
static int[] CreateTableMix() {
var ret = new int[1 << (64 - Fp64Prec)];
for (int i = ret.Length; --i >= 0;) {
ret[i] = ByPtr754_64((uint)i) + Fp64Prec;
}
return ret;
}
public static int By754Adj(ulong bits) {
const int lack = 64 - Fp64Prec;
int r;
return (r = ByPtr754_64(bits >> lack)) > 0 ? r+lack : ByPtr754_64(bits);
}
速度测试结果:
Search: 649830
ByTest: 535859
ByLog2: 560492
ByLn: 376675
ByLg: 392090
ByILog: 252594
Table16: 136847
ByUnion: 123453
754_64: 101358
754_32: 118379
TableMx: 106201
754Adj: 174889
答案 5 :(得分:0)
.NET Core 3.0中有BitOperations.LeadingZeroCount()。它映射到x86的LZCNT / BSR,因此它应该是最有效的解决方案
int bits = x == 0 ? 1 : 32 - BitOperations.LeadingZeroCount(x);