Почему отрицательный ноль важен?

64

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

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

Статья Википедии о концепции не особенно полезна; это только делает смутные заявления о нулевом знаке, делающем некоторые математические операции проще с плавающей запятой, если я правильно понимаю. В этом ответе перечислено несколько функций, которые ведут себя по-разному, и, возможно, что-то может быть выведено из примеров, если вы знакомы с тем, как их можно использовать. (Хотя конкретный пример сложных квадратных корней выглядит неверно, поскольку эти два числа математически эквивалентны, если только у меня нет недопонимания.) Но я не смог найти четкого утверждения о том типе неприятностей, с которыми вы бы столкнулись, если бы его там не было. Чем больше математических ресурсов я смог найти, так это тем, что не существует различий между ними с математической точки зрения, и статья в Википедии, похоже, предполагает, что это редко можно увидеть вне вычислений, помимо описания ограничений.

Так почему отрицательный ноль ценен в вычислительной технике? Я уверен, что просто что-то упустил.

jpmc26
источник
6
Отрицательный ноль может сигнализировать о недостаточном значении числа с плавающей запятой IEEE, но помимо этого его использование представляется спорным и неясным. Если бы я угадал, я бы сказал, что отрицательный ноль представлен в IEEE с плавающей точкой, потому что ... ну, вы можете. Для еще более интересной поездки ищите информацию о сигнализации с плавающей точкой NaN.
Роберт Харви
1
Если конкретным примером является «1 / 0.0» / «1 / -0.0», 0 - это срез ответвления для 1 / x, и предел зависит от того, подойдете ли вы к нему снизу или сверху.
Vatine
@Vatine Нет, конкретный пример, sqrt(-1+0i) = iи sqrt(-1-0i) = -i, хотя, я думаю, хотя и с правильным синтаксисом для некоторого языка программирования. Я буду редактировать, чтобы быть более понятным.
jpmc26
3
Я искал программистов , переполнение стека , информатика , математика и инженерия . Единственный вопрос, который я смог найти, это использование отрицательного значения с плавающей запятой? , Это не может быть только во второй раз, когда это произошло!
Я действительно удивлен, что комплексные числа вообще не встречаются в ответах, особенно учитывая приведенный мной пример с квадратным корнем.
jpmc26

Ответы:

69

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

a = -1 / 1000000000000000000.0

a слишком мал, чтобы быть правильно представленным с плавающей точкой (32 бита), поэтому он «округляется» до -0.

Теперь предположим, что наше вычисление продолжается:

b = 1 / a

Поскольку a float, это приведет к -infinity, что довольно далеко от правильного ответа -1000000000000000000.0

Теперь давайте вычислим b, если нет -0 (поэтому a округляется до +0):

b = 1 / +0
b = +infinity

Результат снова неверен из-за округления, но теперь он «более неправильный» - не только численно, но, что более важно, из-за другого знака (результат вычисления + бесконечность, правильный результат -1000000000000000000.0).

Вы все еще можете сказать, что это не имеет значения, так как оба не правы. Важно то, что существует множество числовых приложений, где наиболее важным результатом вычислений является знак - например, при принятии решения, поворачивать ли влево или вправо на перекрестке, используя какой-либо алгоритм машинного обучения, вы можете интерпретировать положительное значение => поворот влево, отрицательное значение => повернуть направо, фактическая «величина» значения - это просто «коэффициент достоверности».

QBD
источник
Есть ли у вас какие-либо идеи относительно того, может ли знак недостаточности быть особенно важным в вычислениях мнимых / комплексных чисел?
jpmc26
@qbd: Вы знаете, что это за числовые приложения? Я бы сказал, что программы, которые запускаются и используются +infи -infв нормальной работе, прослушиваются.
Бьорн Линдквист
@ BjörnLindqvist Если вам нужны конкретные загружаемые приложения - тогда я не знаю ни одного. Я не думаю, что это обязательно глючит - вместо float / double вы можете использовать что-то вроде BigDecimal с неограниченной точностью. Но стоит ли это, когда программа даст те же результаты, что и с float / double, но с гораздо худшей производительностью?
QBD
Вы написали «числовые приложения, где самым важным результатом вычислений является знак». Я могу в это поверить, но я не могу поверить, что есть какие-то хорошо написанные приложения, которые полагаются на -0 и на значения, являющиеся +infи -inf. Если ваша программа вызывает переполнение с плавающей запятой, это ошибка, и то, что происходит потом, не так интересно, imho. Мы все еще пропускаем практические примеры, в которых полезен -0.
Бьорн Линдквист
1
@ BjörnLindqvist Большая часть x265 выполняется в сборке, опираясь на его неясные детали (которые зависят от архитектуры процессора), о которых мало кто знает во имя производительности. Это неправильно? Полагаться на широко внедренный 30-летний стандарт (который здесь и останется) для одной простой, хорошо понятной функции во имя производительности неожиданно не так уж и плохо.
QBD
8

Во-первых, как вы создаете -0? Есть два способа: (1) выполнить операцию с плавающей запятой, где математический результат отрицателен, но настолько близок к нулю, что он округляется до нуля, а не до ненулевого числа. Этот расчет даст -0. (b) Некоторые операции с участием нулей: умножьте положительный ноль на отрицательное число, или разделите положительный ноль на отрицательное число, или отрицательный положительный ноль.

Наличие отрицательного нуля немного упрощает умножение и деление, знак x * y или x / y всегда является знаком x, исключающим или знаком y. Без отрицательного нуля потребуется дополнительная проверка, чтобы заменить -0 на +0.

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

Оптимизация компиляторов ненависти -0. Например, вы не можете заменить x + 0.0 на x, потому что результат не должен быть x, если x равен -0.0. Вы не можете заменить x * 0.0 на 0.0, потому что результат должен быть -0.0, если x <0 или x равен -0.0.

gnasher729
источник
7
Я бы хотел, чтобы IEEE-754 включал четыре ноля: «точный», положительный бесконечно малый, отрицательный бесконечно малый и беззнаковый (последний - разница между неразличимыми значениями). Выполнение этого привело бы к работе многих аксиом с плавающей запятой - среди них x + 0,0 эквивалента x-0,0 эквивалента x, xy эквивалента x + (- 1,0) * y и 1,0 / x эквивалента -1,0 / (- 1,0 * x) [если x положительный ноль, оба будут pos-inf; если нег-ноль, оба нег-инф; если точный или без знака, оба NaN].
суперкат
Я смог получить отрицательный ноль, пройдя -5и 5в fmod(). Это довольно раздражает для моего случая использования.
Аарон Франке
6

C # Double, который соответствует IEEE 754

    double a = 3.0;
    double b = 0.0;
    double c = -0.0;

    Console.WriteLine(a / b);
    Console.WriteLine(a / c);

печатает:

Infinity
-Infinity

на самом деле, чтобы объяснить немного ...

Double d = -0.0; 

Это означает что-то намного ближе к d = The Limit of x as x approaches 0-или The Limit of x as x approaches 0 from the negatives.


Чтобы ответить на комментарий Филиппа ...

В основном, отрицательный ноль означает недостаток.

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

например, этот код (снова C #):

double a = -0.0;
double b = 0.0;

Console.WriteLine(a.Equals(b));
Console.WriteLine(a==b);
Console.WriteLine(Math.Sign(a));

дает такой результат:

True
True
0

Чтобы объяснить неформально, все специальные значения, которые может иметь IEEE 754 (положительная бесконечность, отрицательная бесконечность, NAN, -0.0), не имеют смысла в практическом смысле. Они не могут представлять какую-либо физическую ценность или любое значение, которое имеет смысл в вычислениях «реального мира». В основном они имеют в виду следующее:

  • положительная бесконечность означает переполнение на положительном конце, которое может представлять плавающая точка
  • отрицательная бесконечность означает переполнение на положительном конце, которое может представлять плавающая точка
  • Отрицательный ноль означает, что переполнение и операнды имели противоположные знаки
  • положительный ноль может означать понижение и операнды имели одинаковый знак
  • NAN означает, что ваш расчет самодовольно не определен, например sqrt(-7), или у него нет ограничения, как 0/0или какPositiveInfinity/PositiveInfinity
AK_
источник
7
Да, но почему это важно? Можете ли вы привести пример из реальной жизни, где разница имеет значение?
Филипп
5

Вопрос о том, как это относится к вычислениям комплексных чисел, действительно становится основой того, почему и +0, и -0 существуют в плавающей точке. Если вы вообще изучаете комплексный анализ, вы быстро обнаружите, что непрерывные функции от комплекса к комплексу обычно не могут рассматриваться как «однозначные», если не принять «вежливую беллетристику», в которой выходы образуют так называемую «поверхность Римана». Например, комплексный логарифм назначает каждому входу бесконечно много выходов; Когда вы «соединяете их», чтобы сформировать непрерывный выходной сигнал, вы получаете все реальные части, образующие «бесконечный штопор» вокруг источника. Непрерывная кривая, которая пересекает действительную ось «вниз от положительно-мнимой стороны» и другую кривую, которая «оборачивается вокруг полюса» и пересекает действительную ось »

Теперь примените это к числовой программе, которая вычисляет, используя сложные числа с плавающей точкой. Действие, предпринимаемое после данного расчета, может сильно отличаться в зависимости от того, на каком «листе» программа в данный момент «включена», а знак последнего вычисленного результата, вероятно, говорит вам, на каком «листе». Теперь предположим, что результат был нулевым? Помните, что здесь «ноль» действительно означает «слишком маленький, чтобы правильно представлять». Но если вычисление может обеспечить сохранение знака (т. Е. Запомнить, какой «лист»), когда результат равен нулю, тогда код может проверить знак и выполнить правильное действие даже в этой ситуации.

PJM
источник
1

Причина проще, чем обычно

Конечно, есть много хаков, которые выглядят действительно хорошо, и они полезны (например, округление до -0.0или, +0.0но предположим, что у нас есть представление со знаком int со знаком минус / плюс в начале (я знаю, что это разрешается двоичным кодом U2). в целых числах обычно, но предполагают менее сложное представление double):

0 111 = 7
^ sign

Что делать, если есть отрицательное число?

1 111 = -7

Ладно, это так просто. Итак, давайте представим 0:

0 000 = 0

Это тоже хорошо. Но как насчет 1 000? Это должен быть запрещенный номер? Лучше нет

Итак, давайте предположим, что есть два типа нуля:

0 000 = +0
1 000 = -0

Что ж, это упростит наши расчеты и, к счастью, даст некоторые дополнительные возможности. Таким образом, +0и -0исходят только из проблем двоичного представления.

Давид Пура
источник
6
Если я правильно читаю это, вы, по сути, просто говорите, что люди, определяющие или внедряющие стандарты, не захотели запретить это. Я не думаю, что это обоснование соответствует тому факту, что дополнение 2 использует представление «отрицательный ноль» для совершенно другого числа и не имеет представления отрицательного нуля. Смотрите статью в Википедии, на которую я ссылаюсь.
jpmc26
1
@ jpmc26 Я думаю , что на самом деле некоторые правда , что в том , что не запрещая это означает , не требующие реализации иметь особый случай. Как таковое, каждое число имеет бит знака и может быть сведено на нет, переключая бит знака. Даже NaN подписаны, и реализации могут (но не обязаны) выбирать соответствующий знак при создании NaN. Если бы отрицательный ноль не существовал, каждый расчет, который привел к 0, должен был бы сделать дополнительную работу, чтобы исправить бит знака и т. Д.
Хоббс
4
@ jpmc26 (т. е. при любом другом умножении двух чисел знак результата представляет собой xor знака умножения, а величина является произведением двух величин. В реальной жизни это работает для -1 * 0 = - 0. Но если ноль с перевернутым битом знака был каким-то особым ненулевым значением, каждый продукт, который мог бы произвести 0, должен был бы проверить и убедиться, что он не
выдал