Имеет ли float отрицательный ноль? (-0f)
Номера с плавающей точкой IEEE имеют бит, назначенный для обозначения знака, что означает, что вы можете технически иметь разные двоичные представления нуля (+0 и -0). Есть ли арифметическая операция, которую я мог бы сделать, например, в C, которая приводит к отрицательному значению с плавающей точкой?
Этот вопрос вдохновлен другим, который ставит под сомнение, можете ли вы безопасно сравнить 0.0f
с помощью ==
, и я еще раз подумал, есть ли другие способы представления нуля, которые могут привести к тому, что float1 == 0.0f
сломается, казалось бы, идеально равные значения.
[Изменить] Пожалуйста, не комментируйте безопасность сравнения поплавков за равенство! Я не пытаюсь добавить к этому переполненному ведру повторяющихся вопросов.
Ответы
Ответ 1
В соответствии со стандартом существует отрицательный ноль, но он равен положительному нулю. Почти для всех целей они ведут себя одинаково, и многие считают, что наличие негатива является деталью реализации. Однако существуют некоторые функции, которые ведут себя совершенно по-другому: деление и 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
Это означало бы, что код будет правильно обрабатывать определенные ограничения без необходимости явной обработки. Он не уверен, что полагаться на эту функцию для значений, близких к ограничениям, является хорошей идеей, так как простая ошибка расчета может изменить знак и сделать значение далеким от правильного, но вы все равно можете воспользоваться им, если вы избежите вычислений, которые измените знак.
Ответ 2
Существует ли арифметическая операция я мог бы сделать, например, в 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
В общем, однако, вам не нужно беспокоиться о отрицательном нуле.
Ответ 3
Да, ноль может быть подписан, но для стандарта требуется положительный и отрицательный ноль для тестирования как равный
Ответ 4
Есть несколько простых арифметических операций, которые приводят к отрицательному нулевому ответу (по крайней мере, в системах i386/x64/ARMv7/ARMv8, на которых я тестировал его):
Они застали меня врасплох, когда я писал оптимизатора для упрощения арифметических выражений. Оптимизация " a = b * 0" до " a = 0" приведет к неправильному ответу (+0), если b окажется отрицательным (правильный ответ равен -0).
Ответ 5
Да, float делает имеет отрицательный ноль, но нет, вам не нужно беспокоиться об этом при сравнении значений с плавающей запятой.
Арифметика с плавающей точкой определена для правильной работы в особых случаях.
Ответ 6
Да, float
имеют отрицательный нуль, как и другие типы с плавающей точкой IEEE, такие как double
(для систем с плавающей точкой IEEE). Ниже приведен пример здесь в Octave о том, как их создать; те же операции работают в C. Оператор ==
рассматривает +0 и -0 то же самое, и поэтому отрицательные нули не нарушают такого типа сравнения.
Ответ 7
Да, вы можете иметь +0 и -0, и это разные битовые шаблоны (не должен проходить тест равенства). Вы никогда не должны использовать == с float, но не IEEE float. < или > в порядке. Есть много других вопросов и обсуждений по этой теме, поэтому я не буду вдаваться в нее здесь.
Ответ 8
этот 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.
Ответ 9
Вы должны проявлять осторожность при выполнении сравнений сравнений с помощью float. Помните, что вы пытаетесь представить десятичное значение в двоичной системе.
Безопасно ли проверять значения с плавающей запятой на равенство 0?
Если вы должны сравнивать значения с плавающей запятой, я бы предложил вам использовать какой-то допуск, приемлемый для вас float1 <= toleranceVal && float1 >= toleranceVal2
, или умножить на коэффициент в десять и отличить как целое.
if (!(int)(float1 * 10000)) { .. some stuff .. }
Ответ 10
-lm имеет функцию signbit(), доступную для указания отрицательного значения (включая -0)