Я наткнулся на код человека, который, кажется, полагает, что существует проблема с вычитанием беззнакового целого числа из другого целого числа того же типа, когда результат был бы отрицательным. Так что такой код будет неправильным, даже если он работает на большинстве архитектур.
unsigned int To, Tf;
To = getcounter();
while (1) {
Tf = getcounter();
if ((Tf-To) >= TIME_LIMIT) {
break;
}
}
Это единственная неопределенно релевантная цитата из стандарта C, которую я смог найти.
Вычисление с использованием беззнаковых операндов никогда не может быть переполнено, потому что результат, который не может быть представлен результирующим целочисленным типом без знака, уменьшается по модулю числа, которое на единицу больше наибольшего значения, которое может быть представлено результирующим типом.
Я полагаю, что можно было бы принять эту цитату как означающую, что, когда правый операнд больше, операция корректируется, чтобы иметь смысл в контексте усеченных по модулю чисел.
т.е.
0x0000 - 0x0001 == 0x 1 0000 - 0x0001 == 0xFFFF
в отличие от использования зависимой от реализации подписанной семантики:
0x0000 - 0x0001 == (без знака) (0 + -1) == (0xFFFF, но также 0xFFFE или 0x8001)
Какая или какая интерпретация верна? Это вообще определяется?
Ответы:
Результат вычитания, генерирующего отрицательное число в типе без знака, четко определен:
Как видите,
(unsigned)0 - (unsigned)1
равно -1 по модулю UINT_MAX + 1, или, другими словами, UINT_MAX.Обратите внимание, что, хотя в нем говорится: «Вычисление с использованием беззнаковых операндов никогда не может переполниться», что может привести вас к мысли, что оно применяется только для превышения верхнего предела, это представлено как мотивация для фактической связывающей части предложения: «a результат, который не может быть представлен результирующим целочисленным типом без знака, уменьшается по модулю числа, которое на единицу больше наибольшего значения, которое может быть представлено результирующим типом ". Эта фраза не ограничивается выходом за верхнюю границу типа и в равной степени применяется к значениям, слишком низким для представления.
источник
uint
всегда был предназначен для представления математического кольца целых чисел0
черезUINT_MAX
, с операциями сложения и умножения по модулюUINT_MAX+1
, а не потому , что переполнения. Однако возникает вопрос, почему, если кольца являются таким фундаментальным типом данных, язык не предлагает более общей поддержки для колец других размеров.Когда вы работаете с беззнаковыми типами, имеет место модульная арифметика (также известная как «циклическое» поведение). Чтобы понять эту модульную арифметику , просто взгляните на эти часы:
9 + 4 = 1 ( 13 по модулю 12 ), поэтому в другом направлении это: 1-4 = 9 ( -3 по модулю 12 ). Тот же принцип применяется при работе с беззнаковыми типами. Если тип результата - это
unsigned
модульная арифметика.Теперь посмотрим на следующие операции, сохраняющие результат в виде
unsigned int
:Если вы хотите убедиться, что результат есть
signed
, сохраните его вsigned
переменной или приведите кsigned
. Если вы хотите получить разницу между числами и убедиться, что модульная арифметика не будет применяться, вам следует рассмотреть возможность использованияabs()
функции, определенной вstdlib.h
:Будьте очень осторожны, особенно при написании условий, потому что:
но
источник
int d = abs(five - seven);
не годится. Сначалаfive - seven
вычисляется: продвижение оставляет типы операндов какunsigned int
, результат вычисляется по модулю(UINT_MAX+1)
и оценивается какUINT_MAX-1
. Тогда это значение является фактическим параметром дляabs
, что является плохой новостью.abs(int)
вызывает неопределенное поведение при передаче аргумента, поскольку он не входит в диапазон и,abs(long long)
вероятно, может содержать значение, но неопределенное поведение возникает, когда возвращаемое значение принудительноint
инициализируетсяd
.operator T()
. Сложение в двух обсуждаемых нами выражениях выполняется по типуunsigned int
на основе типов операндов. Результат сложения естьunsigned int
. Затем этот результат неявно преобразуется в тип, требуемый в контексте, преобразование, которое завершается ошибкой, поскольку значение не может быть представлено в новом типе.double x = 2/3;
vsdouble y = 2.0/3;
Что ж, первая интерпретация верна. Однако ваши рассуждения о «подписанной семантике» в этом контексте неверны.
Опять же, ваша первая интерпретация верна. Беззнаковая арифметика подчиняется правилам арифметики по модулю, что означает, что для 32-битных беззнаковых типов выполняется
0x0000 - 0x0001
оценка0xFFFF
.Однако для получения того же результата требуется и вторая интерпретация (основанная на «семантике со знаком»). Т.е. даже если вы выполняете оценку
0 - 1
в домене подписанного типа и получаете-1
промежуточный результат, это-1
все равно необходимо произвести,0xFFFF
когда позже он будет преобразован в беззнаковый тип. Даже если на какой-то платформе используется экзотическое представление для целых чисел со знаком (дополнение до единицы, величина со знаком), эта платформа по-прежнему должна применять правила арифметики по модулю при преобразовании целочисленных значений со знаком в беззнаковые.Например, эта оценка
по - прежнему гарантированно производить
UINT_MAX
вc
, даже если платформа использует экзотическое представление для целых чисел.источник
С беззнаковыми числами типа
unsigned int
или больше, при отсутствии преобразований типов,a-b
определяется как выдача беззнакового числа, которое при добавлении к немуb
дастa
. Преобразование отрицательного числа в беззнаковое определяется как получение числа, которое при добавлении к исходному числу с обратным знаком даст ноль (поэтому преобразование -5 в беззнаковое даст значение, которое при добавлении к 5 даст ноль) .Обратите внимание, что числа без знака, меньшие, чем
unsigned int
могут быть переведены в типint
до вычитания, поведениеa-b
будет зависеть от размераint
.источник