将多个值编码为单个long

时间:2018-03-29 14:23:46

标签: c# bit-manipulation encode bitwise-operators bit-shift

我试图在C#中将以下数字编码为单个64位长:

  • 高达2048(年)=> 12位
  • 最多16(月)=> 6位
  • 最多32(天)=> 7位
  • 最多32(小时)=> 7位
  • 总计63位的其他值(包括之前的值)

结果数字的结构应包含具有固定位大小的编码值,因此我可以轻松解码它(例如,从数字的第13位开始,从6位开始解码,因为前12位是为年保留的。) / p>

到目前为止,我没有经常使用按位操作,所以我有点挣扎,我想出了以下代码来执行此操作:

private static long AddBitwise(long to, int toAdd, int startPosition, int maxLengthInBits)
{
    var filledNumber = (1 >> maxLengthInBits) | toAdd;
    to |= filledNumber << startPosition;
    return to;
}

然后我这样称它来编码所有值:

private static long CalculateMaxBinary(int a, int b, int c, int d, int e, int f, int g, int h, int i)
{
    long result = 0;

    result = AddBitwise(result, a, 52, 12);
    result = AddBitwise(result, b, 47, 5);
    result = AddBitwise(result, c, 41, 6);
    result = AddBitwise(result, d, 35, 6);
    result = AddBitwise(result, e, 28, 7);
    result = AddBitwise(result, f, 23, 5);
    result = AddBitwise(result, g, 17, 6);
    result = AddBitwise(result, h, 9, 8);
    result = AddBitwise(result, i, 1, 8);

    return result;
}

但是我必须做错事或采取完全错误的方法,有人能给我一个如何在指定位置设置特定固定位数的例子吗?

3 个答案:

答案 0 :(得分:2)

我一般认为编码这样的值是不可取的。 .NET Designers经历了很多麻烦,所以我们永远不必处理“它在内存中的表现如何”,而且从我的原生C ++体验中我认为我们应该避免这个级别。

通常,用一堆8位,16位和32位整数构造一个结构就足够了,并保留它。你只能通过将9个7位值压缩到63位而不是只有9个8位值(72位)来节省那么多。 通常它会花费你更多的代码可读性,内存和CPU时间进行解码和编码,然后这个最小的节省(9位)是值得的。

如果每个实例的9位甚至对内存限制都很重要,那么听起来你应该改变设计的其他部分,这样你就不需要那么多的内存/许多实例开始了。您很可能陷入XY Problem,您认为此优化是解决方案。

答案 1 :(得分:1)

这绝对是可能的,而且实际上比您尝试的更容易。你需要注意在左移之前将数字设为long ,否则比特可能会丢失或者数字可能会移动错误的数量(请记住,移位计数是取模数左操作数的大小)或两者。

但你需要的是投射到long,向左移动,或者将其转换成你拥有的东西:

private static long AddBitwise(long to, int toAdd, int pos)
{
    return to | ((long)toAdd << pos);
}

此处不需要此尺寸,但您可以使用尺寸自动更新位置:

private static long prependBitwise(long to, int value, ref int pos, int size)
{
    pos -= size;
    return to | ((long)value << pos);
}

像这样使用:

int pos = 64;
long packed = 0;
packed = prependBitwise(packed, year, ref pos, 12);
packed = prependBitwise(packed, month, ref pos, 4);
packed = prependBitwise(packed, day, ref pos, 5);
// etc

顺便提一下,你的大部分位域都是超大的。要表示[1..31]中的日期数,只需要5位。 32不是真正的一天,但即使它仍然只需要6位,而不是7位。

还有其他策略,例如仍然包装&#34;顶部字段到底部字段&#34;但随着我们前进,将填充的长左移动,最终将底部字段留在底部位(而不是顶部位中的顶部字段),只是一个不同的对齐方式:

private static long prependBitwise(long to, int value, int size)
{
    return (to << size) | value;
}

这有点好,因为它不需要那个丑陋的by-ref pos,并且只有3个参数就像第一个版本一样(但是它没有让意外重叠字段的弱点) ),如果没有完全填充,它往往会使得到的包装长得更小(更低的值)。

注意填充顶部位(又名&#34;符号位&#34;)很好,你可以像普通位一样对待它,但是当解码时必须要小心提取最顶部位域的方法将使其具有负值。切换到ulong会阻止此类意外。

答案 2 :(得分:0)

如果你真的必须这样做,你可以采取一些纠结:

public static ulong SetBits(ulong value, int bitOffsetInOutput, int inputBits, int inputBitCount)
{
    ulong outputMask = (1ul << bitOffsetInOutput+inputBitCount) - (1ul << bitOffsetInOutput);
    ulong inputMask  = (1ul << inputBitCount) - 1ul;

    return (value & ~outputMask) | (((ulong)inputBits & inputMask) << bitOffsetInOutput);
}

这使您可以将给定64位数的指定位范围设置为从32位数中获取的指定位数,而不会影响64位数中的任何其他位。

请注意,这不会设置最高位 - 但是您说只有63位数据,所以这不应该是一个问题。

测试程序:

using System;

namespace Demo
{
    class Program
    {
        static void Main()
        {
            ulong value = 0;

            value = SetBits(value, 8, 0b111111111111, 6);

            // Expected result = 11111100000000
            Console.WriteLine(Convert.ToString((long)value, 2)); 

            value = SetBits(value, 17, 0xFFFF, 7);

            // Expected result = 111111100011111100000000
            Console.WriteLine(Convert.ToString((long)value, 2));

            value = SetBits(value, 19, 0, 2);

            // Expected result = 111001100011111100000000
            Console.WriteLine(Convert.ToString((long)value, 2));
        }

        public static ulong SetBits(ulong value, int bitOffsetInOutput, int inputBits, int inputBitCount)
        {
            ulong outputMask = (1ul << bitOffsetInOutput+inputBitCount) - (1ul << bitOffsetInOutput);
            ulong inputMask  = (1ul << inputBitCount) - 1ul;

            return (value & ~outputMask) | (((ulong)inputBits & inputMask) << bitOffsetInOutput);
        }
    }
}