Вопрос

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

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

Это то, что я пробовал до сих пор (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;

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

Это было полезно?

Решение

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

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;

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

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

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

Поплавок имеет только одну часть.

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

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

Что такое BigQuotient? Это двойной. Так что у него есть две части. BigQuotient равен BigQuotientBig + BigQuotientLittle. Продолжая:

double littlequotient = dividendlittle / divisor;

Опять же, мелочи мелочь - мелочь + мелочь + мелочь. Теперь мы добавляем быцы:

double quotient = bigquotient + littlequotient;

Как мы вычисляем это? Кваинт имеет две части. quotientbig будет установлен на BigQuotientBig. QuotientLittle будет установлен на BigQuotientLittle + LiteBoTientBig. LittlequotientLittle отбрасывается.

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

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

Хорошо, что такое R1? Это поплавок. Так что у него только одна часть. R1 - BigquotientBig.

float r2 = f2 / divisor;

Что такое R2? Это поплавок. Так что у него только одна часть. R2 маленький кухня.

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

Вы добавляете их вместе, и вы получаете BigQuotientBig + LiteBoTientBig. Что случилось с BigQuotientLittle? Там вы потеряли 32 бита точности, и поэтому он не должен удивительно, что вы получаете неточности 32 бита по пути. Вы не придумали во всех правильных алгоритме для приближения 64 -битной арифметики в 32 битах.

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

Это теперь ясно?

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

Да, пока вы:

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

Обновлять

Прочитав ваши комментарии (двойная точность - это требование), мой обновленный ответ:

Нет.

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

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

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

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

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% времени.

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

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

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

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

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