Могу ли я вычислить pow (10,x) во время компиляции в c?

можно ли вычислить pow (10,x) во время компиляции?

у меня есть процессор без поддержки с плавающей запятой и медленного целочисленного деления. Я пытаюсь выполнить как можно больше вычислений во время компиляции. Я могу значительно ускорить одну конкретную функцию, если я передам оба x и C/pow(10,x) в качестве аргументов (x и C всегда являются постоянными целыми числами, но они разные константы для каждого вызова). Мне интересно, могу ли я сделать эти вызовы функций менее подверженными ошибкам представляем макрос, который делает 1/pow(10,x) автоматически, вместо того чтобы заставлять программиста вычислить его?

есть препроцессор трюк? Могу ли я заставить компилятор оптимизировать вызов библиотеки?

10 ответов


вы можете использовать научную нотацию для значений с плавающей запятой,которая является частью языка C. Это выглядит так:

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

число перед E ( между E может быть столица или маленький 1.602e-19) - часть дроби, где в качестве (знаковой) последовательности цифр после E является экспоненциальной частью. По умолчанию номер имеет тип double, но вы можете прикрепить суффикс с плавающей запятой (f, F, l или L) если вам нужна float или a long double.

я бы не рекомендовал упаковывать эту семантику в макрос:

  1. он не будет работать для переменных с плавающей запятой и т. д.
  2. научная нотация более читабельна.

перед переполнением int (или даже long) возможно очень мало значений. Для ясности, сделайте стол!

edit: если вы используете поплавки (похоже, что вы), то нет, невозможно будет вызвать функцию pow() во время компиляции без фактического написания кода, который запускается в процессе make и выводит значения в файл (например, файл заголовка), который затем компилируется.


GCC сделает это на достаточно высоком уровне оптимизации (-O1 делает это за меня). Например:

#include <math.h>

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

компилирует at-O1-m32 в:

        .file   "test.c"
        .text
.globl test
        .type   test, @function
test:
        pushl   %ebp
        movl    %esp, %ebp
        movl    000, %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.


вы можете сделать это с помощью Boost.Препроцессор:

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, вы можете заставить его вычислить C pow(10, x) реальным C и интегральное x. Заметьте, что, как отметил @quinmars, C позволяет использовать научный синтаксис для выражения числовых констант:

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

используется для констант. Имея это в виду и немного ума, мы можем построить макрос препроцессора, который принимает C и x и объединить их в знак возведения в степень:

#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

На самом деле, у вас есть M4, который является предпроцессорным способом более мощным, чем GCC. Основное различие между этими двумя является GCC не рекурсивным, тогда как M4. Это делает возможными такие вещи, как арифметика во время компиляции (и многое другое!). Приведенный ниже пример кода-это то, что вы хотели бы сделать, не так ли? Я сделал его громоздким в источнике одного файла; но я обычно помещаю определения макросов M4 в отдельные файлы и настраиваю свои правила Makefile. Таким образом, ваш код хранится от уродливых интрузивных определений M4 в исходный код C, который я сделал здесь.

$ cat foo.c
define(M4_POW_AUX, `ifelse(, 1, , `eval( * M4_POW_AUX(, decr()))')')dnl
define(M4_POW, `ifelse(, 0, 1, `M4_POW_AUX(, )')')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

надеюсь, что это поможет!


в последних версиях GCC (около 4.3 ) добавлена возможность использовать GMP и MPFR для оптимизации времени компиляции путем оценки более сложных функций, которые являются постоянными. Этот подход оставляет ваш код простым и портативным и доверяет компилятору выполнять тяжелую работу.

конечно, есть пределы тому, что он может сделать. вот ссылка на описание в changelog, который включает в себя перечень функций, которые поддерживаются этим. 'Бах' - один их.


к сожалению, вы не можете использовать препроцессор для предварительного расчета вызовов библиотеки. Если x является интегралом, вы можете написать свою собственную функцию, но если это тип с плавающей запятой, я не вижу хорошего способа сделать это.


здесь только 23 различных полномочия 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 снова и снова.

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, когда показатель нечетен, размер таблицы теперь составляет только половину.


воспроизведение bdonlan находится на месте, но имейте в виду, что вы можете выполнить почти любую оптимизацию, которую вы выбрали в окне компиляции, если вы готовы проанализировать и проанализировать код в своем собственном препроцессоре. В большинстве версий unix тривиальная задача-переопределить неявные правила, вызывающие компилятор, чтобы вызвать собственный пользовательский шаг, прежде чем он попадет в компилятор.