计算Int32中的前导零

时间:2012-05-03 20:59:24

标签: c# int

如何计算Int32中的前导零?所以我想要做的是写一个函数,如果我的输入Int32是2,则返回30,因为在二进制中我有0000000000000010。

14 个答案:

答案 0 :(得分:16)

我们以数字10为例。它可以用二进制表示如下:

    00000000000000000000000000010100

首先,我们通过右移位和按位“或”自身“抹掉”低位位置上的最高位。

    00000000000000000000000000010100
 or 00000000000000000000000000001010 (right-shifted by 1)
 is 00000000000000000000000000011100

然后

    00000000000000000000000000011100
 or 00000000000000000000000000000111 (right-shifted by 2)
 is 00000000000000000000000000011111

这里,因为它是一个很小的数字,我们已经完成了这项工作,但是通过重复这个过程直到16位的右移,我们可以确保对于任何32位数字,我们已经设置了全部从0到原始数字的MSB的比特为1。

现在,如果我们在“模糊”结果中计算1的数量,我们可以简单地从32减去它,并且我们留下原始值中前导零的数量。

我们如何计算整数中的设置位数? This page有一个神奇的算法就是这样做(“一个可变精度的SWAR算法来执行树木缩减”......如果你得到它,你就比我聪明!) ,转换为C#如下:

int PopulationCount(int x)
{
    x -= ((x >> 1) & 0x55555555);
    x = (((x >> 2) & 0x33333333) + (x & 0x33333333));
    x = (((x >> 4) + x) & 0x0f0f0f0f);
    x += (x >> 8);
    x += (x >> 16);
    return (x & 0x0000003f);
}

通过上面的“涂抹”方法内联这个方法,我们可以生成一个非常快速,无循环且无条件的方法来计算整数的前导零。

int LeadingZeros(int x)
{
    const int numIntBits = sizeof(int) * 8; //compile time constant
    //do the smearing
    x |= x >> 1; 
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;
    //count the ones
    x -= x >> 1 & 0x55555555;
    x = (x >> 2 & 0x33333333) + (x & 0x33333333);
    x = (x >> 4) + x & 0x0f0f0f0f;
    x += x >> 8;
    x += x >> 16;
    return numIntBits - (x & 0x0000003f); //subtract # of 1s from 32
}

答案 1 :(得分:5)

试试这个:

static int LeadingZeros(int value)
{
   // Shift right unsigned to work with both positive and negative values
   var uValue = (uint) value;
   int leadingZeros = 0;
   while(uValue != 0)
   {
      uValue = uValue >> 1;
      leadingZeros++;
   }

   return (32 - leadingZeros);
}

答案 2 :(得分:3)

这里有一些复杂的答案。怎么样?

private int LeadingZeroes(int value)
{
    return (32 - (Convert.ToString(value, 2).Length));
}

虽然现在我猜测可能存在一些负数的问题以及这种类型的解决方案。

答案 3 :(得分:3)

如果您想混合汇编代码以获得最佳性能。以下是您在C#中的表现方式。

首先使支持代码成为可能:

using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using static System.Runtime.CompilerServices.MethodImplOptions;

/// <summary> Gets the position of the right most non-zero bit in a UInt32.  </summary>
[MethodImpl(AggressiveInlining)] public static int BitScanForward(UInt32 mask) => _BitScanForward32(mask);

/// <summary> Gets the position of the left most non-zero bit in a UInt32.  </summary>
[MethodImpl(AggressiveInlining)] public static int BitScanReverse(UInt32 mask) => _BitScanReverse32(mask);


[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

private static TDelegate GenerateX86Function<TDelegate>(byte[] x86AssemblyBytes) {
    const uint PAGE_EXECUTE_READWRITE = 0x40;
    const uint ALLOCATIONTYPE_MEM_COMMIT = 0x1000;
    const uint ALLOCATIONTYPE_RESERVE = 0x2000;
    const uint ALLOCATIONTYPE = ALLOCATIONTYPE_MEM_COMMIT | ALLOCATIONTYPE_RESERVE;
    IntPtr buf = VirtualAlloc(IntPtr.Zero, (uint)x86AssemblyBytes.Length, ALLOCATIONTYPE, PAGE_EXECUTE_READWRITE);
    Marshal.Copy(x86AssemblyBytes, 0, buf, x86AssemblyBytes.Length);
    return (TDelegate)(object)Marshal.GetDelegateForFunctionPointer(buf, typeof(TDelegate));
}

然后这是生成函数的程序集:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate Int32 BitScan32Delegate(UInt32 inValue);

private static BitScan32Delegate _BitScanForward32 = (new Func<BitScan32Delegate>(() => { //IIFE   
   BitScan32Delegate del = null;
   if(IntPtr.Size == 4){
      del = GenerateX86Function<BitScan32Delegate>(
         x86AssemblyBytes: new byte[20] {
         //10: int32_t BitScanForward(uint32_t inValue) {
            0x51,                                       //51                   push        ecx  
            //11:    unsigned long i;
            //12:    return _BitScanForward(&i, inValue) ? i : -1;
            0x0F, 0xBC, 0x44, 0x24, 0x08,               //0F BC 44 24 08       bsf         eax,dword ptr [esp+8] 
            0x89, 0x04, 0x24,                           //89 04 24             mov         dword ptr [esp],eax 
            0xB8, 0xFF, 0xFF, 0xFF, 0xFF,               //B8 FF FF FF FF       mov         eax,-1               
            0x0F, 0x45, 0x04, 0x24,                     //0F 45 04 24          cmovne      eax,dword ptr [esp]
            0x59,                                       //59                   pop         ecx 
            //13: }
            0xC3,                                       //C3                   ret  
      });
   } else if(IntPtr.Size == 8){
      del = GenerateX86Function<BitScan32Delegate>( 
         //This code also will work for UInt64 bitscan.
         // But I have it limited to UInt32 via the delegate because UInt64 bitscan would fail in a 32bit dotnet process.  
            x86AssemblyBytes: new byte[13] {
            //15:    unsigned long i;
            //16:    return _BitScanForward64(&i, inValue) ? i : -1; 
            0x48, 0x0F, 0xBC, 0xD1,            //48 0F BC D1          bsf         rdx,rcx
            0xB8, 0xFF, 0xFF, 0xFF, 0xFF,      //B8 FF FF FF FF       mov         eax,-1 
            0x0F, 0x45, 0xC2,                  //0F 45 C2             cmovne      eax,edx  
            //17: }
            0xC3                              //C3                   ret 
         });
   }
   return del;
}))();


private static BitScan32Delegate _BitScanReverse32 = (new Func<BitScan32Delegate>(() => { //IIFE   
   BitScan32Delegate del = null;
   if(IntPtr.Size == 4){
      del = GenerateX86Function<BitScan32Delegate>(
         x86AssemblyBytes: new byte[20] {
            //18: int BitScanReverse(unsigned int inValue) {
            0x51,                                       //51                   push        ecx  
            //19:    unsigned long i;
            //20:    return _BitScanReverse(&i, inValue) ? i : -1;
            0x0F, 0xBD, 0x44, 0x24, 0x08,               //0F BD 44 24 08       bsr         eax,dword ptr [esp+8] 
            0x89, 0x04, 0x24,                           //89 04 24             mov         dword ptr [esp],eax 
            0xB8, 0xFF, 0xFF, 0xFF, 0xFF,               //B8 FF FF FF FF       mov         eax,-1  
            0x0F, 0x45, 0x04, 0x24,                     //0F 45 04 24          cmovne      eax,dword ptr [esp]  
            0x59,                                       //59                   pop         ecx 
            //21: }
            0xC3,                                       //C3                   ret  
      });
   } else if(IntPtr.Size == 8){
      del = GenerateX86Function<BitScan32Delegate>( 
         //This code also will work for UInt64 bitscan.
         // But I have it limited to UInt32 via the delegate because UInt64 bitscan would fail in a 32bit dotnet process. 
            x86AssemblyBytes: new byte[13] {
            //23:    unsigned long i;
            //24:    return _BitScanReverse64(&i, inValue) ? i : -1; 
            0x48, 0x0F, 0xBD, 0xD1,            //48 0F BD D1          bsr         rdx,rcx 
            0xB8, 0xFF, 0xFF, 0xFF, 0xFF,      //B8 FF FF FF FF       mov         eax,-1
            0x0F, 0x45, 0xC2,                  //0F 45 C2             cmovne      eax,edx  
            //25: }
            0xC3                              //C3                   ret 
         });
   }
   return del;
}))();

为了生成程序集,我启动了一个新的VC ++项目,创建了我想要的函数,然后进入Debug - &gt; Windows - &gt; Disassembly。对于编译器选项,我禁用内联,启用内在函数,优先快速代码,省略帧指针,禁用安全检查和SDL检查。代码是:

#include "stdafx.h"
#include <intrin.h>  

#pragma intrinsic(_BitScanForward)  
#pragma intrinsic(_BitScanReverse) 
#pragma intrinsic(_BitScanForward64)  
#pragma intrinsic(_BitScanReverse64) 


__declspec(noinline) int _cdecl BitScanForward(unsigned int inValue) {
    unsigned long i;
    return _BitScanForward(&i, inValue) ? i : -1; 
}
__declspec(noinline) int _cdecl BitScanForward64(unsigned long long inValue) {
    unsigned long i;
    return _BitScanForward64(&i, inValue) ? i : -1;
}
__declspec(noinline) int _cdecl BitScanReverse(unsigned int inValue) {
    unsigned long i;
    return _BitScanReverse(&i, inValue) ? i : -1; 
}
__declspec(noinline) int _cdecl BitScanReverse64(unsigned long long inValue) {
    unsigned long i;
    return _BitScanReverse64(&i, inValue) ? i : -1;
}

答案 4 :(得分:2)

.NET Core 3.0中有BitOperations.LeadingZeroCount()BitOperations.TrailingZeroCount(),它们直接映射到x86的LZCNT / BSR和TZCNT / BSF。因此,它们将成为最有效的解决方案

答案 5 :(得分:1)

private int GetIntegerOffsetLength(int value)
{
    return (32 - (Convert.ToString(value, 2).Length);
}

答案 6 :(得分:1)

来吧,伙计们,不要再问“为什么要做这个或那个”。如果可以或只是继续,请回答。 计数前导零是许多问题(例如压缩算法)中的常见任务。 甚至还有专用于此的x86硬件指令(clz,bsr)。遗憾的是,您无法在C#中使用这些硬件指令,因为尚不支持内在函数。 我想,转换成字符串是一个笑话。

int的二进制表示具有非常明确的边界。 实际上,在C#int中只是Int32的别名。正如它的namge所暗示的那样,“Int32”总是32位有符号整数,即使你为x64编译你的项目。

你不需要一些特殊的voodo魔法来计算前导零: 这是一个简单的数学解决方案:

这里“x”是你的int(Int32):

int LeadingZeros = (int)(32 - Math.Log((double)x + 1, 2d));
LeadingZeros += (int)((x - (0x80000000u >> LeadingZeros)) >> 31);

编辑:抱歉,我已审核并更正了我的公式。 由于双算术的精度误差,结果可能如此 少数边界情况不正确。所以它仍然需要一些“伏都魔术”。 第二行处理这些情况并产生正确的结果。

答案 7 :(得分:1)

如果只想模拟Lzcnt指令,则可以这样操作(零值给出32):

int Lzcnt(uint value)
{
    //Math.Log(0, 2) is -Infinity, cast to int is 0x80000000
    int i=(int)Math.Log(value, 2);
    return 31-(i&int.MaxValue)-(i>>31);
}

如果您需要知道存储特定值需要多少位,最好是:

1+((int)Math.Log(value, 2)&int.MaxValue)

上面给出了一个零值–因为实际上需要一位来存储零。

但是这些仅适用于uint,而不适用于ulong。 Double(这是Log方法的参数)没有足够的精度来存储ulong到最低有效位,因此(double)0xFFFFFFFFFFFFFF(double)0x100000000000000是无法区分的。

但是,有了.Net Core 3.0,我们终于有了最新,最好的Lzcnt指令。因此,如果仅System.Runtime.Intrinsics.X86.Lzcnt.IsSupported(乌龙语为System.Runtime.Intrinsics.X86.Lzcnt.X64.IsSupported),则可以使用System.Runtime.Intrinsics.X86.Lzcnt.LeadingZeroCount(value)(乌龙语为System.Runtime.Intrinsics.X86.Lzcnt.X64.LeadingZeroCount(value))。

答案 8 :(得分:0)

在C:

unsigned int
lzc(register unsigned int x)
{
        x |= (x >> 1);
        x |= (x >> 2);
        x |= (x >> 4);
        x |= (x >> 8);
        x |= (x >> 16);
        return(WORDBITS - ones(x));
}

(来自http://aggregate.org/MAGIC/#Leading Zero Count

翻译成C#对读者来说是一项微不足道的练习。

修改

我给出链接的原因是,我不需要复制以下内容(再次在C中):

#define WORDBITS 32

unsigned int
ones(unsigned int x)
{
        /* 32-bit recursive reduction using SWAR...
       but first step is mapping 2-bit values
       into sum of 2 1-bit values in sneaky way
    */
        x -= ((x >> 1) & 0x55555555);
        x = (((x >> 2) & 0x33333333) + (x & 0x33333333));
        x = (((x >> 4) + x) & 0x0f0f0f0f);
        x += (x >> 8);
        x += (x >> 16);
        return(x & 0x0000003f);
}

答案 9 :(得分:0)

32 - Convert.ToString(2,2).Count()

答案 10 :(得分:0)

计数前导零/查找第一组/位扫描反向是在OS和其他低级编程中需要的常见事情大多数硬件支持clz以形成单周期指令。大多数c / c ++编译器都有一个内在的编译器。

http://en.wikipedia.org/wiki/Find_first_set

大多数硬件和编译器也有计数尾随零,弹出计数/位数/计数,奇偶校验,bswap /翻转endien,以及其他几个夸张但非常有用的位操作。

答案 11 :(得分:0)

我认为最好的选择是spender's post above。但是,如果有人希望稍微提高性能,则可以使用以下方法。(注意:在我的计算机上,它仅比基准测试快2%)

这是通过将浮点数转换为int然后获取指数位来实现的。

    [StructLayout(LayoutKind.Explicit)]
    private struct ConverterStruct
    {
        [FieldOffset(0)] public int asInt;
        [FieldOffset(0)] public float asFloat;
    }

    public static int LeadingZeroCount(uint val)
    {
        ConverterStruct a;  a.asInt = 0; a.asFloat = val;
        return 30-((a.asInt >> 23 )) & 0x1F;
    }

这也可以扩展到Int64版本...

    [StructLayout(LayoutKind.Explicit)]
    private struct ConverterStruct2
    {
        [FieldOffset(0)] public ulong asLong;
        [FieldOffset(0)] public double asDouble;
    }

    // Same as Log2_SunsetQuest3 except
    public static int LeadingZeroCount(ulong val)
    {
        ConverterStruct2 a;  a.asLong = 0; a.asDouble = val;
        return 30-(int)((a.asLong >> 52)) & 0xFF;
    }

注意: 在浮点数中使用指数的想法来自SPWorley 3/22/2009。在生产代码上请谨慎使用,因为这在具有低字节序的架构上会失败。

以下是Floor-Log2的一些基准-几乎相同:https://github.com/SunsetQuest/Fast-Integer-Log2

答案 12 :(得分:-1)

使用预先计算的计数

可以获得最佳性能
public static class BitCounter
{
    private static readonly int[] _precomputed = new[]
        {
            0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
            4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
        };

    public static int CountOn(int value)
    {
        return _precomputed[value >> 24] +
               _precomputed[(value << 8) >> 24] +
               _precomputed[(value << 16) >> 24] +
               _precomputed[value & 0xFF];
    }

    public static int CountOff(int value)
    {
        return 32 - CountOn(value);
    }
}

答案 13 :(得分:-3)

整数没有前导零,也不支持32位数。话虽这么说,你应该能够通过将整数转换为字符串并检查长度来创建一个函数:

private int GetIntegerOffsetLength(int value)
{
    //change 32 to whatever your upper bound is
    return (32 - (value.ToString().Length + 1));
}