【问题标题】:Encode multiple values to single long将多个值编码为单个 long
【发布时间】:2018-09-08 13:26:42
【问题描述】:

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

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

结果数字的结构应该包含具有固定位大小的编码值,以便我可以轻松地对其进行解码(例如,通过从数字的第 13 位开始取 6 位来解码月份,因为前 12 位是为年份保留的)。

到目前为止,我并没有经常使用按位运算,所以我有点挣扎,我想出了以下代码来做到这一点:

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

但是我必须做错事或采取完全错误的方法,有人可以告诉我如何将指定位置的特定固定位数设置为数字的示例吗?

【问题讨论】:

  • 不得不指出DateTime一个无符号的64位值。解码为already provided in the class
  • 是的,但是我只需要对 DateTime 的一部分进行编码(例如,没有秒数和最多到 2048 年的年份),并且我需要对与 DateTime 无关的其他值进行编码
  • 1 &gt;&gt; maxLengthInBits -- 这是什么意思?
  • @LadislavBohm 为什么?您最终只会得到一个缓慢且容易出错的类,它可能比简单地使用您想要的字段创建一个结构需要更多的内存。那是因为所有这些编码/解码都比简单地访问字段要慢得多。生成的临时值会造成内存浪费。
  • @PanagiotisKanavos 我在 Christopher 的回答下在评论中说明了我的原因。

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


【解决方案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。

还有其他策略,例如仍然打包“顶部字段到底部字段”,但随着我们继续将打包的长向左移动,最终将底部字段留在底部位中(而不是顶部位中的顶部字段) ,只是不同的对齐方式:

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

这有点好,因为它不需要那个丑陋的 by-ref pos,并且像第一个版本一样只有 3 个参数(但没有它的弱点,即意外指定重叠字段),而且如果没有完全填充,它往往会使生成的打包的 long 更小(更低的值)。

请注意,填充最高位(也称为“符号位”)很好,您可以将其视为普通位,但在解码时必须小心,因为某些方法可以提取最高位位域将使其具有负值。切换到ulong 可以避免此类意外。

【讨论】:

  • 感谢这个详细的答案,它实际上是在回答我的问题,尽管最终我可能会使用不同于将值编码为 long 的方法。我终于对按位运算以及如何正确安全地处理它们有了更好的了解,谢谢!
【解决方案2】:

我通常认为对这样的值进行编码是不可取的。 .NET 设计人员遇到了很多麻烦,所以我们永远不必处理“它在内存中是如何表示的”,根据我的原生 C++ 经验,我认为我们应该避免这个级别。

一般来说,用一堆 8 位、16 位和 32 位整数组成一个结构就足够了。您只能通过将 9 个 7 位值压缩为 63 位而不是仅具有 9 个 8 位值(72 位)来节省那么多。 通常,解码和编码会花费您更多的代码可读性、内存和 CPU 时间,然后这种最小的节省(9 位)是值得的。

如果每个实例 9 位甚至适用于内存限制,听起来您应该更改设计的其他部分,这样您就不需要那么多内存/那么多实例。您很有可能陷入XY Problem,您认为这种优化是解决方案。

【讨论】:

  • DateTime 将日期和时间存储为单个刻度值。这已经被解码以返回月、年、日等
  • 我正在考虑这种方法,因为我将处理数据库中的大量数据,并且我需要能够确定这些记录之间的唯一性。这个 long 的单个值本身会有一个唯一的索引。我相信(希望)与将索引分布在 DB 中的 4 列(一些 varchar)相比,有性能优势
  • @Christopher 你对 XY 问题可能是正确的,我会从更广泛的角度重新考虑这个问题,因为这种方法可能不是我想要的。谢谢
  • @LadislavBohm:有专门的“HashValue()”函数(msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx)和接口。还有一个专门的 HashList 集合。听起来这就是你想要的。
  • @LadislavBohm:通常我会尽可能地对数据库查询进行过滤等。大多数 DBMS 使用高效的 B+ 树作为索引,从而显着加快搜索/过滤速度。但对于其他任何事情,您都可以使用 HashCode 函数进行“预过滤”。这是一个很好的“预检查” unqiueness。如果hte 哈希不匹配,它们可以不相等。如果它们确实匹配,您可以对每个字段进行详细(并且更深入)检查以进行验证。
【解决方案3】:

如果你真的必须这样做,你可以求助于一些小玩意儿:

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);
        }
    }
}

【讨论】:

    猜你喜欢
    • 2021-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多