编译编码n个不同状态所需的比特数的时间计算

时间:2014-05-21 11:09:57

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

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

我需要在编译时计算 (结果将用作非类型模板参数)存储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不同状态所需的最小位数,即整数部分(向下舍入)舍入二进制对数:

ceil(log2(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,操作系统冻结,编译器崩溃)。

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

7 个答案:

答案 0 :(得分:6)

存储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

答案 1 :(得分:5)

试试这个:

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。

编辑2 :重命名该功能以避免混淆。

答案 2 :(得分:2)

也许

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

由于最初问题引起的混淆,我选择发布此答案。这是基于@DanielKO和@Henrik的答案。

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

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

答案 5 :(得分:1)

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

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::log快一个数量级和std::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;
}

这不应该太难以概括为不同的参数宽度。

答案 6 :(得分:0)

在C ++ 20中,我们(位于标头SoftDeletes中):

import Portal from '@material-ui/core/Portal';

const FooComponent = (props) => {
   const portalRef = useRef(null);

   return <>
      <form>
        First form
        <div ref={portalRef} />
      </form>
      <Portal container={portalRef.current}>
        <form>Another form here</form>
      </Portal>
   </>;
}

返回:如果x == 0,则为0;否则,加上x的以2为底的对数,并丢弃所有小数部分。 备注:除非T是无符号整数类型,否则此函数不得参与重载解析。