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

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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

До переполнения int (или даже длинного) существует очень мало значений. Для наглядности сделайте это таблицей!

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

Ответ 3

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.

Ответ 4

Вы можете сделать это с помощью 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)};

Ответ 5

Собственно, используя препроцессор 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

Ответ 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

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

Конечно, существуют ограничения на то, что он может сделать. Здесь ссылка на описание в списке изменений, которая включает список функций, которые поддерживаются этим. "pow" - один из них.

Ответ 8

Если вам просто нужно использовать значение во время компиляции, используйте научную нотацию, такую как 1e2 для pow(10, 2)

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

Ответ 9

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

Ответ 10

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