Natknąłem się na kod kogoś, kto wydaje się sądzić, że istnieje problem z odejmowaniem liczby całkowitej bez znaku od innej liczby całkowitej tego samego typu, gdy wynik byłby ujemny. Tak więc taki kod byłby niepoprawny, nawet gdyby działał na większości architektur.
unsigned int To, Tf;
To = getcounter();
while (1) {
Tf = getcounter();
if ((Tf-To) >= TIME_LIMIT) {
break;
}
}
To jedyny niejasny cytat ze standardu C, jaki udało mi się znaleźć.
Obliczenie obejmujące operandy bez znaku nigdy nie może przekroczyć wartości, ponieważ wynik, którego nie można przedstawić za pomocą wynikowego typu liczby całkowitej bez znaku, jest redukowany modulo o liczbę, która jest o jeden większa niż największa wartość, która może być reprezentowana przez wynikowy typ.
Przypuszczam, że można by przyjąć, że ten cytat oznacza, że gdy prawy operand jest większy, operacja jest dostosowywana tak, aby była sensowna w kontekście liczb obciętych modulo.
to znaczy
0x0000 - 0x0001 == 0x 1 0000 - 0x0001 == 0xFFFF
w przeciwieństwie do używania semantyki ze znakiem zależnej od implementacji:
0x0000 - 0x0001 == (unsigned) (0 + -1) == (0xFFFF ale także 0xFFFE lub 0x8001)
Która lub jaka interpretacja jest właściwa? Czy to w ogóle jest zdefiniowane?
Odpowiedzi:
Wynik odejmowania generujący liczbę ujemną w typie bez znaku jest dobrze zdefiniowany:
Jak widać,
(unsigned)0 - (unsigned)1
wynosi -1 modulo UINT_MAX + 1, czyli innymi słowy UINT_MAX.Zauważ, że chociaż mówi "Obliczenie obejmujące operandy bez znaku nigdy nie może się przepełnić", co może prowadzić do wniosku, że ma zastosowanie tylko do przekroczenia górnej granicy, jest to przedstawiane jako motywacja dla rzeczywistej wiążącej części zdania: "a wynik, który nie może być reprezentowany przez wynikowy typ liczby całkowitej bez znaku, jest redukowany modulo liczba, która jest o jeden większa niż największa wartość, która może być reprezentowana przez wynikowy typ. " Ta fraza nie ogranicza się do przekroczenia górnej granicy typu i dotyczy w równym stopniu wartości zbyt niskich, aby mogły być przedstawione.
źródło
uint
zawsze miał reprezentować matematyczny pierścień liczb całkowitych0
doUINT_MAX
, z operacjami dodawania i mnożenia moduloUINT_MAX+1
, a nie dlatego, że przelewu. Jednak nasuwa się pytanie, dlaczego, jeśli pierścienie są tak podstawowym typem danych, język nie oferuje bardziej ogólnego wsparcia dla pierścieni o innych rozmiarach.Kiedy pracujesz z typami bez znaku , ma miejsce arytmetyka modularna (znana również jako zachowanie „zawijania” ). Aby zrozumieć arytmetykę modularną , wystarczy spojrzeć na te zegary:
9 + 4 = 1 ( 13 mod 12 ), więc w drugą stronę jest: 1 - 4 = 9 ( -3 mod 12 ). Ta sama zasada obowiązuje podczas pracy z typami bez znaku. Jeśli typ wyniku to
unsigned
, to wykonywana jest arytmetyka modularna.Teraz spójrz na następujące operacje przechowujące wynik jako
unsigned int
:Jeśli chcesz się upewnić, że wynik jest
signed
, zapisz go wsigned
zmiennej lub przerzuć nasigned
. Jeśli chcesz uzyskać różnicę między liczbami i upewnić się, że arytmetyka modularna nie zostanie zastosowana, powinieneś rozważyć użycieabs()
funkcji zdefiniowanej wstdlib.h
:Zachowaj szczególną ostrożność, szczególnie w warunkach pisania, ponieważ:
ale
źródło
int d = abs(five - seven);
nie jest dobra. Pierwszafive - seven
jest obliczana: promocja pozostawia typy operandów jakounsigned int
, wynik jest obliczany modulo(UINT_MAX+1)
i obliczany doUINT_MAX-1
. Wtedy ta wartość jest rzeczywistym parametrem toabs
, co jest złą wiadomością.abs(int)
powoduje przekazanie argumentu przez niezdefiniowane zachowanie, ponieważ nie jest on w zakresie iabs(long long)
prawdopodobnie może przechowywać wartość, ale niezdefiniowane zachowanie występuje, gdy wartość zwracana jest zmuszanaint
do zainicjowaniad
.operator T()
. Dodatek w dwóch omawianych wyrażeniach jest wykonywany w typieunsigned int
na podstawie typów operandów. Wynik dodawania tounsigned int
. Następnie ten wynik jest niejawnie konwertowany na typ wymagany w kontekście, konwersja, która kończy się niepowodzeniem, ponieważ wartość nie jest reprezentowalna w nowym typie.double x = 2/3;
vsdouble y = 2.0/3;
Cóż, pierwsza interpretacja jest poprawna. Jednak twoje rozumowanie dotyczące „semantyki ze znakiem” w tym kontekście jest błędne.
Twoja pierwsza interpretacja jest poprawna. Arytmetyka bez znaku jest zgodna z zasadami arytmetyki modulo, co oznacza, że
0x0000 - 0x0001
wartościuje do0xFFFF
bez znaku jest dla 32-bitowych typów bez znaku.Jednak druga interpretacja (oparta na „semantyce ze znakiem”) jest również wymagana do uzyskania tego samego wyniku. To znaczy, nawet jeśli oceniasz
0 - 1
w domenie typu podpisanego i otrzymujesz-1
jako wynik pośredni,-1
nadal jest to wymagane do wyprodukowania0xFFFF
gdy później zostanie przekonwertowany na typ bez znaku. Nawet jeśli na niektórych platformach używane są egzotyczne reprezentacje liczb całkowitych ze znakiem (uzupełnienie 1, wielkość ze znakiem), platforma ta nadal musi stosować zasady arytmetyki modulo podczas konwersji wartości całkowitych ze znakiem na liczby całkowite bez znaku.Na przykład ta ocena
jest jeszcze gwarancją produkować
UINT_MAX
wc
, nawet jeśli platforma jest za pomocą egzotycznych reprezentacji dla podpisanych liczb całkowitych.źródło
W przypadku liczby typu bez znaku
unsigned int
lub większej, w przypadku braku konwersji typu,a-b
definiuje się liczbę bez znaku, która po dodaniub
da wynika
. Konwersja liczby ujemnej na liczbę bez znaku jest definiowana jako otrzymanie liczby, która po dodaniu do pierwotnej liczby odwróconej znakiem da zero (więc zamiana -5 na unsigned da wartość, która po dodaniu do 5 da zero) .Zauważ, że liczby bez znaku mniejsze niż
unsigned int
mogą być promowane do typuint
przed odejmowaniem, zachowaniea-b
będzie zależeć od rozmiaruint
.źródło