将多个值编码为单个 long
Encode multiple values to single long
我正在尝试在 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;
}
但是我一定是做错了什么或者采取了完全错误的方法,有人能告诉我如何将指定位置的特定固定位数设置为数字的示例吗?
我通常发现 non-adviseable 可以对这样的值进行编码。 .NET 设计人员经历了很多麻烦,因此我们永远不必处理 "how is it represented in memory" 并且根据我的本地 C++ 经验,我认为我们应该避免那个级别。
一般来说,用一堆 8 位、16 位和 32 位整数创建一个结构并保留它就足够了。您只能通过将 9 个 7 位值压缩为 63 位而不是仅具有 9 个 8 位值(72 位)来节省那么多。
通常它会花费您更多的代码可读性、内存和 CPU 时间去解码和编码,那么这个最小的节省(9 位)是值得的。
如果每个实例 9 位甚至考虑到内存限制,听起来您应该更改设计的其他部分,这样一开始就不需要那么多 memory/that 实例。您很可能陷入 XY Problem,您认为此优化是解决方案。
这绝对是可能的,而且实际上比您尝试过的更容易。您确实需要注意在 long
之前将数字设置为 left-shifting ,否则可能会丢失位或数字可能会移动错误的数量(请记住,移位计数取模左操作数的大小)或两者。
但您只需要转换为 long
,向左移动,然后将其转换为您拥有的内容:
private static long AddBitwise(long to, int toAdd, int pos)
{
return to | ((long)toAdd << pos);
}
此处不需要大小,但您可以 auto-update 使用大小的位置:
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 位。
还有其他策略,例如仍然打包 "top field to bottom field" 但在我们继续进行时将打包的 long 向左移动,最终将底部字段留在底部位中(而不是将顶部字段留在顶部位中) , 只是对齐方式不同:
private static long prependBitwise(long to, int value, int size)
{
return (to << size) | value;
}
这有点好,因为它不需要那么丑 by-ref pos
,并且像第一个版本一样只有 3 个参数(但没有允许指定重叠字段的弱点偶然),并且如果它没有完全填充,它也倾向于使生成的填充 long 更小(更低的值)。
请注意,填充最高位(又名 "sign bit")是可以的,您可以将其视为普通位,但在 解码 时必须小心,因为一些提取最顶层位域的方法将使它具有负值。切换到 ulong
可以避免此类意外。
如果你真的必须这样做,你可以求助于一些bit-twiddling:
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);
}
}
}
我正在尝试在 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;
}
但是我一定是做错了什么或者采取了完全错误的方法,有人能告诉我如何将指定位置的特定固定位数设置为数字的示例吗?
我通常发现 non-adviseable 可以对这样的值进行编码。 .NET 设计人员经历了很多麻烦,因此我们永远不必处理 "how is it represented in memory" 并且根据我的本地 C++ 经验,我认为我们应该避免那个级别。
一般来说,用一堆 8 位、16 位和 32 位整数创建一个结构并保留它就足够了。您只能通过将 9 个 7 位值压缩为 63 位而不是仅具有 9 个 8 位值(72 位)来节省那么多。 通常它会花费您更多的代码可读性、内存和 CPU 时间去解码和编码,那么这个最小的节省(9 位)是值得的。
如果每个实例 9 位甚至考虑到内存限制,听起来您应该更改设计的其他部分,这样一开始就不需要那么多 memory/that 实例。您很可能陷入 XY Problem,您认为此优化是解决方案。
这绝对是可能的,而且实际上比您尝试过的更容易。您确实需要注意在 long
之前将数字设置为 left-shifting ,否则可能会丢失位或数字可能会移动错误的数量(请记住,移位计数取模左操作数的大小)或两者。
但您只需要转换为 long
,向左移动,然后将其转换为您拥有的内容:
private static long AddBitwise(long to, int toAdd, int pos)
{
return to | ((long)toAdd << pos);
}
此处不需要大小,但您可以 auto-update 使用大小的位置:
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 位。
还有其他策略,例如仍然打包 "top field to bottom field" 但在我们继续进行时将打包的 long 向左移动,最终将底部字段留在底部位中(而不是将顶部字段留在顶部位中) , 只是对齐方式不同:
private static long prependBitwise(long to, int value, int size)
{
return (to << size) | value;
}
这有点好,因为它不需要那么丑 by-ref pos
,并且像第一个版本一样只有 3 个参数(但没有允许指定重叠字段的弱点偶然),并且如果它没有完全填充,它也倾向于使生成的填充 long 更小(更低的值)。
请注意,填充最高位(又名 "sign bit")是可以的,您可以将其视为普通位,但在 解码 时必须小心,因为一些提取最顶层位域的方法将使它具有负值。切换到 ulong
可以避免此类意外。
如果你真的必须这样做,你可以求助于一些bit-twiddling:
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);
}
}
}