Унарный минус и преобразование без подписей

Это всегда технически корректно:

unsigned abs(int n)
{
    if (n >= 0) {
        return n;
    } else {
        return -n;
    }
}

Мне кажется, что здесь, если -INT_MIN > INT_MAX, выражение "-n" может переполняться при n == INT_MIN, поскольку -INT_MIN находится за пределами границ. Но на моем компиляторе это, похоже, работает нормально... это детали реализации или поведение, на которое можно положиться?

Более длинная версия

Немного контекста: я пишу С++-оболочку для целочисленного типа GMP (mpz_t) и беру вдохновение для существующей оболочки GMP С++ (называемой mpz_class). При обработке добавления mpz_t со знаками целых чисел существует такой код:

static void eval(mpz_ptr z, signed long int l, mpz_srcptr w)
{
  if (l >= 0)
    mpz_add_ui(z, w, l);
  else
    mpz_sub_ui(z, w, -l);
}

Другими словами, если целое число со знаком положительное, добавьте его с помощью подпрограммы беззнакового сложения, если целое число со знаком отрицательно добавить его, используя процедуру беззнакового вычитания. Обе подпрограммы * _ui принимают unsigned long как последние аргументы. Является ли выражение

-l

под угрозой переполнения?

Ответ 1

Если вы хотите избежать переполнения, вы должны сначала отличить n к unsigned int, а затем применить унарный минус к нему.

unsigned abs(int n) {
  if (n >= 0)
    return n;
  return -((unsigned)n);
}

В исходном коде отрицание происходит до преобразования типа, поэтому поведение undefined, если n < -INT_MAX.

При отрицании выражения без знака никогда не будет переполнения. Вместо этого результат будет по модулю 2^x, для соответствующего значения x.

Ответ 2

Нет такой вещи, как переполнение беззнаковых целых чисел в C. Арифметика для них четко определяется как вычисление по модулю их max + 1, они могут "обернуть", но технически это не считается переполнением. Таким образом, часть преобразования вашего кода в порядке, хотя в крайних случаях вы можете столкнуться с неожиданными результатами.

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

if (INT_MIN < -INT_MAX && n == INT_MIN ) /*do something special*/

Ответ 3

Сегодня большинство компьютеров используют шкалу чисел с двумя дополнениями, что означает, что отрицательная часть больше, чем положительная, например, от -128 до 127. Это означает, что если вы можете представить положительное число отрицательное число, вы можете представить отрицательный номер без беспокойства.

Ответ 4

Может быть, он справится с симметричным диапазоном чисел 2's-комплемента:

#include <limits.h>

unsigned int abs(int n){

  unsigned int m;

  if(n == INT_MIN)
    m = INT_MAX + 1UL;
  else if(n < 0)
    m = -n;
  else 
    m = n;

  return m;
}

Ответ 5

Это должно избегать поведения undefined и работать со всеми представлениями подписанного int (2 дополнения, 1 дополнения, знака и величины):

unsigned myabs(int v)
{
  return (v >= 0) ? (unsigned)v : (unsigned)-(v+1)+1;
}

Современные компиляторы могут удалить избыточную -1+1 и распознать идиому для вычисления абсолютного значения целого числа со знаком.

Здесь gcc производит:

_myabs:
    movl    4(%esp), %eax
    cltd
    xorl    %edx, %eax
    subl    %edx, %eax
    ret

Ответ 6

Да, он переполнится, для себя.

#include <stdio.h>
#include <limits.h>
int main(int argc, char**argv) {
    int foo = INT_MIN;
    if (-foo == INT_MIN) printf("overflow\n");
    return 0;
}

печатает "переполнение"

Однако это просто типичное поведение, не требуемое стандартом. Если вы хотите безопасно играть, см. Принятый ответ для чего.

Ответ 7

Очень хороший вопрос, который раскрывает различия между C89, C99 и С++. Так что это некоторые комментарии к этим стандартам.

В C89, где n является int:

(unsigned)n

не определен для всех n: нет ограничений на преобразование подписанного или unsigned int, за исключением того, что представление неотрицательного подписанного int идентично представлению unsigned int того же значения, при условии, что это значение равно представима.

Это считалось дефектом, а на C99 , к сожалению, была предпринята ошибочная попытка ограничить кодировку двумя дополнениями, одним дополнением или знаковой величиной с таким же количеством бит. К сожалению, комитет С не обладал значительными математическими знаниями и полностью нарушил спецификацию: с одной стороны, он плохо сформирован из-за кругового определения и, следовательно, ненормативный, а с другой стороны, если вы извините эту ошибку, это грубое переубеждение, которое, например, исключает представление BCD (используется в C на старых мэйнфреймах IBM), а также позволяет программисту взломать значение целого числа, перебирая биты представления (что очень плохо).

С++ столкнулся с некоторыми проблемами, чтобы обеспечить лучшую спецификацию, однако она испытывает ту же ошибку кругового определения.

Грубо говоря, представление значения v представляет собой массив без знака char с элементами sizeof (v). Беззнаковый char имеет мощность в два числа элементов и должен быть достаточно большим, чтобы гарантировать, что он верно кодирует любую структуру данных с псевдонимом. Количество бит в unsigned char хорошо определено как двоичный журнал числа представляемых значений.

Количество бит любого беззнакового значения аналогично хорошо определено, если оно имеет мощность двух чисел от 0 до 2 ^ n-1, посредством схемы канонического позиционного кодирования.

К сожалению, комитет хотел спросить, были ли какие-либо "дыры" в представлении. Например, у вас может быть 31-битное целое число на машине x86? Я говорю, к сожалению, потому что это плохо сформированный вопрос, и ответ аналогично неправильный.

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

Представление полно, если оно является сюръекцией, то есть оно находится на всем диапазоне пространства представления. Если представление заполнено, то нет "дыр", то есть неиспользуемых битов. Однако это еще не все. Представление 255 значений массиву из 8 бит не может быть заполнено, но нет неиспользуемых битов. Нет отверстий.

Проблема заключается в следующем: рассмотрим неподписанный int, тогда есть два разных поразрядных представления. Существует хорошо определенный массив из 2-х оснований базы данных, определенный из канонической кодировки, а затем имеется массив бит физического представления, заданный сглаживанием массива без знака char. Даже если это представление заполнено, между двумя типами битов существует отсутствие соответствия.

Мы все знаем, что "бит высокого порядка" логического представления может находиться на одном конце физического представления на некоторых машинах, а другой на других машинах: он называется endian-ness. Но на самом деле нет причин, чтобы биты не могли быть переписаны в каком-либо порядке вообще, на самом деле нет причин, по которым бит должен выстраиваться вообще! Просто подумайте о добавлении 1 по модулю максимального значения плюс 1 в качестве представления, чтобы увидеть это.

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

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

Поэтому не ясно, что

(unsigned)n

фактически произведет желаемый результат для отрицательных значений.