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