Существует ли значение с плавающей точкой x, для которого x-x == 0 является ложью?

В большинстве случаев я понимаю, что сравнительное испытание с плавающей точкой должно быть реализовано с помощью по диапазону значений (брюшной пресс (x-y) <эпсилон), но делает сам, вычитание подразумевает, что результатом будет нуль?

// can the assertion be triggered?
float x = //?;
assert( x-x == 0 )

Мое предположение - то, что nan/inf мог бы быть особыми случаями, но я больше интересуюсь тем, что происходит для простых значений.

править:

Я рад выбрать ответ, если кто-то может процитировать ссылку (стандарт плавающей точки IEEE)?

21
задан mskfisher 9 May 2012 в 16:27
поделиться

6 ответов

Как вы намекнули, inf - inf равно NaN , что не равно нулю. Точно так же NaN - NaN равно NaN . Однако верно, что для любого конечного числа с плавающей запятой x , x - x == 0,0 (в зависимости от режима округления результат x - x может быть отрицательным нулем, но отрицательный ноль сравнивается с 0,0 в арифметике с плавающей запятой).

Редактировать: немного сложно дать четкую ссылку на стандарты, потому что это новое свойство правил, установленных в стандарте IEEE-754. В частности, это следует из требования, чтобы операции, определенные в разделе 5, были правильно округлены. Вычитание - такая операция (раздел 5.4.1 «Арифметические операции»), и правильно округленный результат x - x - это ноль соответствующего знака (раздел 6.3, параграф 3):

Когда сумма двух операндов с {{ 1}} противоположные знаки (или разница двух операндов с одинаковыми знаками) равна ровно нулю, знак этой суммы (или разности) должен быть +0 для всех атрибуты направления округления, кроме roundTowardNegative; под этим атрибутом знак точной нулевой суммы (или разности) должен быть -0.

Таким образом, результат x - x должен быть +/- 0 и, следовательно, должен быть равен 0,0 (раздел 5.11, параграф 2):

При сравнении знак нуля не учитывается.

Дальнейшее редактирование: Это не означает, что компилятор с ошибками не может вызвать срабатывание этого утверждения. Ваш вопрос неоднозначный; не существует конечного числа с плавающей запятой x , такого что x - x == 0 ложно. Однако это не то, что проверяет опубликованный вами код; он проверяет, может ли определенное выражение в языке C-стиля давать ненулевое значение; в частности, на определенных платформах при определенных (непродуманных) оптимизациях компилятора два экземпляра переменной x в этом выражении могут иметь разные значения, что приводит к сбою утверждения ( особенно если x является результатом некоторого вычисления, а не постоянным, представимым значением). Это ошибка числовой модели на этих платформах, но это не значит, что этого не может быть.

59
ответ дан 29 November 2019 в 06:21
поделиться

Что касается того, что говорит Марк - посмотрите эту ссылку http: // www. parashift.com/c++-faq-lite/newbie.html#faq-29.18 . (Не уверен, что это применимо к вашей ситуации.)

1
ответ дан 29 November 2019 в 06:21
поделиться

Мой ответ на главный вопрос: «Существует ли значение x с плавающей запятой, для которого x-x == 0 ложно?» is: по крайней мере, реализация с плавающей запятой на процессорах Intel делает NO арифметическим потерей значимости в операциях "+" и "-", и поэтому вы не сможете найти x, для которого x-x == 0 ложно. То же самое верно для всех процессоров, которые поддерживают IEEE 754-2008 (см. Ссылки ниже).

Мой краткий ответ на другой ваш вопрос: if (xy == 0) точно так же безопасен, как if (x == y), поэтому assert (xx == 0) в порядке, потому что нет арифметического переполнения. производиться в xx или (xy).

Причина в следующем. Число float / double будет храниться в памяти в форме мантиссы и двоичной экспоненты. В стандартном случае мантисса нормирована: это> = 0,5 и <1. В вы можете найти некоторые константы из стандарта IEEE с плавающей запятой. Сейчас для нас интересны только подписчики

#define DBL_MIN         2.2250738585072014e-308 /* min positive value */
#define DBL_MIN_10_EXP  (-307)                  /* min decimal exponent */
#define DBL_MIN_EXP     (-1021)                 /* min binary exponent */

Но не все знают, что у вас могут быть двойные числа меньше, чем DBL_MIN. Если вы выполняете арифметические операции с числами в DBL_MIN, это число будет НЕ нормализованным, и поэтому вы работаете с этими числами, как с целыми числами (операция только с мантиссой), без каких-либо «ошибок округления».

Замечание : Я лично стараюсь не употреблять слова «круглые ошибки», потому что нет ошибок в арифметических компьютерных операциях. Эти операции только не то же самое, что операции +, -, * и / с такими же компьютерными номерами, как число с плавающей запятой. Есть детерминированных операций над подмножеством чисел с плавающей запятой, которые могут быть сохранены в форме (мантисса, экспонента) с четко определенным количеством бит для каждого. Такое подмножество чисел с плавающей запятой мы можем назвать компьютерным плавающим числом . Таким образом, результат классической операции с плавающей запятой будет спроецирован обратно в компьютерный набор чисел с плавающей запятой. Такая операция проектирования является детерминированной и имеет множество функций, например, если x1> = x2, то x1 * y> = x2 * y.

Извините за длинное замечание и вернемся к нашей теме.

Чтобы точно показать, что у нас есть, если мы работаем с числами меньше DBL_MIN, я написал небольшую программу на C:


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

void DumpDouble(double d)
{
    unsigned char *b = (unsigned char *)&d;
    int i;

    for (i=1; i<=sizeof(d); i++) {
        printf ("%02X", b[sizeof(d)-i]);
    }
    printf ("\n");
}

int main()
{
    double x, m, y, z;
    int exp;

    printf ("DBL_MAX=%.16e\n", DBL_MAX);
    printf ("DBL_MAX in binary form: ");
    DumpDouble(DBL_MAX);

    printf ("DBL_MIN=%.16e\n", DBL_MIN);
    printf ("DBL_MIN in binary form: ");
    DumpDouble(DBL_MIN);

    // Breaks the floating point number x into its binary significand
    // (a floating point value between 0.5(included) and 1.0(excluded))
    // and an integral exponent for 2
    x = DBL_MIN;
    m = frexp (x, &exp);
    printf ("DBL_MIN has mantissa=%.16e and exponent=%d\n", m, exp);
    printf ("mantissa of DBL_MIN in binary form: ");
    DumpDouble(m);

    // ldexp() returns the resulting floating point value from
    // multiplying x (the significand) by 2
    // raised to the power of exp (the exponent).
    x = ldexp (0.5, DBL_MIN_EXP);   // -1021
    printf ("the number (x) constructed from mantissa 0.5 and exponent=DBL_MIN_EXP (%d) in binary form: ", DBL_MIN_EXP);
    DumpDouble(x);

    y = ldexp (0.5000000000000001, DBL_MIN_EXP);
    m = frexp (y, &exp);
    printf ("the number (y) constructed from mantissa 0.5000000000000001 and exponent=DBL_MIN_EXP (%d) in binary form: ", DBL_MIN_EXP);
    DumpDouble(y);
    printf ("mantissa of this number saved as double will be displayed by printf(%%.16e) as %.16e and exponent=%d\n", m, exp);

    y = ldexp ((1 + DBL_EPSILON)/2, DBL_MIN_EXP);
    m = frexp (y, &exp);
    printf ("the number (y) constructed from mantissa (1+DBL_EPSILON)/2 and exponent=DBL_MIN_EXP (%d) in binary form: ", DBL_MIN_EXP);
    DumpDouble(y);
    printf ("mantissa of this number saved as double will be displayed by printf(%%.16e) as %.16e and exponent=%d\n", m, exp);

    z = y - x;
    m = frexp (z, &exp);
    printf ("z=y-x in binary form: ");
    DumpDouble(z);
    printf ("z will be displayed by printf(%%.16e) as %.16e\n", z);
    printf ("z has mantissa=%.16e and exponent=%d\n", m, exp);

    if (x == y)
        printf ("\"if (x == y)\" say x == y\n");
    else
        printf ("\"if (x == y)\" say x != y\n");

    if ((x-y) == 0)
        printf ("\"if ((x-y) == 0)\" say \"(x-y) == 0\"\n");
    else
        printf ("\"if ((x-y) == 0)\" say \"(x-y) != 0\"\n");
}

Этот код произвел следующий вывод:

DBL_MAX=1.7976931348623157e+308
DBL_MAX in binary form: 7FEFFFFFFFFFFFFF
DBL_MIN=2.2250738585072014e-308
DBL_MIN in binary form: 0010000000000000
DBL_MIN has mantissa=5.0000000000000000e-001 and exponent=-1021
mantissa of DBL_MIN in binary form: 3FE0000000000000
the number (x) constructed from mantissa 0.5 and exponent=DBL_MIN_EXP (-1021) in binary form: 0010000000000000
the number (y) constructed from mantissa 0.5000000000000001 and exponent=DBL_MIN_EXP (-1021) in binary form: 0010000000000001
mantissa of this number saved as double will be displayed by printf(%.16e) as 5.0000000000000011e-001 and exponent=-1021
the number (y) constructed from mantissa (1+DBL_EPSILON)/2 and exponent=DBL_MIN_EXP (-1021) in binary form: 0010000000000001
mantissa of this number saved as double will be displayed by printf(%.16e) as 5.0000000000000011e-001 and exponent=-1021
z=y-x in binary form: 0000000000000001
z will be displayed by printf(%.16e) as 4.9406564584124654e-324
z has mantissa=5.0000000000000000e-001 and exponent=-1073
"if (x == y)" say x != y
"if ((x-y) == 0)" say "(x-y) != 0"

Итак, мы можем видеть, что если мы работаем с числами меньше, чем DBL_MIN, они не будут нормализованы (см. 0000000000000001 ).Мы работаем с этими числами как с целыми числами и без каких-либо «ошибок». Таким образом, если мы присвоим y = x , то if (xy == 0) точно так же безопасно, как if (x == y) и assert (xx == 0) работает нормально. В этом примере z = 0,5 * 2 ^ (- 1073) = 1 * 2 ^ (- 1072). Это действительно самое маленькое число, которое мы можем сохранить в двойном размере. Все арифметические операции с числами меньше DBL_MIN работают как с целым числом, умноженным на 2 ^ (- 1072).

Итак, у меня нет проблем с переполнением на моем компьютере под управлением Windows 7 с процессором Intel. Если бы у кого-то был другой процессор, было бы интересно сравнить наши результаты .

Есть ли у кого-нибудь идея, как можно произвести арифметическое переполнение с помощью операций - или +? Мои эксперименты выглядят так, что это невозможно.

EDITED : я немного изменил код для лучшей читаемости кода и сообщений.

ДОБАВЛЕННЫЕ ССЫЛКИ : Мои эксперименты показывают, что http://grouper.ieee.org/groups/754/faq.html#underflow абсолютно верен на моем процессоре Intel Core 2. То, как он будет вычисляться, не даст потери значимости в операциях "+" и "-" с плавающей запятой. Мои результаты не зависят от параметров компилятора Microsoft Visual C Strict (/ fp: strict) или Precise (/ fp: Precise) (см. http://msdn.microsoft.com/en-us/library/e7s85ffb%28VS. 80% 29.aspx и http://msdn.microsoft.com / en-us / library / Aa289157 )

ЕЩЕ ОДНА (ВОЗМОЖНО, ПОСЛЕДНЯЯ) ССЫЛКА И МОЕ ЗАМЕЧАНИЕ : Я нашел хорошую ссылку http://en.wikipedia.org / wiki / Subnormal_numbers , где описано то же, что я писал ранее. Включая денормальные числа или денормализованные числа (теперь часто называемые субнормальными числами, например, в IEEE 754-2008 ) следуют следующему утверждению:

«Денормальные числа обеспечивают гарантию того, что сложение и вычитание чисел с плавающей запятой никогда не будут потеряны; два соседних числа с плавающей запятой всегда имеют представимую ненулевую разницу. Без постепенного потери значимости вычитание a-b может привести к потере значимости и {{1} }} дает ноль, даже если значения не равны »

Таким образом, все мои результаты должны быть верными на любом процессоре, поддерживающем IEEE 754-2008.

3
ответ дан 29 November 2019 в 06:21
поделиться

Да, за исключением особых случаев xx всегда будет 0. Но x * (1 / x) не всегда будет 1; -)

3
ответ дан 29 November 2019 в 06:21
поделиться

Да, самовычитание всегда должно приводить к нулю, кроме для особых случаев.

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

http://grouper.ieee.org/groups/754/

3
ответ дан 29 November 2019 в 06:21
поделиться

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

4
ответ дан 29 November 2019 в 06:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: