在C#中计算整数log2的最快方法是什么?

时间:2012-01-23 10:19:18

标签: c# algorithm math bit-manipulation

如何最有效地计算C#中整数(日志库2)所需的位数?例如:

int bits = 1 + log2(100);

=> bits == 7

6 个答案:

答案 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;
    }

注意:

  • SPWorley 3/22/2009启发了在浮点数中使用指数的想法。
  • 请谨慎使用生产代码,因为这可能会在字节序不小的体系结构上失败。

以下是一些基准:(此处的代码: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);