【问题标题】:How to compute log with the preprocessor如何使用预处理器计算日志
【发布时间】:2015-02-19 07:28:50
【问题描述】:

如何在 windows 上使用预处理器进行 log(x)?

喜欢:

#define A    log(4)/log(2)

在我的代码之后是数组

int b[A]; // A=2 will be computed with the preprocessor ! not in run time

【问题讨论】:

  • 为了清楚起见,您希望log 在哪个基地?

标签: c


【解决方案1】:

最好的选择,使用 constexpr 变量模板:

template <unsigned int x>
constexpr enable_if_t<x != 0, int> log2 = 1 + log2<x / 2U>;

template <>
constexpr int log2<1U> = 0;

Live Example

【讨论】:

    【解决方案2】:

    这个答案的灵感来自5gon12eder,但第一个宏更简单。与5gon12eder 的解决方案不同,此实现为BITS_TO_REPRESENT(0) 提供0,这可以说是正确的。此BITS_TO_REPRESENT(N) 函数返回表示小于或等于非负整数N 的无符号整数的位数;存储一个有符号的数量级N 需要一个额外的位。

    #define NEEDS_BIT(N, B)     (((unsigned long)N >> B) > 0)
    
    #define BITS_TO_REPRESENT(N)                            \
            (NEEDS_BIT(N,  0) + NEEDS_BIT(N,  1) + \
             NEEDS_BIT(N,  2) + NEEDS_BIT(N,  3) + \
             NEEDS_BIT(N,  4) + NEEDS_BIT(N,  5) + \
             NEEDS_BIT(N,  6) + NEEDS_BIT(N,  7) + \
             NEEDS_BIT(N,  8) + NEEDS_BIT(N,  9) + \
             NEEDS_BIT(N, 10) + NEEDS_BIT(N, 11) + \
             NEEDS_BIT(N, 12) + NEEDS_BIT(N, 13) + \
             NEEDS_BIT(N, 14) + NEEDS_BIT(N, 15) + \
             NEEDS_BIT(N, 16) + NEEDS_BIT(N, 17) + \
             NEEDS_BIT(N, 18) + NEEDS_BIT(N, 19) + \
             NEEDS_BIT(N, 20) + NEEDS_BIT(N, 21) + \
             NEEDS_BIT(N, 22) + NEEDS_BIT(N, 23) + \
             NEEDS_BIT(N, 24) + NEEDS_BIT(N, 25) + \
             NEEDS_BIT(N, 26) + NEEDS_BIT(N, 27) + \
             NEEDS_BIT(N, 28) + NEEDS_BIT(N, 29) + \
             NEEDS_BIT(N, 30) + NEEDS_BIT(N, 31)   \
            )
    

    BITS_TO_REPRESENT几乎是一个以 2 为底的对数。由于从浮点到整数的默认转换是截断,因此以 2 为底的对数的整数版本对应于浮点计算floor(log(N)/log(2))BITS_TO_REPRESENT(N) 返回比 floor(log(N)/log(2)) 大一的结果。

    例如:

    1. BITS_TO_REPRESENT(7)3,而 floor(log(7)/log(2))2
    2. BITS_TO_REPRESENT(8)4,而 floor(log(8)/log(2))3

    【讨论】:

    • 感谢您的回答。这个解决方案很短很简单,可以通过简单的脚本推广到 n 位架构。
    【解决方案3】:

    使用最多 32 位整数的 LOG 宏的简短定义可能是:

    #define LOG_1(n) (((n) >= 2) ? 1 : 0)
    #define LOG_2(n) (((n) >= 1<<2) ? (2 + LOG_1((n)>>2)) : LOG_1(n))
    #define LOG_4(n) (((n) >= 1<<4) ? (4 + LOG_2((n)>>4)) : LOG_2(n))
    #define LOG_8(n) (((n) >= 1<<8) ? (8 + LOG_4((n)>>8)) : LOG_4(n))
    #define LOG(n)   (((n) >= 1<<16) ? (16 + LOG_8((n)>>16)) : LOG_8(n))
    

    但是,在使用它之前,请检查您是否真的需要它。人们通常需要对 2 的幂的值使用对数。例如在实现位数组时。虽然很难将log 计算为常量表达式,但定义 2 的幂非常容易。因此,您可以考虑将常量定义为:

    #define logA   4
    #define A      (1<<logA)
    

    代替:

    #define A     16
    #define logA  LOG(A)
    

    【讨论】:

    • #define BITS_TO_REPRESENT(n) (LOG(n) + !!((n) &amp; ((n) - 1))) 给出了与最佳答案相同的用例,我认为
    • 非常适合我的用例!注意,准确地说:从数学上讲,这个答案等同于floor(log2(x))。以编程方式,这个答案是数字中最高位的位置(0 是最低值位)。
    • (好吧,从技术上讲,log2(0) 是 -infinity;对于这个宏,LOG(0) 产生 0)
    【解决方案4】:

    好的,现在是肮脏的蛮力预处理器诡计。

    根据您的问题,我假设您真正想要的不是一般对数(这在整数算术中甚至是不可能的),而是表示给定数字所需的位数。如果我们将自己限制为 32 位整数,有一个解决方案,虽然它并不漂亮。

    #define IS_REPRESENTIBLE_IN_D_BITS(D, N)                \
      (((unsigned long) N >= (1UL << (D - 1)) && (unsigned long) N < (1UL << D)) ? D : -1)
    
    #define BITS_TO_REPRESENT(N)                            \
      (N == 0 ? 1 : (31                                     \
                     + IS_REPRESENTIBLE_IN_D_BITS( 1, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS( 2, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS( 3, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS( 4, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS( 5, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS( 6, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS( 7, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS( 8, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS( 9, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(10, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(11, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(12, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(13, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(14, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(15, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(16, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(17, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(18, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(19, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(20, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(21, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(22, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(23, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(24, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(25, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(26, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(27, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(28, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(29, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(30, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(31, N)    \
                     + IS_REPRESENTIBLE_IN_D_BITS(32, N)    \
                     )                                      \
       )
    

    这个想法是,当且仅当 n &geq; 2d-1n d。在特别处理 n = 0 的情况之后,我们简单地暴力破解所有 32 个可能的答案。

    如果N 可以精确地使用D 位表示,则辅助宏IS_REPRESENTIBLE_IN_D_BITS(D, N) 将扩展为一个表达式,计算结果为D,否则为-1。我已经定义了宏,如果答案为“否”,则结果为 -1。为了补偿负数,我在末尾添加了 31。如果数字不能用任何 1、...、32 位表示,那么总体结果将为 -1,这应该有助于我们发现一些错误。

    表达式BITS_TO_REPRESENT(42) 是用于数组长度声明的有效编译时常量。

    总而言之,对于许多应用程序来说,总是使数组长度为 32 个元素的额外成本似乎是可以接受的,它为您节省了很多麻烦。所以我只有在必要时才会使用这种诡计。

    更新:只是为了避免混淆:此解决方案使用预处理器来评估“对数”。预处理器所做的只是执行文本替换,如果使用-E 开关(至少对于 GCC)进行编译,您可以看到该替换。我们来看看这段代码:

    int
    main()
    {
      int digits[BITS_TO_REPRESENT(42)];
      return 0;
    }
    

    它将被预处理(被警告):

    int
    main()
    {
      int digits[(42 == 0 ? 1 : (31 + (((unsigned long) 42 >= (1UL << (1 - 1)) && (unsigned long) 42 < (1UL << 1)) ? 1 : -1) + (((unsigned long) 42 >= (1UL << (2 - 1)) && (unsigned long) 42 < (1UL << 2)) ? 2 : -1) + (((unsigned long) 42 >= (1UL << (3 - 1)) && (unsigned long) 42 < (1UL << 3)) ? 3 : -1) + (((unsigned long) 42 >= (1UL << (4 - 1)) && (unsigned long) 42 < (1UL << 4)) ? 4 : -1) + (((unsigned long) 42 >= (1UL << (5 - 1)) && (unsigned long) 42 < (1UL << 5)) ? 5 : -1) + (((unsigned long) 42 >= (1UL << (6 - 1)) && (unsigned long) 42 < (1UL << 6)) ? 6 : -1) + (((unsigned long) 42 >= (1UL << (7 - 1)) && (unsigned long) 42 < (1UL << 7)) ? 7 : -1) + (((unsigned long) 42 >= (1UL << (8 - 1)) && (unsigned long) 42 < (1UL << 8)) ? 8 : -1) + (((unsigned long) 42 >= (1UL << (9 - 1)) && (unsigned long) 42 < (1UL << 9)) ? 9 : -1) + (((unsigned long) 42 >= (1UL << (10 - 1)) && (unsigned long) 42 < (1UL << 10)) ? 10 : -1) + (((unsigned long) 42 >= (1UL << (11 - 1)) && (unsigned long) 42 < (1UL << 11)) ? 11 : -1) + (((unsigned long) 42 >= (1UL << (12 - 1)) && (unsigned long) 42 < (1UL << 12)) ? 12 : -1) + (((unsigned long) 42 >= (1UL << (13 - 1)) && (unsigned long) 42 < (1UL << 13)) ? 13 : -1) + (((unsigned long) 42 >= (1UL << (14 - 1)) && (unsigned long) 42 < (1UL << 14)) ? 14 : -1) + (((unsigned long) 42 >= (1UL << (15 - 1)) && (unsigned long) 42 < (1UL << 15)) ? 15 : -1) + (((unsigned long) 42 >= (1UL << (16 - 1)) && (unsigned long) 42 < (1UL << 16)) ? 16 : -1) + (((unsigned long) 42 >= (1UL << (17 - 1)) && (unsigned long) 42 < (1UL << 17)) ? 17 : -1) + (((unsigned long) 42 >= (1UL << (18 - 1)) && (unsigned long) 42 < (1UL << 18)) ? 18 : -1) + (((unsigned long) 42 >= (1UL << (19 - 1)) && (unsigned long) 42 < (1UL << 19)) ? 19 : -1) + (((unsigned long) 42 >= (1UL << (20 - 1)) && (unsigned long) 42 < (1UL << 20)) ? 20 : -1) + (((unsigned long) 42 >= (1UL << (21 - 1)) && (unsigned long) 42 < (1UL << 21)) ? 21 : -1) + (((unsigned long) 42 >= (1UL << (22 - 1)) && (unsigned long) 42 < (1UL << 22)) ? 22 : -1) + (((unsigned long) 42 >= (1UL << (23 - 1)) && (unsigned long) 42 < (1UL << 23)) ? 23 : -1) + (((unsigned long) 42 >= (1UL << (24 - 1)) && (unsigned long) 42 < (1UL << 24)) ? 24 : -1) + (((unsigned long) 42 >= (1UL << (25 - 1)) && (unsigned long) 42 < (1UL << 25)) ? 25 : -1) + (((unsigned long) 42 >= (1UL << (26 - 1)) && (unsigned long) 42 < (1UL << 26)) ? 26 : -1) + (((unsigned long) 42 >= (1UL << (27 - 1)) && (unsigned long) 42 < (1UL << 27)) ? 27 : -1) + (((unsigned long) 42 >= (1UL << (28 - 1)) && (unsigned long) 42 < (1UL << 28)) ? 28 : -1) + (((unsigned long) 42 >= (1UL << (29 - 1)) && (unsigned long) 42 < (1UL << 29)) ? 29 : -1) + (((unsigned long) 42 >= (1UL << (30 - 1)) && (unsigned long) 42 < (1UL << 30)) ? 30 : -1) + (((unsigned long) 42 >= (1UL << (31 - 1)) && (unsigned long) 42 < (1UL << 31)) ? 31 : -1) + (((unsigned long) 42 >= (1UL << (32 - 1)) && (unsigned long) 42 < (1UL << 32)) ? 32 : -1) ) )];
      return 0;
    }
    

    这看起来很糟糕,如果在运行时对其进行评估,那将是相当多的指令。但是,由于所有操作数都是常量(准确地说是字面量),因此编译器能够在编译时对其进行评估。它必须这样做,因为数组长度声明在 C 89 中必须是常量。

    如果您在其他不需要为编译时常量的地方使用宏,则取决于编译器是否计算表达式。然而,任何合理的编译器都应该执行这种相当基本的优化——称为常量折叠——如果启用了优化。如果有疑问 - 一如既往 - 请查看生成的汇编代码。

    例如,让我们考虑这个程序。

    int
    main()
    {
      return BITS_TO_REPRESENT(42);
    }
    

    return 语句中的表达式显然不需要是编译时常量,所以让我们看看 GCC 会生成什么代码。 (我使用-S 开关在组装阶段停止。)

    即使没有启用任何优化,我也会得到以下汇编代码,它表明宏扩展被折叠到常量 6 中。

    main:
    .LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $6, %eax  # See the constant 6?
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
    

    【讨论】:

    • 好的,谢谢,但是你确定一切都在编译时完成了吗? (好把戏,我喜欢它=))
    • @HascoetJulien 数组长度声明必须在编译时进行评估。如果您在不需要为编译时常量的表达式中使用宏,则取决于编译器是否对其进行静态评估。也就是说,如果编译器没有在编译时评估具有所有操作数常量的表达式(至少在启用优化的情况下),它会非常愚蠢。这被称为常量折叠,是更基本的优化之一。如果有疑问,请一如既往地查看生成的汇编代码。
    • @HascoetJulien 不客气。我对我的回答添加了一个修正,以进一步解释我在上一条评论中的意思。
    • 在数学上,这个答案等价于floor(log2(n)) + 1(n==0 时除外,结果为 1)。以编程方式,这个答案是表示一个数字所需的位数(正如你所解释的那样)。
    【解决方案5】:

    C 预处理器#define 纯粹是一种文本替换机制。您将无法在编译时计算日志值。

    您也许可以使用 C++ 模板,但这是我不明白的黑魔法,目前无关紧要。

    或者正如我在下面的评论中提到的,您可以构建自己的预处理器,在将更新的代码交给标准 C 编译器之前评估数组大小方程。

    编辑

    在浏览更多内容时,我看到了这个 SO 问题:Do any C or C++ compilers optimize within define macros?

    这个问题是关于评估这个宏字符串的:

    #include <math.h>
    #define ROWS 15
    #define COLS 16
    #define COEFF 0.15
    #define NODES (ROWS*COLS)
    #define A_CONSTANT (COEFF*(sqrt(NODES)))
    

    一致认为A_CONSTANT 可以是编译时间常数,取决于编译器的智能程度,以及将哪些数学函数定义为intrinsics。它还暗示 GCC 足够聪明,可以解决这个问题。

    因此,您的问题的答案可以通过尝试找到,并查看您的编译器实际生成的代码类型。

    【讨论】:

    • 此问题标记为 C,而不是 C++,因此模板不可用。
    • @Cornstalks 我知道 .. 但你永远不知道 OP 真正在问什么
    • 而 OP 专门谈论预处理器,他可能对 #define 的工作原理感到困惑。
    • 谢谢,我确切地知道#define 是如何工作的,但我想做一个通用的实现,并且数组长度取决于日志值,我想要静态分配而不是 malloc。此外,它还用于循环展开和其他棘手的优化,当数组 a 用编译器声明并且在运行时没有时可以完成
    • @HascoetJulien 或者构建自己的预处理器,在将源代码扔到标准 C 编译器之前评估方程。这比修改编译器要容易。
    猜你喜欢
    • 2013-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多