Как заставить pow (float, int) вернуть float
перегруженная функция float pow(float base, int iexp )
был удален в C++11 и теперь pow
возвращает double
. В моей программе я вычисляю много из них (с одной точностью), и меня интересует наиболее эффективный способ, как это сделать.
есть ли какая-то специальная функция (в стандартных библиотеках или любой другой) с вышеуказанной подписью?
если нет, то лучше (с точки зрения производительности в одной точности) явно привести результат pow
на float
перед любым другим операции (которые бросали бы все остальное в double
) или cast iexp
на float
и используйте перегруженную функцию float pow(float base, float exp)
?
EDIT: зачем мне нужно float
и не используйте double
?
основная причина-ОЗУ - мне нужны десятки или сотни ГБ, поэтому это сокращение является огромным преимуществом. Поэтому мне нужно от float
и float
. И теперь мне нужен самый эффективный способ добиться этого (меньше слепков, использовать уже оптимизированные алгоритмы и т. д.).
5 ответов
вы могли бы легко написать свой собственный fpow
С помощью возведение в квадрат.
float my_fpow(float base, unsigned exp)
{
float result = 1.f;
while (exp)
{
if (exp & 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
скучная часть:
этот алгоритм дает лучшую точность, которую можно архивировать с помощью float
типа / base / > 1
доказательство:
пусть мы хотим вычислить pow(a, n)
здесь a
база и n
- это экспонента.
Давайте определимся b1=a1, b2=a2, b3=a4, b4=a8и так далее.
затем an является продуктом над всеми такими bя гдеth бита в n.
Итак, мы заказали набор B={bk1, bk1,...бКН} и для любого j бит kj находится в n.
следующий очевидный алгоритм A может использоваться для минимизации ошибок округления:
- если B содержит один элемент, то результатом
- выберите два элемента p и q С B с минимальным модулем
- снять их с B
- вычислить продукт s = p * q и поставить его на B
- перейти к первому шагу
теперь давайте докажем, что элементы в B можно просто умножить слева направо без потери точности. Приходит форма того, что:
bj > б1 * b2*...* bj-1
, потому что bj =bj-1 * bj-1 =bj-1 * bj-2 * bj-2=...=bj-1 * bj-2*...* b1 * b1
С, b1 = a1 = a и его по модулю более одного затем:
bj > b1 * b2*...* bj-1
следовательно, мы можем заключить, что при умножении слева направо переменная аккумулятора меньше любого элемента из B.
затем выражение result *= base;
(за исключением самой первой итерации, конечно) умножение двух минимальных чисел из B, поэтому ошибка округления минимальна. Итак, код использует алгоритм A.
еще один вопрос, на который можно честно ответить только "неправильным вопросом". Или, по крайней мере: "ты действительно хочешь туда пойти?". float
теоретически требуется ca. 80% меньше умирает космос (для такого же количества циклов) и поэтому может быть гораздо дешевле для Навальной обработки. GPUs любовь float
по этому поводу.
Однако давайте посмотрим на x86 (по общему признанию, вы не сказали, на какой архитектуре вы находитесь, поэтому я выбрал наиболее распространенный). Цена в космосе уже уплачена. Ты буквально ничего не получить с помощью float
для расчетов. На самом деле, вы можете даже сбросить пропускная способность, потому что дополнительные расширения от float
до double
требуются и дополнительное округление до промежуточного float
точности. Другими словами, вы платите дополнительно, чтобы иметь менее точный результат. Обычно этого следует избегать, за исключением случаев, когда вам нужна максимальная совместимость с какой-либо другой программой.
см. комментарий Йенса. Эти параметры дают компилятору разрешение игнорировать некоторые языковые правила для достижения более высокой производительности. Излишне говорить, что иногда это может привести к обратным результатам.
есть два сценария, где float
может быть более эффективным, на x86:
- GPU (включая GPGPU), на самом деле многие графические процессоры даже не поддерживают
double
и если они это сделают, это обычно намного медленнее. Тем не менее, вы заметите только при выполнении очень многих вычислений такого рода. - процессор SIMD ака векторизация
вы бы знали, Если бы вы сделали GPGPU. Явная векторизация с использованием встроенных компиляторов также является выбором – который вы могли бы сделать, конечно, но это требует довольно анализа затрат и выгод. Возможно, ваш компилятор способен автоматически векторизовать некоторые циклы, но это обычно ограничивается "очевидными" приложениями, например, где вы умножаете каждое число в vector<float>
другое float
, и этот случай не так очевиден ИМО. Даже если ты pow
каждое число в таком векторе тем же int
компилятор может быть достаточно умен, чтобы векторизовать это эффективно, особенно если pow
находится в другом блоке перевода, и без эффективной генерации кода времени связи.
если вы не готовы рассмотреть возможность изменения всей структуры вашей программы, чтобы обеспечить эффективное использование SIMD (включая GPGPU), и вы не на архитектуре, где float
- Это действительно намного дешевле, по умолчанию, я предлагаю вам придерживаться double
непременно, и рассмотрим float
в лучшем случае формат хранения, который может быть полезен для сохранения ОЗУ или улучшения местоположения кэша (когда у вас есть большое из них). Даже тогда измерение-отличная идея.
тем не менее, вы можете попробовать алгоритм ivaigult (только с double
для промежуточного и для результата), который связан с классическим алгоритмом под названием египетские умножения (и множество других имен), только то, что операнды умножаются и не добавил. Я не знаю как!--17--> работает точно, но возможно, что этот алгоритм может быть быстрее в некоторых случаях. Опять же, вы должны быть ОКР о бенчмаркинге.
Если вы нацелились GCC вы можете попробовать
float __builtin_powif(float, int)
Я понятия не имею, насколько это сложно.
есть ли какая-то специальная функция (в стандартных библиотеках или любой другой) с вышеуказанной подписью?
к сожалению,я не знаю.
но, как многие уже упоминали бенчмаркинг необходим чтобы понять, есть ли вообще проблема.
я собрал быстрый тест онлайн. Стандарт код:
#include <iostream>
#include <boost/timer/timer.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <cmath>
int main ()
{
boost::random::mt19937 gen;
boost::random::uniform_real_distribution<> dist(0, 10000000);
const size_t size = 10000000;
std::vector<float> bases(size);
std::vector<float> fexp(size);
std::vector<int> iexp(size);
std::vector<float> res(size);
for(size_t i=0; i<size; i++)
{
bases[i] = dist(gen);
iexp[i] = std::floor(dist(gen));
fexp[i] = iexp[i];
}
std::cout << "float pow(float, int):" << std::endl;
{
boost::timer::auto_cpu_timer timer;
for(size_t i=0; i<size; i++)
res[i] = std::pow(bases[i], iexp[i]);
}
std::cout << "float pow(float, float):" << std::endl;
{
boost::timer::auto_cpu_timer timer;
for(size_t i=0; i<size; i++)
res[i] = std::pow(bases[i], fexp[i]);
}
return 0;
}
результаты тестов (быстрые выводы):
- gcc: c++11 последовательно быстрее, чем c++03.
-
лязг: действительно
int
-версия C++03, кажется, немного быстрее. Я не уверен, что это в пределах погрешности, так как я только запускаю тест онлайн. -
как: даже при вызове c++11
pow
Сint
кажется, немного больше производительным.
было бы здорово, если бы другие могли проверить, выполняется ли это для их конфигураций.
вместо этого попробуйте использовать powf (). Это функция C99, которая также должна быть доступна в C++11.