У поплавка отрицательный ноль?(-0f) - PullRequest
25 голосов
/ 23 февраля 2011

Числам с плавающей запятой IEEE назначен бит для обозначения знака, что означает, что вы можете технически иметь различные двоичные представления нуля (+0 и -0). Есть ли какая-нибудь арифметическая операция, которую я мог бы выполнить, например, в C, которая привела бы к отрицательному нулевому значению с плавающей точкой?

Этот вопрос вдохновлен другим вопросом, который поставил под сомнение возможность безопасного сравнения 0.0f с использованием ==, и я задался вопросом: есть ли другие способы представления нуля, из-за которых float1 == 0.0f, по-видимому, будет нарушен? равные значения.

[Редактировать] Пожалуйста, не комментируйте о безопасности сравнения поплавков на равенство! Я не пытаюсь добавить к этому переполненному ведру повторяющихся вопросов.

Ответы [ 10 ]

17 голосов
/ 23 февраля 2011

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

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

int main() {
    double x = 0.0;
    double y = -0.0;
    printf("%.08f == %.08f: %d\n", x, y, x == y);
    printf("%.08f == %.08f: %d\n", 1 / x, 1 / y, 1 / x == 1 / y);
    printf("%.08f == %.08f: %d\n", atan2(x, y), atan2(y, y), atan2(x, y) == atan2(y, y));
}

Результат из этого кода:

0.00000000 == -0.00000000: 1
1.#INF0000 == -1.#INF0000: 0
3.14159265 == -3.14159265: 0

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

10 голосов
/ 23 февраля 2011

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

Конечно:

float negativeZero = -10.0e-30f * 10.0e-30f;

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

Семантика отрицательного нуля хорошо определена стандартом IEEE-754;единственный реальный наблюдаемый способ, которым его поведение отличается от нуля в арифметическом выражении, состоит в том, что если вы разделите его, вы получите другой знак бесконечности.Например:

1.f /  0.f --> +infinity
1.f / -0.f --> -infinity

Сравнения, сложения и вычитания с -0.f дают тот же результат, что и с +0.f (в режиме округления по умолчанию).Умножение может сохранять знак нуля, но, как уже было отмечено, оно обычно не наблюдается.

Существуют некоторые математические библиотечные функции, поведение которых может варьироваться в зависимости от знака нуля.Например:

copysignf(1.0f, 0.0f) -->  1.0f
copysignf(1.0f,-0.0f) --> -1.0f

Это чаще встречается в сложных функциях:

csqrtf(-1.0f + 0.0f*i) --> 0.0f + 1.0f*i
csqrtf(-1.0f - 0.0f*i) --> 0.0f - 1.0f*i

В целом, однако, вам не нужно беспокоиться об отрицательном нуле.

7 голосов
/ 23 февраля 2011

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

3 голосов
/ 05 апреля 2016

Существует несколько простых арифметических операций, которые приводят к отрицательному нулевому ответу (по крайней мере, в системах i386 / x64 / ARMv7 / ARMv8, на которых я его тестировал):

  • -1 * 0
  • 0 / -1

Это застало меня врасплох, когда я писал оптимизатор для упрощения арифметических выражений. Оптимизация от " a = b * 0 " до " a = 0 " приведет к неправильному ответу (+0), если b окажется отрицательным (правильный ответ равен -0).

2 голосов
/ 23 февраля 2011

Да, число с плавающей запятой имеет отрицательный ноль, но нет , вам не нужно беспокоиться об этом при сравнении значения с плавающей точкой.

Арифметика с плавающей точкой определена для корректной работы в особых случаях.

1 голос
/ 23 февраля 2011

Да, вы можете иметь +0 и -0, и это разные битовые комбинации (должен пройти проверку на равенство). Вы никогда не должны использовать == с плавающей точкой, конечно, не с плавающей точкой IEEE. <или> в порядке. Есть много других SO вопросов и обсуждений на эту тему, поэтому я не буду вдаваться в подробности здесь.

1 голос
/ 23 февраля 2011

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

0 голосов
/ 04 февраля 2013

-lm имеет функцию signbit (), позволяющую указать, является ли значение отрицательным (включая -0)

0 голосов
/ 23 февраля 2011

Вы должны проявлять осторожность при выполнении сравнений на равенство с использованием чисел с плавающей точкой.Помните, что вы пытаетесь представить десятичное значение в двоичной системе.

Безопасно ли проверять значения с плавающей запятой на равенство 0?

Если необходимосравните значения с плавающей запятой, я бы посоветовал вам использовать какой-то допуск, который вам подходит float1 <= toleranceVal && float1 >= toleranceVal2, или умножить на коэффициент десять и привести к целому числу.if (!(int)(float1 * 10000)) { .. some stuff .. }

0 голосов
/ 23 февраля 2011

это float1 == 0.0f никогда не является безопасным сравнением.

если у вас есть что-то вроде

float x = 0.0f;
for (int i = 0; i < 10; i++) x += 0.1f;
x -= 1.0f;
assert (x == 0.0f);

он потерпит неудачу, даже если предполагается, что он равен 0.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...