Негативное свойство отрицательных чисел
Тема далеко не новая, но часто упускаемая при обучении программированию, поэтому повторить лишний раз не помешает.
Допустим, вы хотите посчитать вещественный квадратный корень из модуля целого числа n
, значение которого может быть любым. Какой код вы для этого напишете (используем синтаксис языка Си, хотя язык здесь не важен)?
1) float f = sqrt( abs(n) );
2) float f = sqrt( (float) abs(n) );
3) float f = sqrt( fabs( (float)n ) );
Разместим здесь КПДВ, чтобы у желающих было время поразмыслить.
Единственным корректным кодом при заданных условиях является ответ номер 3. Для значения n, равного минимальному отрицательному числу (то есть, например, -2147483648
для 32-разрядного типа int), два других ответа дадут значение NaN
или приведут к аварийному завершению программы, в зависимости от архитектуры процессора. Строго говоря, это неопределённое поведение.
Почему так происходит? Опытные программисты, конечно, всё поняли, а для менее опытных объясним.
Отрицательные целые числа во всех современных процессорах представляются в дополнительном коде. Преимуществом дополнительного кода перед прямым и обратным кодом является только одно значение для кодирования нуля вместо раздельных +0
и -0
. Но так как ноль у нас один, а общее количество значений, кодируемых двоичными разрядами, очевидно, чётно, то получается, что для одного положительного или отрицательного значения в любом случае не найдётся пары среди значений с другим знаком. И действительно, минимальное отрицательное число (-128
, -32768
, -2147483648
и т.д., в зависимости от разрядности целых) не имеет представимой положительной пары своей разрядности (+128
, +32768
, +2147483648
и т.д.).
Что же происходит при попытке обратить знак такого непарного числа? А ничего:
-(-2147483648) == -2147483648
abs (-2147483648) == -2147483648
Поэтому наша попытка обезопасить вычисление sqrt
при помощи abs
проваливается на таком значении, и мы получаем квадратный корень из отрицательного числа. Ну и во всех остальных алгоритмах возможна такая же ерунда.
В вещественных числах такого не происходит, так как они кодируются в прямом коде, и несимметричных вещественных значений нет. Зато есть +0.0
и -0.0
.