2009-09-01 2 views
4

Я отлаживал свой проект и не мог найти ошибку. Наконец я нашел его. Посмотрите на код. Вы думаете, что все в порядке, и результат будет «ОК, хорошо! Хорошо!», Не так ли? Теперь скомпилируйте его с помощью VC (я пробовал vs2005 и vs2008).Visual C++ math.h ошибка

#include <math.h> 
#include <stdio.h> 


int main() { 
    for (double x = 90100.0; x<90120.0; x+=1) 
    { 
     if (cos(x) == cos(x)) 
      printf ("x==%f OK!\n", x); 
     else 
      printf ("x==%f FAIL!\n", x); 
    } 

    getchar(); 
    return 0; 
} 

Волшебная двойная константа - 90112.0. Когда x < 90112.0 все в порядке, когда x> 90112.0 - Нет! Вы можете изменить cos на грех.

Любые идеи? Не забывайте, что sin и cos являются периодическими.

+3

Вы проживаете ошибка - вы не локализовать. (HTTP: //en.wikipedia.org/wiki/Internationalization_and_localization) –

+0

oops, извините: - [ – f0b0s

+4

Я ВЫСОКО сомневаюсь, что есть ошибка в math.h ... –

ответ

36

Может быть это: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.18

Я знаю, что это трудно принять, но арифметика с плавающей просто не работает, как и большинство людей ожидают. Хуже того, некоторые из различий зависят от деталей вашего оборудования с плавающей запятой вашего конкретного компьютера и/или настроек оптимизации, которые вы используете в своем конкретном компиляторе. Возможно, вам это не понравится, но так оно и есть. Единственный способ «достать», чтобы выделить свои предположения о том, как вещи должны вести себя и принимать вещи, как они на самом деле сделать ведут себя ...

(с акцентом на слово «часто», а поведение зависит от вашего оборудования, компилятора и т. д.): вычисления с плавающей запятой и сравнения часто выполняются специальным оборудованием, которое часто содержит специальные регистры, и эти регистры часто имеют больше бит, чем double. Это означает, что вычисления промежуточных вычислений с плавающей запятой часто имеют больше бит, чем sizeof(double), и когда значение с плавающей запятой записывается в ОЗУ, оно часто усекается, часто теряя некоторые бит точности ...

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

+1

Самая полезная строка из связанной статьи: «Практически все работает с плавающей точкой, отличается от того, как большинство программистов склонны думать, что это должно работать. Если вы собираетесь использовать плавающие точки, вам нужно узнать, как это работает. – Jherico

+0

@ Jherico Я почти включил ту же самую цитату :) –

+1

oops. спасибо, это был хромой вопрос. Я знал, что я не должен сравнивать поплавки с разных результатов, но не одинаковые результаты тоже. – f0b0s

5

Вы должны никогда не сравнить двойников равенства в большинстве случаев. Вы не можете получить то, что ожидаете.

Регистры с плавающей точкой могут иметь разный размер, чем значения памяти (в современных компьютерах intel, регистры FPU - 80 бит и 64 бит). Если компилятор генерирует код, который вычисляет первый косинус, затем сохраняет значение в памяти, вычисляет второй косинус и сравнивает значение в памяти с регистром, тогда значения могут отличаться (из-за ошибок округления от 80 до 64 бит) ,

Значения с плавающей запятой немного сложны. Google для сравнения с плавающей запятой.

+1

Очень сложно сказать, что «вы не должны сравнивать двойники за равенство». Совершенно нормально сравнивать двойники, вы должны просто знать, что ваш компилятор будет делать с этим сравнением, и как правильно обойти известные проблемы, подобные этому. –

+1

true. Никогда не говори никогда. –

1

Компилятор, возможно, сгенерировал код, который заканчивает сравнение 64-разрядного двойного значения с 80-битным внутренним регистром с плавающей запятой . Тестирование значений с плавающей запятой для равенства склонно к подобным ошибкам - вы почти всегда лучше выполняете «нечеткое» сравнение, например (fabs (val1 - val2) < EPSILON), а не (val1 == val2).

-1

Как сблизиться с проблемой?Изменить если блок:

if ((float)cos(x) == (float)cos(x))
10

Как уже отмечалось, математическая библиотека VS делает свои вычисления на x87 FPU и генерируя 80-разрядные результаты, даже если тип является двойной.

Таким образом:

  1. сов() вызывается, и возвращается с соз (х) на вершине стека x87 как 80bit поплавком
  2. сов (х) извлекается из стека x87 и хранится в памяти как двойной; это приводит к его округляются до 64-битной плавающей точки, которая изменяет свое значение
  3. сов() вызываются, и возвращается с соз (х) на вершине стека x87 как 80bit поплавок
  4. закругленное значение загружается в стек x87 из памяти
  5. округленные и неосновные значения cos (x) сравниваются неравномерно.

Многие математические библиотеки и компиляторы защитить вас от этого либо делать вычисления в 64-битной плавающей точкой в ​​SSE регистры при наличии, или заставляя значения должны быть сохранены и скругленные перед сравнением, или путем сохранения и перезагрузки конечный результат в фактическом вычислении cos(). Комбинация/библиотека, с которой вы работаете, не так прощает.

5

В сов (х) == соз (х) процедура генерируется в режиме выпуска:

 
00DB101A call  _CIcos (0DB1870h) 
00DB101F fld   st(0) 
00DB1021 fucompp 

Значение вычисляется один раз, а затем клонируют, а затем по сравнению с самим собой - результат будет нормально

то же самое в режиме отладки:

 
00A51405 sub   esp,8 
00A51408 fld   qword ptr [x] 
00A5140B fstp  qword ptr [esp] 
00A5140E call  @ILT+270(_cos) (0A51113h) 
00A51413 fld   qword ptr [x] 
00A51416 fstp  qword ptr [esp] 
00A51419 fstp  qword ptr [ebp-0D8h] 
00A5141F call  @ILT+270(_cos) (0A51113h) 
00A51424 add   esp,8 
00A51427 fld   qword ptr [ebp-0D8h] 
00A5142D fucompp   

Теперь, происходят странные вещи.
1. Х загружен на fstack (Х, 0)
2. Х хранится на обычном стеке (усечения)
3. Косинус вычисляется, результат на стек флоат
4. Х загружается снова
5 X хранится в обычном стеке (усечение, так как на данный момент мы «симметричны»)
6. Результат 1-го косинуса, который был в стеке, сохраняется в памяти, теперь происходит другое усечение для 1-го значения
7. Косинус вычисляется, 2-й результат, если в поплавковой стеке, но это значение было усечено только один раз
8. 1-е значение загружается на фстак, но это значение было усечено дважды (один раз до вычисления косинуса, один раз после)
9. Эти 2 значения сравниваются - мы получаем ошибки округления.

0

Приращение и тестирование значения поплавка как переменной управления контуром, как правило, является действительно плохой идеей. Создайте отдельный int LCV только для циклического включения, если нужно.

В этом случае проще:

for (int i = 90100; i<90120; i+=1) { 
    if (cos(i) == cos(i)) 
     printf ("i==%d OK!\n", i); 
    else 
     printf ("i==%d FAIL!\n", i); 
} 
+0

ошибка нет, вопрос был примерно двойной. Я мог бы использовать ваш код, но с cos ((double) i) – f0b0s

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