Потеря точности с плавающей запятой c ++: 3015 / 0.00025298219406977296 - PullRequest
17 голосов
/ 28 марта 2010

проблема.

Компилятор Microsoft Visual C ++ 2005, 32-разрядная версия Windows XP SP3, 64-разрядный процессор amd.

Код:

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 ( ((без знака __int64 ) (& f)) == 0x4166bb4160000000), хотя он должен быть 11917834.814763514 (т. Е. ((без знака __int64 *) 1011 *) (& f)) == 0x4166bb415a128aef).
То есть дробная часть теряется.
К сожалению, мне нужна дробная часть, чтобы быть правильной.

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

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

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

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

fld         qword ptr [__real@4166bb415a128aef (828EA0h)]  
fstp        qword ptr [f]  

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

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

Ответы [ 5 ]

15 голосов
/ 28 марта 2010

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

4 голосов
/ 28 марта 2010

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

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

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

2 голосов
/ 28 марта 2010

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

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

1 голос
/ 28 марта 2010

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

#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

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

0 голосов
/ 28 марта 2010

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

...