【问题标题】:Store one decimal digit存储一位十进制数字
【发布时间】:2017-07-08 11:58:28
【问题描述】:

我有一个与大量小整数(实际上是十进制数字)有关的问题。存储此类数据的空间有效方式是什么?

使用std::bitset<4> 存储一位十进制数字是个好主意吗?

【问题讨论】:

  • 什么为您的 bitset 提供 sizeof?
  • 你的号码都是个位数吗?你有多少号?您需要按位置访问数字吗?您愿意以性能换取空间效率吗? 显然,在进行这种优化之前,您必须弄清楚您需要什么! 在大多数情况下,使用适当的整数或字符类型可能就足够了。
  • 另外,您是否可以在数字序列或该序列的一部分中利用模式?某些数字是否可能比其他数字更频繁地出现?如果是这样,您也许可以使用某种编码,而不是直接表示。
  • 请记住,在使用任何数学、比较或 I/O 之前,您的程序需要花费执行时间打包和解包。您将以性能换取空间。
  • 除非这是一个空间受限的系统,否则我不推荐压缩。我建议使用外部存储而不是压缩。压缩会插入更多代码,并且随着代码的增多,可能会有更多的注入缺陷需要解决。

标签: c++ digits


【解决方案1】:

根据 它必须如何节省空间以及检索的效率应该如何,我看到了两种可能性:

  • 由于std::bitset<4> 的向量(据我所知)存储在未打包的设置中(每个位集都存储在一个 32 位或 64 位的内存字中),因此您可能至少应该使用打包表示,例如使用 64 位字存储 16 位:

    store (if the digit was not stored before):
    block |= digit << 4 * index
    load:
    digit = (block >> 4 * index) & 0xF
    reset:
    block &= ~(0xF << 4 * index);
    

这些 64 位字的向量 (uint64_t) 连同一些访问方法应该很容易实现。

  • 如果您的空间要求更严格,您可以使用例如尝试使用除法和模数将 3 位数字打包成 10 位(最多 1024),这会大大降低时间效率。此外,与 64 位字的对齐要困难得多,所以如果您需要获得最终 16% 的改进,我只建议您这样做,最多可以得到每位数 3.3 位。

【讨论】:

    【解决方案2】:

    如果您想要一种非常紧凑的方式,那么不,使用bitset&lt;4&gt; 是个坏主意,因为bitset&lt;4&gt; 将使用至少一个字节,而不是4 位。

    我建议使用std::vector&lt;std::uint32_t&gt;

    您可以在 uint32_t 中存储多个数字。两种常用方式:

    1. 每个数字使用 4 位,并使用位运算。这样,您可以在 4 个字节中存储 8 个数字。在这里,设置/获取操作非常快。效率:4bit/digit
    2. 使用 base 10 编码。 uint32_t 最大值为 256^4-1,可以在 4 个字节中存储 9 个数字。效率:3.55bit/digit。在这里,如果您需要设置/获取所有 9 位,那么它几乎与以前的版本一样快(因为除以 10 将由一个好的编译器优化,CPU 不会进行实际的除法)。如果你需要随机访问,那么 set/get 会比之前的版本慢(你可以通过libdivide 加快速度)。

    如果你使用uint64_t而不是uint32_t,那么第一种方式可以存储16位(相同的4bit/digit效率),第二种方式可以存储19位:3.36bit/digit efficieny,很漂亮接近理论最小值:~3.3219bit/digit

    【讨论】:

      【解决方案3】:

      使用 std::bitset 存储一位十进制数字是个好主意吗?

      是的,原则上这是个好主意。这是一个众所周知的优化,称为BCD 编码。

      (实际上是十进制数字)。存储此类数据的空间有效方式是什么?

      您可以使用占用字节的一个半字节来压缩十进制数字表示。数学也可以应用优化,而不是数字的 ASCII 表示等。

      std::bitset&lt;4&gt; 不能很好地压缩数据。
      std::bitset&lt;4&gt; 仍将占用一个完整字节。

      我能想到的另一种数据结构是位域

      // Maybe #pragma pack(push(1))
      struct TwoBCDDecimalDigits {
          uint8_t digit1 : 4;
          uint8_t digit2 : 4;
      };
      // Maybe #pragma pack(pop)
      

      甚至还有一个可用的库,可以将此格式转换为目标 CPU 架构支持的标准化数字格式:


      我能想到的另一种方法是编写自己的类:

      class BCDEncodedNumber {
          enum class Sign_t : char {
              plus = '+' ,
              minus = '-'
          };
          std::vector<uint8_t> doubleDigitsArray;
      public:
          BCDEncodedNumber() = default;
          BCDEncodedNumber(int num) {
              AddDigits(num); // Implements math operation + against the
                              // current BCD representation stored in 
                              // doubleDigitsArray.
          }    
      };
      

      【讨论】:

      • C++ 中的所有对象都必须至少占用 1 个字节,因此如果您使用 bitset&lt;4&gt;,您不会比仅使用 char 获得任何好处。
      • @MatteoItalia 好吧,那是个误会。我的意思不是每个数字 0-9 使用一个完整字节,而是使用单个字节的半字节。让我纠正我的答案。
      • 肯定更好,虽然恕我直言,在这里使用位域只是一个障碍 - 假设您有一个 TwoBCDDecimalDigits 的向量,现在要访问第 n 个数字,您必须获取元素 n/2然后有一个 if over n%2 来决定读取哪个成员。如果它只是uint8_t 的数组,就像在您的第二个解决方案中一样,您可以直接移动,而无需分支(第 n 个数字 = array[n&gt;&gt;1]&gt;&gt;((n&amp;1)&lt;&lt;2))。
      • @Matteo 好吧,我现在没心情详细说明BCDEncodedNumber 的实现细节。还有一个适当的可移植库可用。但希望 Q&A 可以将每个人推向需要研究的正确方向。
      猜你喜欢
      • 2011-11-01
      • 1970-01-01
      • 2010-11-20
      • 2023-03-26
      • 2016-05-11
      • 2013-11-11
      • 2020-11-06
      • 1970-01-01
      • 2017-09-07
      相关资源
      最近更新 更多