【问题标题】:Compile time computing of number of bits needed to encode n different states编译时计算编码 n 个不同状态所需的位数
【发布时间】:2014-05-21 11:09:56
【问题描述】:

编辑:在最初的问题中有一个错误的公式,并且尝试的算法正在做一些与预期完全不同的事情。很抱歉,我决定重写问题以消除所有混乱。

我需要在编译时计算(结果将用作非类型模板参数)存储n不同状态所需的最小位数:

constexpr unsigned bitsNeeded(unsigned n);

或通过模板

结果应该是:

+-----------+--------+
| number of | bits   |
| states    | needed |
+-----------+--------+
|     0     |    0   | * or not defined
|           |        |
|     1     |    0   |
|           |        |
|     2     |    1   |
|           |        |
|     3     |    2   |
|     4     |    2   |
|           |        |
|     5     |    3   |
|     6     |    3   |
|     7     |    3   |
|     8     |    3   |
|           |        |
|     9     |    4   |
|    10     |    4   |
|    ..     |   ..   |
|    16     |    4   |
|           |        |
|    17     |    5   |
|    18     |    5   |
|    ..     |   ..   |
+-----------+--------+

供参考的初始(以某种方式更正)版本:

我需要计算在编译时(结果将用作非类型模板参数)存储n不同状态所需的最小位数,即积分部分(向下取整) 二进制对数向上取整:

constexpr unsigned ceilLog2(unsigned n);

这是我想出的(完全错误):

constexpr unsigned intLog2(unsigned num_states_) {
  return
    num_states_ == 1 ?
      1 :
      (
      intLog2(num_states_ - 1) * intLog2(num_states_ - 1) == num_states_ - 1 ?
        intLog2(num_states_ - 1) + 1 :
        intLog2(num_states_ - 1)
      );
}

这会产生正确的结果(对于 num_states_ != 0),但递归会呈指数级增长,并且对于任何大于 10 的输入(内存使用量)实际上无法使用在编译过程中超过 2GB,操作系统冻结并且编译器崩溃)。

如何在编译时以实用的方式计算它?

【问题讨论】:

  • 我不敢相信这会产生正确的结果。
  • @Henrik 没有。 intLog2Runtime(256) = 16, intLog2Runtime(1024) = 32.
  • 您似乎在尝试计算平方根,而不是对数。
  • 最小位数是向上取整(而不是向下)的二进制对数。

标签: c++ c++11 compile-time constexpr logarithm


【解决方案1】:

存储n不同状态所需的最小位数为ceil(log2(n))

constexpr unsigned floorlog2(unsigned x)
{
    return x == 1 ? 0 : 1+floorlog2(x >> 1);
}

constexpr unsigned ceillog2(unsigned x)
{
    return x == 1 ? 0 : floorlog2(x - 1) + 1;
}

请注意ceillog2(1) == 0。这完全没问题,因为如果你想序列化一个对象,并且你知道它的一个数据成员只能取值42,你不需要为这个成员存储任何东西。只需在反序列化时分配 42

【讨论】:

  • @DanielKO 看起来你的编译器坏了。 ceillog2(2) == 1ideone.com/6ztr7X
  • 我的错误,我贴错了代码;我一直忘记中间按钮粘贴在基于 Web 的文本编辑器中是不可靠的。
【解决方案2】:

试试这个:

constexpr unsigned numberOfBits(unsigned x)
{
    return x < 2 ? x : 1+numberOfBits(x >> 1);
}

更简单的表达式,correct result

编辑:“正确的结果”,如“建议的算法甚至没有接近”;当然,我正在计算“表示值 x 的位数”​​;如果您想知道从 0 到 x-1 的位数,请从参数中减去 1。要表示 1024,您需要 11 位,从 0 计数到 1023(1024 个状态)您需要 10。

EDIT 2:重命名函数以避免混淆。

【讨论】:

  • 正确的结果?你确定吗?
  • 是的,我的算法正在做一些完全不同的事情。我对此感到抱歉。我正在搜索的是存储 n 个不同值所需的位数,例如对于 n = 4,需要 2 位。您的解决方案非常有用,谢谢,很容易适应我想要的。再次为这个非常糟糕的问题感到抱歉。
  • 再次对问题中的混淆感到抱歉。当x 是 2 的幂时,您的函数是错误的。例如,对于 8 个状态,只需要 3 位。你的答案是 4。
  • @bolov 要么您没有阅读 ×edit×,要么您没有尝试将 8 写入二进制(1000,即 4 位)。
  • 建议与@bolov 的答案不同并删除这个,太专注于讨论...
【解决方案3】:

由于最初的问题引起的混乱,我选择发布此答案。这是建立在@DanielKO 和@Henrik 的答案之上的。

编码n不同状态所需的最小位数:

constexpr unsigned bitsNeeded(unsigned n) {
  return n <= 1 ? 0 : 1 + bitsNeeded((n + 1) / 2);
}

【讨论】:

  • n == UINT_MAX 会溢出,@DanielKO 不会。
【解决方案4】:

也许

constexpr int mylog(int n) {
    return (n<2) ?1:
           (n<4) ?2:
           (n<8) ?3:
           (n<16)?4:
           (n<32)?5:
           (n<64)?6:
           …
           ;
}

因为您将使用它作为模板参数,您可能想查看boost has to offer 的内容

【讨论】:

  • 是的,我想过这个,我只是跳了它不会到这个。
【解决方案5】:

在 C++20 中,我们有(在标题 &lt;bit&gt; 中):

template<class T>
  constexpr T log2p1(T x) noexcept;

返回: 如果 x == 0, 0;否则一加 x 的以 2 为底的对数,丢弃任何小数部分。 备注:除非 T 是无符号整数类型,否则此函数不应参与重载决议。

【讨论】:

  • 我觉得应该叫std::bit_width()officially
  • 现在是;但那不是我写那个答案的时候。
【解决方案6】:

constexpr 有点动力不足,直到 C++14。我推荐模板:

template<unsigned n> struct IntLog2;
template<> struct IntLog2<1> { enum { value = 1 }; };

template<unsigned n> struct IntLog2 {
private:
  typedef IntLog2<n - 1> p;
public:
  enum { value = p::value * p::value == n - 1 ? p::value + 1 : p::value };
};

【讨论】:

  • 为什么您建议使用模板? constexpr 也是如此。
  • @KonradRudolph:我的直觉是,由于模板以这种方式使用的时间超过了constexpr,编译器可能会更快地评估这种 1:1 转换。不过,我应该考虑改变算法,如果你的反对是出于审美,我可以理解。
【解决方案7】:

我在自己的代码中使用过的东西:

static inline constexpr
uint_fast8_t log2ceil (uint32_t value)
/* Computes the ceiling of log_2(value) */
{
    if (value >= 2)
    {
        uint32_t mask = 0x80000000;
        uint_fast8_t result = 32;
        value = value - 1;

        while (mask != 0) {
            if (value & mask)
                return result;
            mask >>= 1;
            --result;
        }
    }
    return 0;
}

它需要 C++14 用作 constexpr,但它有一个很好的特性,即它在运行时相当快——比使用 std::logstd::ceil 快​​大约一个数量级——而且我'已经验证了它对所有可表示的非零值产生相同的结果(log 在零上未定义,尽管 0 对这个应用程序来说是一个合理的结果;您不需要任何位来区分零值)使用以下程序:

#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <cmath>
#include "log2ceil.hh"

using namespace std;

int main ()
{
    for (uint32_t i = 1; i; ++i)
    {
        // If auto is used, stupid things happen if std::uint_fast8_t
        // is a typedef for unsigned char
        int l2c_math = ceil (log (i) / log (2));
        int l2c_mine = log2ceil (i);
        if (l2c_mine != l2c_math)
        {
            cerr << "Incorrect result for " << i << ": cmath gives "
                 << l2c_math << "; mine gives " << l2c_mine << endl;
            return EXIT_FAILURE;
        }
    }

    cout << "All results are as correct as those given by ceil/log." << endl;
    return EXIT_SUCCESS;
}

这也不应该太难推广到不同的参数宽度。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-02-23
    • 1970-01-01
    • 1970-01-01
    • 2011-01-29
    • 2016-11-26
    • 2020-12-08
    • 2014-01-08
    • 1970-01-01
    相关资源
    最近更新 更多