Могу ли я вычислить pow (10, x) во время компиляции на c?
-
21-08-2019 - |
Вопрос
Возможно ли вычислить pow (10, x) во время компиляции?
У меня есть процессор без поддержки с плавающей запятой и медленного целочисленного деления.Я пытаюсь выполнить как можно больше вычислений во время компиляции.Я могу значительно ускорить одну конкретную функцию, если передам обе x
и C/pow(10,x)
в качестве аргументов (x и C всегда являются постоянными целыми числами, но это разные константы для каждого вызова).Мне интересно, могу ли я сделать эти вызовы функций менее подверженными ошибкам, введя макрос, который выполняет 1/pow(10,x)
автоматически, вместо того, чтобы заставлять программиста вычислять это?
Есть ли какой-то трюк с препроцессором?Могу ли я заставить компилятор оптимизировать вызов библиотеки?
Решение
Вы можете использовать научную нотацию для значений с плавающей запятой, которая является частью языка C.Это выглядит примерно так:
e = 1.602E-19 // == 1.602 * pow(10, -19)
Число перед E
( тот самый E
может быть, большой или маленький 1.602e-19
) - это дробная часть, где в качестве последовательности цифр со знаком после E
является экспоненциальной частью.По умолчанию номер имеет следующий тип double
, но вы можете присоединить суффикс с плавающей запятой (f
, F
, l
или L
) если вам нужен float
или long double
.
Я бы не рекомендовал упаковывать эту семантику в макрос:
- Это не будет работать для переменных, значений с плавающей запятой и т.д.
- Научная нотация более удобочитаема.
Другие советы
Существует очень мало возможных значений, прежде чем вы переполните int (или даже long).Для наглядности оформите это в виде таблицы!
Редактировать:Если вы используете значения с плавающей запятой (похоже, что это так), то нет, будет невозможно вызвать функцию pow() во время компиляции без фактического написания кода, который выполняется в процессе make и выводит значения в файл (например, файл заголовка), который затем компилируется.
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.
Вы можете сделать это с помощью 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 в отдельные файлы и настраиваю свои правила создания файлов.Таким образом, ваш код будет защищен от уродливых навязчивых определений 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
Надеюсь, это поможет!
Последние версии GCC (около 4.3 ) добавили возможность использовать GMP и MPFR для выполнения некоторых оптимизаций во время компиляции путем оценки более сложных функций, которые являются постоянными.Такой подход делает ваш код простым и переносимым, и вы доверяете компилятору выполнять тяжелую работу.
Конечно, есть пределы тому, что он может сделать. Вот ссылка на описание в списке изменений, который включает в себя список функций, которые поддерживаются этим."пау" - это одно из них.
Если вам просто нужно использовать значение в время компиляции, используйте научная нотация как 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, когда показатель нечетный, размер таблицы теперь составляет всего половину.
К сожалению, вы не можете использовать препроцессор для предварительного вычисления вызовов библиотеки.Если x является целым числом, вы могли бы написать свою собственную функцию, но если это тип с плавающей запятой, я не вижу никакого хорошего способа сделать это.
воспроизведение bdonlan на высоте, но имейте в виду, что вы можете выполнить практически любую оптимизацию, выбранную вами в окне компиляции, при условии, что вы готовы анализировать код в своем собственном пользовательском препроцессоре.В большинстве версий unix переопределение неявных правил, которые заставляют компилятор вызывать ваш собственный пользовательский шаг до того, как он попадет в компилятор, является тривиальной задачей.