Является ли (float) (1.2345f * 6.7809) более точным, чем 1.2345f * 6.7809f?
У меня есть несколько блоков кода, которые делают:
float total = <some float>;
double some_dbl = <some double>;
total *= some_dbl;
Это вызывает предупреждение компилятора, которое я хочу заткнуть, но мне не нравится отключать такие предупреждения - вместо этого я предпочитаю явно использовать типы по мере необходимости. Что заставило меня думать... это (float)(total * some_dbl)
более точно, чем total * (float)some_dbl
? Является ли это компилятором или платформой?
Пример лучшего кода (связанный ниже):
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main() {
double d_total = 1.2345678;
float f_total = (float)d_total;
double some_dbl = 6.7809123;
double actual = (d_total * some_dbl);
float no_cast = (float)(f_total * some_dbl);
float with_cast = (float)(f_total * (float)some_dbl);
cout << "actual: " << setprecision(25) << actual << endl;
cout << "no_cast: " << setprecision(25) << no_cast << endl;
cout << "with_cast: " << setprecision(25) << with_cast << endl;
cout << "no_cast, nextafter: " << setprecision(25) << nextafter(no_cast, 500.0f) << endl;
cout << endl;
cout << "Diff no_cast: " << setprecision(25) << actual - no_cast << endl;
cout << "Diff with_cast: " << setprecision(25) << with_cast - actual << endl;
return 0;
}
Edit:
Итак, я выстрелил. С примерами, которые я попробовал, я быстро нашел, где total * (float)(some_dbl)
представляется более точным. Я предполагаю, что это не всегда так, но это скорее удача ничьей, или компилятор усекает двойники, чтобы плавать, а не округлять, что приводит к потенциально худшим результатам. См.: http://ideone.com/sRXj1z
Изменить 2: Я подтвердил с помощью std::nextafter
, что (float)(total * some_dbl)
возвращает усеченное значение и обновляет связанный код. Удивительно: если компилятор в этом случае всегда усекает удваивает, то вы можете сказать (float)some_dbl <= some_dbl
, что затем означает with_cast <= no_cast
. Однако, это не так! with_cast
не только больше, чем no_cast
, но и ближе к фактическому значению, что является неожиданным, учитывая, что мы отбрасываем информацию до того, как произойдет умножение.
Ответы
Ответ 1
Это будет иметь значение в зависимости от размера задействованных чисел, потому что double
имеет не только более высокую точность, но также может содержать числа, превышающие float
. Здесь образец, который покажет один такой экземпляр:
double d = FLT_MAX * 2.0;
float f = 1.0f / FLT_MAX;
printf("%f\n", d * f);
printf("%f\n", (float)d * f);
printf("%f\n", (float)(d * f));
И вывод:
2.000000
inf
2.000000
Это происходит потому, что, хотя float
может, очевидно, удерживать результат вычисления - 2.0
, он не может удерживать промежуточное значение FLT_MAX * 2.0
Ответ 2
Если вы выполните операцию, то компилятор преобразует переменные в самый большой тип данных этой операции. Здесь он двойной. По-моему, операция: (float) (var1f * var2) имеет большую точность.
Ответ 3
Я тестировал его, и они не равны. Результат ниже true
. http://codepad.org/3GytxbFK
#include <iostream>
using namespace std;
int main(){
double a = 1.0/7;
float b = 6.0f;
float c = 6.0f;
b = b * (float)a;
c = (float)((double)c * a);
cout << (b-c != 0.0f) << endl;
return 0;
}
Это приводит меня к разуму: эффект от результата умножения, выраженный как double
до a float
, будет иметь больше шансов округлить. Некоторые биты могут упасть с конца с помощью умножения float
, которое было бы правильно учтено, когда умножение выполняется на double
, а затем добавлено к float
.
Кстати, я выбрал 1/7 * 6, потому что он повторяется в двоичном формате.
Изменить: При исследовании кажется, что округление должно быть одинаковым как для преобразования из double в float, так и для умножения поплавков, по крайней мере, в реализации, соответствующей IEEE 754. https://en.wikipedia.org/wiki/Floating_point#Rounding_modes
Ответ 4
Основываясь на цифрах с вашего дампа кода, двумя соседними возможными значениями float
являются:
d1 = 8.37149524...
d2 = 8.37149620...
Результат выполнения умножения в двойной точности:
8.37149598...
который находится между этими двумя, конечно. Преобразование этого результата в float
определяется реализацией относительно того, округляется ли он вверх или вниз. В результатах вашего кода конверсия выбрала d1
, что разрешено, хотя оно и не является самым близким. Умножение с смешанной точностью заканчивалось d2
.
Таким образом, мы можем заключить, несколько неинтуитивно, что выполнение вычисления удвоений в двойной точности, а затем преобразование в float
в некоторых случаях менее точное, чем выполнение целиком в точности float
!