Есть ли значение с плавающей точкой x, для которого x-x == 0 является ложным?
В большинстве случаев я понимаю, что тест сравнения с плавающей запятой должен реализовываться с использованием диапазона значений (abs (x-y) < epsilon), но само вычитание означает, что результат будет равен нулю?
// can the assertion be triggered?
float x = //?;
assert( x-x == 0 )
Я предполагаю, что nan/inf могут быть особыми случаями, но меня больше интересует то, что происходит для простых значений.
изменить
Я рад выбрать ответ, если кто-то может ссылаться на ссылку (стандарт с плавающей точкой IEEE)?
Ответы
Ответ 1
Как вы намекали, 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):
Когда сумма двух операндов с противоположные знаки (или разность два операнда с похожими знаками) ровно нуль, знак этой суммы (или разница) должна составлять +0 атрибуты округления, кроме roundTowardNegative; под этим атрибут, знак точного нуля сумма (или разность) должна быть равна -0.
Таким образом, результат x - x
должен быть +/- 0
и поэтому должен сравниваться с 0.0
(раздел 5.11, параграф 2):
Сравнения должны игнорировать знак нуля.
Дальнейшее редактирование:. Нельзя сказать, что компилятор с ошибкой не мог вызвать это утверждение. Ваш вопрос неоднозначен; нет конечного числа с плавающей запятой x
, для которого x - x == 0
является ложным. Однако это не тот код, который вы проверили; он проверяет, может ли определенное выражение на языке C-стиля оценивать ненулевое значение; в частности, на некоторых платформах с определенными (непродуманными) оптимизациями компилятора, два экземпляра переменной x
в этом выражении могут иметь разные значения, в результате чего утверждение терпит неудачу (особенно если x
является результатом некоторых вычисление, а не постоянное, представимое значение). Это ошибка в модели чисел на этих платформах, но это не значит, что этого не может быть.
Ответ 2
Если представление преобразуется (например, из формата 64-разрядной памяти в формат 80-битного внутреннего регистра на x86), я бы ожидал, что в некоторых случаях это может привести к срабатыванию.
Ответ 3
Да, кроме особых случаев x-x
всегда будет 0. Но x*(1/x)
не всегда будет 1; -)
Ответ 4
Да, самовыравнивание всегда должно приводить к нулю, за исключением особых случаев.
Проблема возникает, когда вы добавляете, вычитаете, умножаете или делите перед сравнением, где корректируются экспонента и мантисса. Когда показатели одинаковы, мантиссы вычитаются, и если они одинаковы, все заканчивается на нуле.
http://grouper.ieee.org/groups/754/
Ответ 5
Мой ответ на главный вопрос: "Существует ли значение с плавающей запятой x, для которого x-x == 0 является ложным?": реализация по крайней мере с плавающей запятой на процессорах Intel делает NO арифметическое недоиспользование в операциях "+" и "-", поэтому вы не сможете найти x, для которого x-x == 0 является ложным. То же самое верно для всех процессоров, поддерживающих IEEE 754-2008 (см. Ниже ссылки).
Мой короткий ответ на другой ваш вопрос: if (xy == 0) является настолько безопасным, как если бы (x == y), поэтому assert (xx == 0) в порядке, потому что нет арифметического нижнего потока в xx или (xy).
Причина следующая. Поплавок/двойной номер будет храниться в памяти в форме мантиссы и двоичной степени. В стандартном случае мантисса нормирована: она равна = 0,5 и < 1. В <float.h>
вы можете найти некоторые константы из стандарта 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, это число будет NOT нормализовано и поэтому вы будете работать с этими числами, как с целыми числами (только с мантиссой) без каких-либо "круглых ошибок".
Примечание. Я лично стараюсь не использовать слова "круглые ошибки", потому что в арифметических операциях с компьютером есть без ошибок. Эта операция не отличается от операций с символами +, -, * и/с теми же номерами компьютеров, что и число с плавающей запятой. В подмножестве чисел с плавающей запятой существуют детерминированные операции, которые могут быть сохранены в форме (мантисса, экспонента) с определенным количеством бит для каждого. Такое подмножество поплавков можно назвать компьютерным плавающим числом. Таким образом, результат классической операции с плавающей запятой будет проецироваться обратно в набор чисел с плавающим числом компьютеров. Такая операция проектирования является детерминированной и имеет множество функций, например, если 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 (x-y == 0)
точно так же безопасен, как if (x == y)
, а assert(x-x == 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, То, как он будет рассчитываться, не приводит к перетоку в операциях с плавающей запятой "+" и "-". Мои результаты независимы от строковых (/fp: strict) или точных (/fp: exact) компиляторов Microsoft Visual C (см. 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, где описано то же, что я написал ранее. Включение денормальных чисел или денормализованных чисел (теперь часто называемых субнормальными числами, например, в In IEEE 754-2008) следуют следующему статуту:
"Денормальные числа предоставляют гарантируют, что дополнение и вычитание чисел с плавающей запятой никогда не заканчивается; два поблизости числа с плавающей запятой всегда имеют представляющая ненулевую разницу. Без постепенного вычитание a-b может производить нуль, даже если значения не равны."
Таким образом, все мои результаты должны быть правильными на любом процессоре, поддерживающем IEEE 754-2008.
Ответ 6
Относительно того, что говорит Марк, проверьте эту ссылку http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.18. (Не уверен, что это относится к вашей ситуации.)