Предел точности с плавающей запятой С++: 3015/0.00025298219406977296

Проблема.

Компилятор Microsoft Visual С++ 2005, 32-битные окна xp sp3, amd 64 x2 cpu.

код:

double a = 3015.0; 
double b = 0.00025298219406977296;
//*((unsigned __int64*)(&a)) == 0x40a78e0000000000  
//*((unsigned __int64*)(&b)) == 0x3f30945640000000  
double f = a/b;//3015/0.00025298219406977296;

результат вычисления (т.е. "f" ) равен 11917835.000000000 (((unsigned __int64) (& f)) == 0x4166bb4160000000), хотя это должно быть 11917834.814763514 (т.е. ((без знака __int64) (& f)) = = 0x4166bb415a128aef).
То есть дробная часть теряется.
К сожалению, мне нужна дробная часть, чтобы быть правильной.

Вопросы:
1) Почему это происходит?
2) Как я могу исправить проблему?

Дополнительная информация:
0) Результат берется непосредственно из окна "watch" (он не был напечатан, и я не забыл установить точность печати). Я также предоставил шестнадцатеричный дамп переменной с плавающей запятой, поэтому я абсолютно уверен в результате вычисления.
1) Разборка f = a/b:

fld         qword ptr [a]  
fdiv        qword ptr [b]  
fstp        qword ptr [f]  

2) f = 3015/0,00025298219406977296; дает правильный результат (f == 11917834.814763514, ((unsigned __int64) (& f)) == 0x4166bb415a128aef), но похоже, что в этом случае результат просто вычисляется во время компиляции:

fld         qword ptr [[email protected] (828EA0h)]  
fstp        qword ptr [f]  

Итак, как я могу исправить эту проблему?

P.S. Я нашел временное обходное решение (мне нужна только дробная часть деления, поэтому я просто использую f = fmod (a/b)/b на данный момент), но мне все же хотелось бы знать, как правильно исправить эту проблему - double точность должна быть 16 десятичных цифр, поэтому такой расчет не должен вызывать проблем.

Ответы

Ответ 1

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

Ответ 2

Интересно, что если вы объявите как a, так и b как float, вы получите ровно 11917835.000000000. Поэтому я предполагаю, что где-то происходит преобразование в единую точность, либо в интерпретации констант, либо позже в вычислениях.

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

Изменить: действительно ли вы подтвердили, что скомпилированная программа генерирует неверный результат? В противном случае наиболее вероятным кандидатом для (ошибочного) преобразования одной точности будет отладчик.

Ответ 3

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

#include <iostream>
#include <iomanip>

int main() { 
    double a = 3015.0; 
    double b = 0.00025298219406977296;
    double f = a/b;

    std::cout << std::fixed << std::setprecision(15) << f << std::endl;
    return 0;
}

Это дает:

+11917834,814763514000000

Что выглядит правильно для меня. Я использую VС++ 2008 вместо 2005, но, я думаю, разница в коде, а не в компиляторе.

Ответ 4

Если вам нужна точная математика, не используйте плавающие точки.

Сделайте себе одолжение и получите библиотеку BigNum с поддержкой рационального числа.

Ответ 5

Вы уверены, что изучаете значение f сразу после команды fstp? Если вы включили оптимизацию, возможно, окно просмотра может отображать значение, взятое в какой-то более поздней точке (это кажется немного правдоподобным, поскольку вы говорите, что смотрите на дробную часть f позже - некоторые инструкции завершаются, маскируя его как-то?)