Подтвердить что ты не робот

Извлечь дробную часть двойного * эффективно * в C

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

Я хочу

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

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

Непрерывный код будет очень предпочтительным.

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

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.

Помощь с этим алгоритмом или любыми лучшими будет оценена.

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

#include <math.h>
double fraction = fmod(d, 1.0);

Ответ 4

Предложение

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

#include <math.h>

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

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


Benchmark

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

Ниже приведены измерения времени для вычислений 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) на самом деле супер-медленны!

Ответ 5

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

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

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

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

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

Ответ 6

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

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

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