двойное деление с помощью двух поплавков? - PullRequest
0 голосов
/ 04 ноября 2010

Я хотел бы сделать двойное деление, используя два числа с плавающей запятой (похоже, что Direct Compute не поддерживает двойное деление).

Это возможно?

Это то, что я пробовал до сих пор (код c #, позже должен быть HLSL):

int count = 7;
double value = 0.0073812398871474;
float f1 = (float)value;
float f2 = (float)((value - f1));
float r1 = f1 / count;
float r2 = f2 / count;
double result = (double)r1 + (double)r2;

0,00105446285765182 (результат)

0,00105446284102106 (правильный результат)

Это связано с округлением в f1. Если значение равно:

 double value = 0.0073812344471474;

Тогда результат верный.

Ответы [ 5 ]

5 голосов
/ 04 ноября 2010

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

int count = 7;
double value = 0.0073812398871474;
double r = (double) (1.0f / count); // approximate reciprocal
r = r * (2.0 - count*r); // much better approximation
r = r * (2.0 - count*r); // should be full double precision by now.
double result = value * r;
3 голосов
/ 04 ноября 2010

Видимо, ваша арифметическая ошибка не сразу вам понятна.Позвольте мне изложить это по буквам.

Предположим, что двойник состоит из двух частей: большой и малой, каждая из которых имеет точность примерно 32 бита.(Это не совсем то, как удваивается работа, но это подойдет для наших целей.)

У поплавка есть только одна часть.

Представьте, что мы делали это 32 бита за раз, но все держалиудваивается:

double divisor = whatever;
double dividend = dividendbig + dividendlittle;
double bigquotient = dividendbig / divisor;

Что такое большой коэффициент?Это двойной.Так что у него есть две части.bigquotient равно bigquotientbig + bigquotientlittle.Продолжая:

double littlequotient = dividendlittle / divisor;

снова, маленький коэффициент - маленький коэффициент большой + маленький коэффициент - маленький.Теперь мы добавим коэффициенты:

double quotient = bigquotient + littlequotient;

Как мы можем это вычислить?частное имеет две части.Соотношение будет установлено на BigquotientBig.частное маленькое будет установлено как большое частное + маленькое частное.littlequotientlittle отбрасывается.

Теперь предположим, что вы делаете это поплавками.У вас есть:

float f1 = dividendbig;
float f2 = dividendlittle;
float r1 = f1 / divisor;

ОК, что такое r1?Это поплавок.Так что это только одна часть.r1 - большое число.

float r2 = f2 / divisor;

Что такое r2?Это поплавок.Так что это только одна часть.r2 малозначно.

double result = (double)r1 + (double)r2;

Вы складываете их вместе и получаете большой коэффициент + маленький коэффициент большой. Что случилось с bigquotientlittle? Вы потеряли 32 бита там, и поэтому неудивительно, что вы получите неточности 32 бита на этом пути. Вы вообще не придумали правильный алгоритм для аппроксимации 64-битной арифметики в 32 битах.

Чтобы вычислить (big + little)/divisor, вы не можете просто сделать (big / divisor) + (little / divisor).Это правило алгебры не применяется, когда вы округляете во время каждого деления!

Теперь понятно?

3 голосов
/ 04 ноября 2010

Возможно ли это?

Да, пока вы:

  • Примите неизбежную потерю точности
  • Имейте в видуВо-первых, не все двойники вписываются в числа с плавающей точкой

    номер

1 голос
/ 04 ноября 2010

Так как насчет чего-то вроде

result = value * (double)(1f / (float)count);

Там вы делите только два поплавка. У меня там больше бросков, чем нужно, но важна концепция.

Edit:
Итак, вы беспокоитесь о разнице между фактическим и округленным, верно? так что делайте это снова и снова, пока не поймете все правильно!

double result = 0;
double difference = value;
double total = 0;
float f1 = 0;
while (difference != 0)
{
    f1 = (float)difference;
    total += f1;
    difference = value - total;
    result += (double)(f1 / count);
}

... но вы знаете, простой ответ по-прежнему "Нет". Это все еще даже не ловит ВСЕ ошибки округления. Из моих тестов он снижает погрешности до 1e-17 самое большее, примерно в 30% случаев.

0 голосов
/ 04 ноября 2010

В комментарии вы говорите:

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

Значение IEEE-754 single precision имеет 24 значащих двоичных разряда. Значение double precision имеет 53 значащих цифры. Вы даже не можете представить значение двойной точности как два значения одинарной точности без потери точности, тем более арифметика с таким представлением.

Тем не менее, возможно сделать правильно округленное деление двойной точности, используя только преобразования между двойной и единственной, вычитанием / сложением двойной точности и операциями с одинарной точностью, но это довольно сложно, если вы действительно хотите сделать это правильно. Вам нужно правильное округление IEEE-754 или просто правильный ответ до последнего бита или двух?

...