2010-03-28 4 views
17

Проблема.C++ с плавающей запятой точность: 3015/0.00025298219406977296

Компилятор Microsoft Visual C++ 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; 

результат вычисления (т.е. "е") является 11917835,000000000 (((неподписанных __int64) (& е)) == 0x4166bb4160000000), хотя это должно быть 11917834,814763514 (т.е. ((без знака __int64) (& f)) == 0x4166bb415a128aef).
I.e. дробная часть теряется.
К сожалению, мне нужна дробная часть, чтобы быть правильной.

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

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

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

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

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

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

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

ответ

15

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

+1

Это правильный ответ. Программа использует Direct3D, и, конечно же, расчет происходит после создания устройства. Самое смешное, что я знал о D3D, регулирующем точность FPU, но я полностью забыл об этом, потому что я не видел эту ошибку за последние несколько лет. Проблема решена. – SigTerm

+1

Какой флаг следует использовать при создании устройства? Существует ли такая же проблема с Direct2D? – dalle

1

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

#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

Нет, я не печатаю номер, результат берется непосредственно из окна «смотреть». – SigTerm

+0

Вы пробовали распечатать его? Возможно, ошибка находится в окне просмотра! –

+0

@Martin Окно часов показывает полную точность. –

4

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

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

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

+0

Невозможно отличить отличную точность, как ясно показывает демонтаж. –

+0

Не на этих трех строках, во всяком случае. –

2

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

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

+3

Ему не нужен 11917834.814763514100059144562708, ему просто нужно 11917834.814763514. Отказ от производительности и памяти просто для того, чтобы получить точность, встроенную в машину, кажется немного иррациональным (простить каламбур). – Gabe

+0

Несомненно, мы не имеем права рассчитывать на точность, но мы по-прежнему имеем право запросить этот уровень правильности, который обещает нам спецификация с плавающей запятой! – AakashM

+0

Не обижайтесь, но я думаю, что использование bignums только для одного вычисления слишком много, по крайней мере в этом случае. – SigTerm

0

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

Смежные вопросы