Вопрос

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

#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.

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

Это было полезно?

Решение

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

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

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

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

Другие советы

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

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

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

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

Процедура cos(x) == cos(x), созданная в режиме выпуска:

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

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

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

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

Компилятор мог сгенерировать код, который в конечном итоге сравнивает 64-разрядное двойное значение с 80-разрядным внутренним регистром с плавающей запятой.Проверка значений с плавающей запятой на равенство подвержена такого рода ошибкам - вам почти всегда лучше выполнять "нечеткое" сравнение, например (fabs(val1 - val2) < ЭПСИЛОН), а не (val1 == val2).

Увеличение и тестирование значения с плавающей запятой в качестве переменной управления циклом, как правило, является очень плохой идеей.Создайте отдельный 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);
}

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

if ( (float)cos(x) == (float)cos(x) )
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top