【问题标题】:How to compute non-integer (fractional) log2 at compile time in C如何在 C 中的编译时计算非整数(小数)log2
【发布时间】:2020-12-02 08:05:59
【问题描述】:

有各种基于宏的解决方案可以在编译时计算整数值log2,但是如果您需要比整数更高的精度,即二进制点之后的几个二进制位,该怎么办?生成的值是浮点表达式还是定点缩放的整数表达式并不重要,只要它的计算结果为编译时常量值即可。

您可能会问,这有什么用?我想到的申请是compute the number of bits needed to optimally pack a structure whose fields have value sets that don't span a power of 2 range

【问题讨论】:

  • 作为扩展,gcc 将允许您在编译时常量表达式中使用浮点 log2() 函数。 Example。但是,clang 不会,除非它需要一些我找不到的选项。

标签: c math c-preprocessor


【解决方案1】:

我想出了以下方法 - 它不是特别有创意,但它确实有效,并且不会完全减慢编译速度。 IOW,它完成了我需要它做的事情 - 在二进制点之后添加几个(这里:十几个)二进制位置的精度。

它的工作原理是将数字表示为 2 的幂,然后将其乘以恰好具有等于二进制分数 (2^-n) 的 log2 值的系数。乘以这些系数相当于将对数相加,因此FRAC_LOG2 宏扩展为使用嵌套三元表达式选择的元素的总和。

#define IROOT2_1 .7071067812 // 2^-(2^-1)
#define IROOT2_2 .8408964153 // 2^-(2^-2)
#define IROOT2_3 .9170040432 // 2^-(2^-3)
#define IROOT2_4 .9576032807 // 2^-(2^-4)
#define IROOT2_5 .9785720621 // 2^-(2^-5)
#define IROOT2_6 .9892280132 // 2^-(2^-6)
#define IROOT2_7 .9945994235 // 2^-(2^-7)
#define IROOT2_8 .9972960561 // 2^-(2^-8)
#define IROOT2_9 .9986471129 // 2^-(2^-9)
#define IROOT2_A .9993233275 // 2^-(2^-10)
#define IROOT2_B .9996616065 // 2^-(2^-11)
#define IROOT2_C .9998307889 // 2^-(2^-12)

#define BIT_SCAN_REV(n) \
(n>>15?15:n>>14?14:n>>13?13:n>>12?12:n>>11?11:n>>10?10:n>>9?9:\
n>>8?8:n>>7?7:n>>6?6:n>>5?5:n>>4?4:n>>3?3:n>>2?2:n>>1?1:0)

#define FRAC_LOG2_1(m,n) (1./4096.)*\
                         ((m<=n*IROOT2_1?2048:0)+FRAC_LOG2_2(m,n*(m<=n*IROOT2_1?IROOT2_1:1)))
#define FRAC_LOG2_2(m,n) ((m<=n*IROOT2_2?1024:0)+FRAC_LOG2_3(m,n*(m<=n*IROOT2_2?IROOT2_2:1)))
#define FRAC_LOG2_3(m,n)  ((m<=n*IROOT2_3?512:0)+FRAC_LOG2_4(m,n*(m<=n*IROOT2_3?IROOT2_3:1)))
#define FRAC_LOG2_4(m,n)  ((m<=n*IROOT2_4?256:0)+FRAC_LOG2_5(m,n*(m<=n*IROOT2_4?IROOT2_4:1)))
#define FRAC_LOG2_5(m,n)  ((m<=n*IROOT2_5?128:0)+FRAC_LOG2_6(m,n*(m<=n*IROOT2_5?IROOT2_5:1)))
#define FRAC_LOG2_6(m,n)   ((m<=n*IROOT2_6?64:0)+FRAC_LOG2_7(m,n*(m<=n*IROOT2_6?IROOT2_6:1)))
#define FRAC_LOG2_7(m,n)   ((m<=n*IROOT2_7?32:0)+FRAC_LOG2_8(m,n*(m<=n*IROOT2_7?IROOT2_7:1)))
#define FRAC_LOG2_8(m,n)   ((m<=n*IROOT2_8?16:0)+FRAC_LOG2_9(m,n*(m<=n*IROOT2_8?IROOT2_8:1)))
#define FRAC_LOG2_9(m,n)    ((m<=n*IROOT2_9?8:0)+FRAC_LOG2_A(m,n*(m<=n*IROOT2_9?IROOT2_9:1)))
#define FRAC_LOG2_A(m,n)    ((m<=n*IROOT2_A?4:0)+FRAC_LOG2_B(m,n*(m<=n*IROOT2_A?IROOT2_A:1)))
#define FRAC_LOG2_B(m,n)    ((m<=n*IROOT2_B?2:0)+FRAC_LOG2_C(m,n*(m<=n*IROOT2_B?IROOT2_B:1)))
#define FRAC_LOG2_C(m,n)     (m<=n*IROOT2_C?1:0)

#define FRAC_LOG2(n) (BIT_SCAN_REV(n) + FRAC_LOG2_1(1<<BIT_SCAN_REV(n), n))

当然,它并不便宜 - 对于一个 2 位数字,它会扩展到编译器必须挖掘的大约 700kb 代码,但它的精度超过 5 个小数位。

一种解决方法是将BIT_SCAN_REV 的积分结果存储在一个枚举中,这样它就只是几个字母而不是大约 170 个:

enum { 
  input = 36,
  bsr = BIT_SCAN_REV(input),
  bsr_ = 1<<bsr_,
};

static const float output = bsr + FRAC_LOG2_1(bsr_, input);

在没有递归宏的情况下以低得多的内存成本执行此操作的另一种方法是在任何时候计算值时都需要使用包含文件。

【讨论】:

  • 很好,但是我希望位掩码而不是嵌套的&lt; 条件......但我对编译时表达式也不是很熟悉...... :) 我做过的最好的field 是任何维度的逆方阵,作为 C++ 中的模板。这是我在运行时对log 的方法进行比较看起来你在做同样的事情......
  • 由于这是在编译时完成的,并且不会特别减慢编译器的速度,因此最容易理解的形式会胜出,并且恰好具有最简单的表达方式。如果您考虑一下,位掩码也可以进行测试并具有嵌套表达式,并且将具有比递归比较字符串更多的语法树元素。 31 个三元运算节点,32 个二元运算节点,64 个数字文字节点。很难打败它。请记住,编译器必须为整个事物构建一棵树。尾端一次做一点已经很困难了——有 c*2^12 个树节点!
猜你喜欢
  • 2012-02-16
  • 2012-07-07
  • 2010-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-19
  • 2012-10-09
  • 1970-01-01
相关资源
最近更新 更多