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

StackOverflow https://stackoverflow.com/questions/1066099

  •  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.

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

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

Другие советы

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top