Извлечь дробную часть двойного * эфективно * в С - PullRequest
19 голосов
/ 08 апреля 2011

Я хочу взять IEEE double и удалить любую целочисленную часть наиболее эффективным способом.

Я хочу

1035 ->0
1045.23->0.23
253e-23=253e-23

Меня не волнует правильная обработкаденормалы, бесконечности или NaNs.Я не возражаю против небольшого переворота, так как знаю, что работаю с двойными кодами IEEE, поэтому он должен работать на разных компьютерах.

Код без ветвлений будет гораздо предпочтительнее.

Моя первая мысль (в псевдоcode)

char exp=d.exponent;
(set the last bit of the exponent to 1)
d<<=exp*(exp>0);
(& mask the last 52 bits of d)
(shift d left until the last bit of the exponent is zero, decrementing exp each time)
d.exponent=exp;

Но проблема в том, что я не могу придумать эффективный способ сдвига d влево до тех пор, пока последний бит показателя степени не станет равным нулю, плюс, похоже, потребуется вывести ноль, если всепоследние биты не были установлены.Похоже, это связано с проблемой логарифма с основанием 2.

Помощь с этим алгоритмом, а также любые другие, более эффективные, будет высоко оценена.

Вероятно, я должен отметить, что причина, по которой я хочу получить код без ответвлений, заключается в том, чтоЯ хочу, чтобы это эффективно векторизовало.

Ответы [ 6 ]

34 голосов
/ 08 апреля 2011

Как насчет чего-то простого?

double fraction = whole - ((long)whole);

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

12 голосов
/ 08 апреля 2011

Оптимальная реализация полностью зависит от целевой архитектуры.

На современных процессорах Intel этого можно достичь с помощью двух инструкций: roundsd и subsd, но это не может быть выражено в portable C-коде.

На некоторых процессорах самый быстрый способ сделать это с целочисленными операциями над представлением с плавающей запятой. На ум приходит ранний Atom и многие ARM-процессоры.

На некоторых других процессорах самое быстрое - привести к целому и обратно, затем вычесть, разветвить для защиты больших значений.

Если вы собираетесь обрабатывать много значений, вы можете установить режим округления с округлением до нуля, затем сложить и вычесть +/- 2 ^ 52 к числу, усеченному до целого числа, а затем вычесть из оригинала значение, чтобы получить дробь. Если у вас нет SSE4.1, но у вас есть другой современный процессор Intel и вы хотите векторизовать, это, как правило, лучшее, что вы можете сделать. Однако это имеет смысл, только если у вас есть много значений для обработки, потому что изменение режима округления довольно дорого.

На других архитектурах другие реализации являются оптимальными. В общем, не имеет смысла говорить об «эффективности» программ на Си; только эффективность конкретной реализации на конкретной архитектуре.

10 голосов
/ 08 апреля 2011
#include <math.h>
double fraction = fmod(d, 1.0);
7 голосов
/ 13 апреля 2014

Предложение

Функция remainder вычисляет остаток, но не целую часть, как modf:

#include <math.h>

double fracpart(double input)
{
    return remainder(input, 1.);
}

Это наиболее эффективный (и переносимый) способ, поскольку он не вычисляет ненужные значения для выполнения работы (см. modf, (long), fmod и т. Д.)


Benchmark

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

Ниже приведены измерения времени для 65536 вычислений (скомпилировано с Clang с отключенными оптимизациями):

method 1 took 0.002389 seconds (using remainder)
method 2 took 0.000193 seconds (casting to long)
method 3 took 0.000209 seconds (using floor)
method 4 took 0.000257 seconds (using modf)
method 5 took 0.010178 seconds (using fmod)

Снова с Clang, на этот раз используя флаг -O3:

method 1 took 0.002222 seconds (using remainder)
method 2 took 0.000000 seconds (casting to long)
method 3 took 0.000000 seconds (using floor)
method 4 took 0.000223 seconds (using modf)
method 5 took 0.010131 seconds (using fmod)

Оказывается, простейшее решение, похоже, дает наилучшие результаты на большинстве платформ, а конкретные методы для выполнения этой задачи (fmod, modf, remainder) на самом деле очень медленные!

3 голосов
/ 04 декабря 2015

Некоторые профилирования и эксперименты с использованием C ++ в Microsoft Visual Studio 2015 показывают, что лучший метод для положительных чисел:

double n;
// ...
double fractional_part = n - floor(n);

Это быстрее, чем modf, и, как уже упоминалось, функция остатка округляется до ближайшего целого числа и поэтому не используется.

3 голосов
/ 26 июля 2012

Стандартная функция библиотеки modf решает эту проблему довольно аккуратно.

#include <math.h>
/*...*/
double somenumber;
double integralPart;
double fractionalPart = modf(somenumber, &integralPart);

Это должно делать то, что вы просили, переносимо и достаточно эффективно.

Недокументированная деталь - может ли второй аргумент быть NULL, а затем избегать временной составляющей, что было бы желательно при использовании, подобном описанному вами.

К сожалению, многие реализации не поддерживают NULL для второго аргумента, поэтому придется использовать временное значение, независимо от того, используете ли вы это значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...