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

Есть ли какой-либо выигрыш в точности при кастинге в двойном и обратном направлении при выполнении флоат-деления?

В чем разница между двумя следующими?

float f1 = some_number;
float f2 = some_near_zero_number;
float result;

result = f1 / f2;

и

float f1 = some_number;
float f2 = some_near_zero_number;
float result;

result = (double)f1 / (double)f2;

Меня особенно интересуют очень маленькие значения f2, которые могут приводить к + бесконечности при работе с поплавками. Есть ли какая-то точность?

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

4b9b3361

Ответ 1

Я собираюсь считать двоичную арифметику с плавающей запятой IEEE 754 с float 32 бит и double 64 бит.

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

Преобразование от float до double является точным. Для бесконечных входов NaN или нулевого делителя это не делает различий. Учитывая конечный результат числа, стандарт IEEE 754 требует, чтобы результат был результатом действительного деления числа f1/f2, округленного до типа, используемого в делении.

Если это делается как деление float, которое является ближайшим float к точному результату. Если это делается как double деление, это будет ближайший double с дополнительным шагом округления для назначения result.

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

Для простого преобразования, если ответ очень близок к половине пути между двумя значениями float, два шага округления могут выбрать неправильный float. Я предположил, что это может также относиться к результатам разделения. Тем не менее, Паскаль Куок в комментарии к этому ответу обратил внимание на очень интересный документ Безвредное двойное округление базовой арифметики Операции Pierre Roux, утверждая, что двойное округление безвредно для нескольких операций, включая деление, в условиях, которые вытекают из предположений, которые я сделал в начале этого ответа.

Ответ 2

Если результат отдельного сложения, вычитания, умножения или деления с плавающей запятой немедленно сохраняется в float, то для промежуточных значений не будет улучшения точности с использованием double. Однако в тех случаях, когда операции объединены вместе, точность часто улучшается с использованием промежуточного типа с более высокой степенью точности, при условии, что каждый из них согласуется с их использованием. В Turbo Pascal около 1986 года код вроде:

Function TriangleArea(A: Single, B:Single, C:Single): Single
Begin
  Var S: Extended;  (* S stands for Semi-perimeter *)
  S := (A+B+C) * 0.5;
  TriangleArea := Sqrt((S-A)*(S-B)*(S-C)*S)
End;

будет расширять все операнды операций с плавающей запятой, чтобы напечатать Extended (80-битный float), а затем преобразовать их в одно- или двукратную точность при хранении в переменных этих типов. Очень хорошая семантика для цифровой обработки. Turbo C этой области вел себя аналогичным образом, но бесполезно не предоставил никакого числового типа, способного удерживать промежуточные результаты; отказ языков предоставлять тип переменной, который мог бы удерживать промежуточные результаты, привел к тому, что люди необоснованно критиковали концепцию промежуточного результата с более высокой точностью, когда реальной проблемой было то, что языки не смогли должным образом ее поддерживать.

В любом случае, если бы кто-то написал этот метод на современный язык, например С#:

    public static float triangleArea(float a, float b, float c)
    {
        double s = (a + b + c) * 0.5;
        return (double)(Math.Sqrt((s - a) * (s - b) * (s - c) * s));
    }

код будет хорошо работать, если компилятор будет продвигать операнды добавления до double перед выполнением вычисления, но это то, что он может или не может сделать. Если компилятор выполняет вычисление как float, точность может быть ужасной. При использовании приведенной выше формулы для вычисления площади равнобедренного треугольника с длинными сторонами 16777215 и короткой стороной 4, например, стремительное продвижение даст правильный результат 3.355443E + 7 при выполнении математики как float в зависимости от порядка операндов, дают 5.033165E + 7 [более 50% слишком большой] или 16777214.0 [более 50% слишком малы].

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

Хотя отдельные операции над float, которые будут немедленно сохранены в float, могут выполняться точно так же с типом float, как они могут быть с типом double, охотно продвигающие операнды часто помогают значительно, когда операции объединены. В некоторых случаях операции перегруппировки могут избежать проблем, вызванных потерей продвижения (например, приведенная выше формула использует пять добавлений, четыре умножения и квадратный корень, переписывая формулу как:

Math.Sqrt((a+b+c)*(b-a+c)*(a-b+c)*(a-c+b))*0.25

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

Ответ 3

"Усиление точности при двойном и обратном направлении при выполнении флоат-дивизиона?"
Результат зависит от других факторов, кроме только двух опубликованных методов.


C позволяет оценивать операции float на разных уровнях в зависимости от FLT_EVAL_METHOD. (См. Таблицу ниже) Если текущая настройка равна 1 или 2, два метода, отправленные OP, предоставят тот же ответ.

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

Из-за этого разделение float, которое переполняется или становится равным 0.0 (результат с полной потерей точности) из-за экстремальных значений float, и если оптимизация для последующих вычислений может фактически не превышать/под потоком, поскольку фактор переносился вперед как double.

Чтобы заставить фактор стать float для будущих вычислений в разгар возможных оптимизаций, код часто использует volatile

volatile float result = f1 / f2;

C не указывает точность математических операций, но обычное применение стандартов, таких как IEEE 754, обеспечивает единую операцию, например binary32 divide приведет к представлению ближайшего ответа. Если различие происходит в более широком формате, таком как double или long double, то более широкое преобразование отношения к float испытывает другой шаг округления, который в редких случаях приведет к другому ответу, чем прямой float/float.


FLT_EVAL_METHOD
-1 неопределимый; 0 оценивать все операции и константы только по диапазону и точности типа; 1 оценить операции и константы типа float и double для диапазон и точность типа double, оцените операции long double и константы для диапазона и точности типа long double.
2 оценить все операции и константы в диапазоне и точности long double.

Практические рекомендации:
Используйте float vs. double для экономии места при необходимости. (float обычно более узкая, редко такая же, как double). Если точность важна, используйте double (или long double).

Использование float vs. double для улучшения скорости может работать или не работать, так как основные операции платформы могут быть double. Это может быть быстрее, то же или медленнее - профиль узнать. Большая часть C была первоначально спроектирована с double, поскольку только уровень FP был выполнен за исключением double to/from float. Позже C добавил функции, такие как sinf(), чтобы облегчить быстрые, прямые операции float. Таким образом, чем более современный компилятор/платформа, тем более вероятно float будет быстрее. Снова: профиль, чтобы узнать.