Как вычесть два целых числа без знака с переносом или переполнением

Есть два целых числа без знака (x и y), которые нужно вычесть. х всегда больше у. Однако и x, и y могут сворачиваться; например, если они оба были байтами, после 0xff идет 0x00. Проблемный случай, если x зацикливается, а y нет. Теперь x кажется меньше, чем y. К счастью, x не будет повторяться дважды (гарантированно только один раз). Предполагая, что байты, x завернуты и теперь равны 0x2, тогда как y нет и равен 0xFE. Правильный ответ x - y должен быть 0x4.

Может быть,

( x > y) ? (x-y) : (x+0xff-y);

Но я думаю, что есть другой способ, что-то связанное с дополнением 2s?, и в этой встроенной системе x и y являются самыми большими типами unsigned int, поэтому добавление 0xff... невозможно

Как лучше всего написать заявление (целевой язык C)?


person mgag    schedule 13.01.2010    source источник
comment
Что тебе кроме этого лучшего способа? Пожалуйста, уточните ваши требования...   -  person ThinkJet    schedule 14.01.2010
comment
Что вы имеете в виду, что и x, и y могут обернуться? Вы имеете в виду, что они могут обернуться, если они будут преобразованы из неподписанных в подписанные типы?   -  person jamesdlin    schedule 14.01.2010
comment
Два целых числа без знака. Проблемный случай, если x зацикливается, а y нет. Теперь x кажется меньше, чем y. К счастью, x не будет повторяться дважды (гарантированно только один раз). Предполагая байты, x обернулся и теперь равен 0x2, тогда как y нет и равен 0x14. Правильный ответ x - y должен быть 0x4. ( х › у) ? (x-y): (x+0xff-y); но я думаю, что есть другой способ, и в этой встроенной системе x и y являются самыми большими беззнаковыми целыми типами, поэтому добавление 0xff... невозможно.   -  person mgag    schedule 14.01.2010
comment
Если целочисленное значение со знаком зациклено (переполнено), все ставки отключены. Если вы используете систему дополнения до двух, вы все равно можете вычесть их, присвоив им значение unsigned, но это не переносимо. Мусор на входе, мусор на выходе.   -  person Alok Singhal    schedule 14.01.2010
comment
Ваш переформулированный вопрос по-прежнему имеет мало смысла. Ценность не оборачивается сама по себе. Когда у вас есть x, этот x никуда не перенесется, пока вы не начнете его каким-то образом изменять. Если вы модифицируете его, покажите нам, как. В настоящее время нет никакого способа осмысленно выяснить, о чем вы говорите.   -  person AnT    schedule 14.01.2010
comment
Alok: переполнение целого числа со знаком является неопределенным поведением.   -  person Joey    schedule 22.09.2011
comment
Также стоит взглянуть на: Определено ли поведение целочисленного вычитания без знака? :)   -  person LihO    schedule 24.02.2013


Ответы (8)


Предполагая два целых числа unsigned:

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

Уточняю: сценарий, описанный автором постера, кажется, сбивает людей с толку, но он типичен для монотонно увеличивающихся счетчиков фиксированной ширины, таких как аппаратные счетчики тиков или порядковые номера в протоколах. Счетчик идет (например, для 8 бит) 0xfc, 0xfd, 0xfe, 0xff, 0x00, 0x01, 0x02, 0x03 и т. д., и вы знаете, что из двух значений x и y, которые у вас есть, x приходит позже. Если x==0x02 и y==0xfe, вычисление xy (как 8-битный результат) даст правильный ответ 4, предполагая, что вычитание двух n-битных значений выполняет перенос по модулю 2< sup>n - что C99 гарантирует для вычитания значений без знака. (Примечание: стандарт C не гарантирует такое поведение для вычитания значений со знаком.)

person Matthew Slattery    schedule 14.01.2010
comment
Если вы используете беззнаковый тип в C, он гарантированно завершится, как описано здесь, независимо от того, использует ли система дополнение до 2 с (§6.2.5, параграф 9: Вычисление, включающее беззнаковые операнды, никогда не может переполниться, потому что результат которое не может быть представлено результирующим целочисленным типом без знака, уменьшается по модулю числа, которое на единицу больше, чем наибольшее значение, которое может быть представлено результирующим типом.) - person Stephen Canon; 14.01.2010
comment
Стоит повторить, что Стивен совершенно прав. беззнаковая арифметика полностью определена в C на основе ширины беззнакового типа. - person caf; 14.01.2010
comment
@Stephen: OP изменил вопрос, и изначально его формулировка подразумевала, что он ожидал, что подписанные значения будут предсказуемо обернуты. Вы, конечно, совершенно правы. - person Alok Singhal; 14.01.2010
comment
@endolith: спасибо, что прокомментировали этот старый ответ и подтолкнули меня к его редактированию. Вы и другие здесь совершенно правы; в то время, когда я ответил на это, я уже сталкивался с этой уловкой, но не знал о гарантии в стандарте, что она всегда работает для беззнакового вычитания. - person Matthew Slattery; 11.02.2012
comment
Преобразование неподписанного целого числа в подписанное целое небезопасно. §6.3.1.3 Целые числа со знаком и без знака: новый тип является знаковым, и значение не может быть представлено в нем; либо результат определяется реализацией, либо выдается сигнал, определяемый реализацией. - person czz; 11.04.2013

Вот еще немного подробностей о том, почему это «просто работает», когда вы вычитаете «меньшее» из «большего».

Несколько вещей, связанных с этим…
1. В аппаратном обеспечении вычитание использует сложение: соответствующий операнд просто инвертируется перед добавлением.
2. В дополнении до двух (которое используется почти во всем) целое число инвертируется. инвертируя все биты, а затем добавляя 1.

Аппаратное обеспечение делает это более эффективно, чем кажется из приведенного выше описания, но это основной алгоритм вычитания (даже когда значения беззнаковые).

Итак, давайте рис. 2 — 250, используя 8-битные целые числа без знака. В двоичном виде у нас есть

  0 0 0 0 0 0 1 0  
- 1 1 1 1 1 0 1 0

Мы отрицаем вычитаемый операнд, а затем добавляем. Напомним, что для инвертирования мы инвертируем все биты, а затем добавляем 1. После инвертирования битов второго операнда мы имеем

0 0 0 0 0 1 0 1  

Тогда после прибавления 1 имеем

0 0 0 0 0 1 1 0  

Теперь выполняем сложение...

  0 0 0 0 0 0 1 0   
+ 0 0 0 0 0 1 1 0

= 0 0 0 0 1 0 0 0 = 8, which is the result we wanted from 2 - 250
person Purdude    schedule 27.05.2010

Может я не понимаю, но что не так с:

unsigned r = x - y;

person GManNickG    schedule 14.01.2010
comment
В случае байтов: 255 - ( - 10) = ? - person ThinkJet; 14.01.2010
comment
Он сказал, что оба не подписаны. - person GManNickG; 14.01.2010
comment
20 - 200 не дает 0 и он это имел в виду - person Idan; 20.03.2014

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

person AnT    schedule 14.01.2010
comment
x - y можно обернуть, если вы используете подписанные целые числа. - person jamesdlin; 14.01.2010
comment
@jamesdlin: В теме вопроса четко и ясно указано, что мы вычитаем целые числа без знака. - person AnT; 14.01.2010
comment
Ой, извините. В самом содержании вопроса говорилось просто ints, но я забыл обратить внимание на само название. - person jamesdlin; 14.01.2010

Это эффективный способ определить количество свободного места в кольцевом буфере или выполнить управление потоком в скользящем окне. Используйте unsigned ints для head и tail - увеличьте их и дайте им обернуться! Длина буфера должна быть степенью двойки.

free = ((head - tail) & size_mask), где size_mask — это 2^n-1 размер буфера или окна.

person C guy    schedule 22.09.2011
comment
Если вы хотите снизить производительность, вы можете разрешить любую положительную длину буфера, используя оператор модуля: free = ((head - tail) % buf_len); - person oosterwal; 05.09.2012

Просто чтобы поместить уже правильный ответ в код:

Если вы знаете, что x — меньшее значение, следующий расчет просто работает:

int main()
{
    uint8_t x = 0xff;
    uint8_t y = x + 20;
    uint8_t res = y - x;
    printf("Expect 20: %d\n", res); // res is 20

    return 0;
}

Если вы не знаете, какой из них меньше:

int main()
{
    uint8_t x = 0xff;
    uint8_t y = x + 20;
    int8_t res1 = (int8_t)x - y;
    int8_t res2 = (int8_t)y - x;
    printf("Expect -20 and 20: %d and %d\n", res1, res2);

    return 0;
}

Где разница должна быть в пределах диапазона uint8_t в этом случае.

Эксперимент с кодом помог мне лучше понять решение.

person Tarion    schedule 15.08.2017

Проблема должна быть сформулирована следующим образом:

Предположим, что положение (угол) двух указателей a и b часов задается uint8_t. Вся окружность делится на 256 значений uint8_t. Как можно эффективно рассчитать меньшее расстояние между двумя указателями?

Решение:

uint8_t smaller_distance = abs( (int8_t)( a - b ) );

Я подозреваю, что нет ничего более эффективного, иначе было бы что-то более эффективное, чем abs().

person hpc64    schedule 20.12.2017

Чтобы повторить ответ всех остальных, если вы просто вычтете два и интерпретируете результат как неподписанный, все будет в порядке.

Если только у вас нет явного контрпримера.

Ваш пример x = 0x2, y= 0x14 не приведет к 0x4, он приведет к 0xEE, если только у вас нет дополнительных ограничений на математику, которые не указаны.

person MSN    schedule 14.01.2010
comment
да, это был тип - исправлено сейчас. - person mgag; 14.01.2010