Является ли (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!