【问题标题】:Can I compute pow(10,x) at compile-time in c?我可以在 c 的编译时计算 pow(10,x) 吗?
【发布时间】:2010-11-07 03:45:13
【问题描述】:

是否可以在编译时计算 pow(10,x)?

我有一个不支持浮点和慢整数除法的处理器。我试图在编译时执行尽可能多的计算。如果我将xC/pow(10,x) 作为参数传递(x 和C 始终是常量整数,但对于每个调用它们是不同的常量),我可以显着加快一个特定函数的速度。我想知道是否可以通过引入自动执行1/pow(10,x) 的宏而不是强迫程序员计算它来使这些函数调用更不容易出错?

有预处理器技巧吗?我可以强制编译器优化库调用吗?

【问题讨论】:

  • 我相信我已经看到了 C 预处理器已经完成的证据(我认为它是在预处理器中的混淆 C 竞赛中实现的磁带机。)所以有一种方法。不过不知道那是什么。
  • 预处理器#defines 不能递归,因为它们只是文本替换。所以像格雷格一样,这里是一个不要花时间寻找的地方。 :)
  • @Greg D:但是,从图灵机开始并实现 10 的指数函数让我觉得雄心勃勃。

标签: c compile-time-constant


【解决方案1】:

在您溢出 int(甚至是 long)之前,可能的值非常少。为了清楚起见,把它做成一张桌子!

编辑:如果您使用浮点数(看起来像),那么不,如果不实际编写在 make 过程中运行并将值输出到的代码,就不可能在编译时调用 pow() 函数然后编译的文件(例如头文件)。

【讨论】:

  • 受到启发!对于已知基数的幂。那不是小数。 :)
  • C 和 x 以不会溢出的方式相关:给定一个值 v,选择 x 使得 0.1
  • 你的意思是total = (total<<1) + (total<<3) 当你使用total *= 10时,大多数编译器都可以自动做到这一点
  • @leiz - 什么?我的意思是 C 是 0.1 到 1 之间数字的 1.15 定点表示。x 始终是整数。
  • 不需要使用表格。对于整数,您可以使用 E 表示法,例如1.0E5.
【解决方案2】:

GCC 将以足够高的优化级别执行此操作(-O1 为我执行此操作)。例如:

#include <math.h>

int test() {
        double x = pow(10, 4);
        return (int)x;
}

在 -O1 -m32 编译为:

        .file   "test.c"
        .text
.globl test
        .type   test, @function
test:
        pushl   %ebp
        movl    %esp, %ebp
        movl    $10000, %eax
        popl    %ebp
        ret
        .size   test, .-test
        .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
        .section        .note.GNU-stack,"",@progbits

这在没有强制转换的情况下也可以工作 - 当然,你会在其中得到一个浮点加载指令,因为 Linux ABI 在 FPU 寄存器中传递浮点返回值。

【讨论】:

  • 不错。我想知道他们如何在合理的编译时间内检查 pow 是否是纯函数。也许他们有一个已知功能的列表?
  • 一些数学函数很可能是编译器内置的;此外,GCC 有一个非标准的 pure 函数属性
  • 'pure' 不足以进行这种交叉编译单元优化;如果它们在同一个编译单元中,GCC 将在内联后执行常量折叠。 pure 主要只是对编译器的一个提示,它不需要使其寄存器中的数据无效。
  • 我讨厌进入 c 代码的词法解析器,但理论上你可以编写自己的预处理器来精确控制优化。
  • 与其说是一个预编译器,不如说是一个真正的预处理器——你必须处理局部函数指针遮蔽全局 pow() 等的可能性
【解决方案3】:

你可以用 Boost.Preprocessor 做到这一点:

http://www.boost.org/doc/libs/1_39_0/libs/preprocessor/doc/index.html

代码:

#include <boost/preprocessor/repeat.hpp>

#define _TIMES_10(z, n, data) * 10
#define POW_10(n) (1 BOOST_PP_REPEAT(n, _TIMES_10, _))

int test[4] = {POW_10(0), POW_10(1), POW_10(2), POW_10(3)};

【讨论】:

  • 不幸的是,我只使用 c。
  • 然而 OP 要求 C 解决方案,而 Boost 是一个 C++ 库
  • 但是您可以将它与 C(Boost.Preprocessor 库)一起使用,它只是预处理器,我检查了它;)只配置您的包含目录并包含它!
【解决方案4】:

您可以将科学记数法用于浮点值,这是 C 语言的一部分。看起来是这样的:

e = 1.602E-19   // == 1.602 * pow(10, -19)

E 之前的数字(E 可能是大写或小的1.602e-19)是小数部分,而E 之后的(有符号)数字序列是指数部分。默认情况下,数字类型为double,但如果您需要floatlong double,您可以附加浮点后缀(fFlL)。

我不建议将此语义打包到宏中:

  1. 它不适用于变量、浮点值等。
  2. 科学记数法更具可读性。

【讨论】:

  • 虽然你不推荐,但这正是我需要的:#define P10(X) (1eX),结合#define fixedpt(value,digits) ((value)*(1&lt;&lt;15)/P10(digits))给我想要的结果,不依赖优化设置。
  • 哪个编译器使用#define P10(X) (1eX)?在 Arduino 编译器中,我需要 #define P10(X) (1e##X)
【解决方案5】:

实际上,通过利用 C 预处理器,您可以让它为任何实数 C 和整数 x 计算 C pow(10, x)。请注意,正如@quinmars 所指出的,C 允许您使用科学语法来表达数字常量:

#define myexp 1.602E-19   // == 1.602 * pow(10, -19)

用于常量。考虑到这一点,再加上一点聪明,我们可以构造一个预处理器宏,它接受Cx 并将它们组合成一个求幂记号:

#define EXP2(a, b) a ## b
#define EXP(a, b) EXP2(a ## e,b)
#define CONSTPOW(C,x) EXP(C, x)

这现在可以用作常数数值:

const int myint = CONSTPOW(3, 4); // == 30000
const double myfloat = CONSTPOW(M_PI, -2); // == 0.03141592653

【讨论】:

    【解决方案6】:

    实际上,您拥有 M4,它是一种比 GCC 更强大的预处理器。这两者之间的主要区别是 GCC 不是递归的,而 M4 是递归的。它使诸如在编译时进行算术(以及更多!)之类的事情成为可能。下面的代码示例是您想要做的,不是吗?我在一个文件源中使它变得庞大;但我通常将 M4 的宏定义放在单独的文件中并调整我的 Makefile 规则。这样,您的代码就不会被难看的侵入式 M4 定义保存到我在这里完成的 C 源代码中。

    $ cat foo.c
    define(M4_POW_AUX, `ifelse($2, 1, $1, `eval($1 * M4_POW_AUX($1, decr($2)))')')dnl
    define(M4_POW, `ifelse($2, 0, 1, `M4_POW_AUX($1, $2)')')dnl
    
    #include <stdio.h>
    
    int                     main(void)
    {
      printf("2^0 = %d\n", M4_POW(2, 0));
      printf("2^1 = %d\n", M4_POW(2, 1));
      printf("2^4 = %d\n", M4_POW(2, 4));
    
      return 0;
    }
    

    编译此代码示例的命令行使用 GCC 和 M4 从标准输入读取的能力。

    $ cat foo.c | m4 - | gcc -x c -o m4_pow -
    $ ./m4_pow
    2^0 = 1
    2^1 = 2
    2^4 = 16
    

    希望对您有所帮助!

    【讨论】:

      【解决方案7】:

      如果您只需要在编译时使用该值,请使用scientific notation,例如 1e2 for pow(10, 2)

      如果您想在编译时填充这些值,然后在运行时使用它们,那么只需使用查找表,因为有only 23 different powers of 10 完全可表示双精度

      double POW10[] = {1., 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10,
      1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
      

      您可以在运行时从上面的查找表中获得更大的 10 次方以快速获得结果,而无需一次又一次地乘以 10,但结果只是一个接近 10 次方的值,就像您使用 10eX 时一样X > 22

      double pow10(int x)
      {
         if (x > 22)
            return POW10[22] * pow10(x - 22);
         else if (x >= 0)
            return POW10[x];
          else
              return 1/pow10(-x);
      }
      

      如果不需要负指数,则可以删除最终分支。

      如果内存是一个约束,您还可以进一步减小查找表的大小。例如,通过仅存储 10 的偶数次方并在指数为奇数时乘以 10,表大小现在只有一半。

      【讨论】:

        【解决方案8】:

        最新版本的 GCC(大约 4.3)增加了使用 GMP 和 MPFR 通过评估更复杂的常量函数来进行一些编译时优化的能力。这种方法使您的代码简单且可移植,并相信编译器可以完成繁重的工作。

        当然,它的功能是有限度的。 Here's a link to the description in the changelog,其中包括受此支持的功能列表。 'pow' 就是其中之一。

        【讨论】:

          【解决方案9】:

          很遗憾,您不能使用预处理器来预先计算库调用。如果 x 是整数,您可以编写自己的函数,但如果它是浮点类型,我看不出有什么好的方法。

          【讨论】:

            【解决方案10】:

            bdonlan 的回放很准确,但请记住,只要您愿意在自己的自定义预处理器中解析和分析代码,您几乎可以在编译框中执行您选择的任何优化。在大多数版本的 unix 中,覆盖调用编译器的隐式规则以在您自己的自定义步骤到达编译器之前调用它是一项微不足道的任务。

            【讨论】:

              猜你喜欢
              • 2023-03-14
              • 2022-08-15
              • 2015-05-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-09-23
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多