Chcę zdefiniować funkcję, która przyjmuje unsigned int
jako argument i zwraca int
kongruentny modulo UINT_MAX + 1 do argumentu.
Pierwsza próba może wyglądać tak:
int unsigned_to_signed(unsigned n)
{
return static_cast<int>(n);
}
Jednak, jak wie każdy prawnik zajmujący się językiem, rzutowanie wartości większych niż INT_MAX z unsigned na signed jest zdefiniowane przez implementację.
Chcę to zaimplementować w taki sposób, że (a) opiera się tylko na zachowaniu wymaganym przez specyfikację; i (b) kompiluje się w no-op na dowolnym nowoczesnym komputerze i optymalizującym kompilatorze.
A co do dziwacznych maszyn ... Jeśli nie ma podpisanego modulo int congruent UINT_MAX + 1 do unsigned int, powiedzmy, że chcę zgłosić wyjątek. Jeśli jest więcej niż jeden (nie jestem pewien, czy jest to możliwe), powiedzmy, że chcę największego.
OK, druga próba:
int unsigned_to_signed(unsigned n)
{
int int_n = static_cast<int>(n);
if (n == static_cast<unsigned>(int_n))
return int_n;
// else do something long and complicated
}
Nie przejmuję się zbytnio wydajnością, gdy nie jestem na typowym systemie dwójkowym, bo moim skromnym zdaniem jest to mało prawdopodobne. A jeśli mój kod stanie się wąskim gardłem we wszechobecnych systemach wielkości znaków w 2050 roku, cóż, założę się, że ktoś może to rozgryźć i zoptymalizować.
Otóż, ta druga próba jest bliska tego, czego chcę. Chociaż rzutowanie do int
jest zdefiniowane w implementacji dla niektórych danych wejściowych, rzutowanie z powrotem do unsigned
jest gwarantowane przez standard, aby zachować wartość modulo UINT_MAX + 1. Tak więc warunek sprawdza dokładnie to, czego chcę, i nie skompiluje się do niczego w żadnym systemie, który prawdopodobnie napotkam.
Jednak ... nadal rzutuję do int
bez uprzedniego sprawdzenia, czy wywoła zachowanie zdefiniowane w implementacji. W jakimś hipotetycznym systemie w 2050 r. Mógłby zrobić nie wiadomo co. Powiedzmy, że chcę tego uniknąć.
Pytanie: Jak powinna wyglądać moja „trzecia próba”?
Podsumowując, chcę:
- Przesyłanie z int unsigned do int signed int
- Zachowaj wartość mod UINT_MAX + 1
- Wywołaj tylko standardowe zachowanie
- Skompiluj do no-op na typowej maszynie z uzupełnieniem do dwóch z optymalizującym kompilatorem
[Aktualizacja]
Podam przykład, aby pokazać, dlaczego nie jest to trywialne pytanie.
Rozważmy hipotetyczną implementację C ++ z następującymi właściwościami:
sizeof(int)
równa się 4sizeof(unsigned)
równa się 4INT_MAX
wynosi 32767INT_MIN
równa się -2 32 + 32768UINT_MAX
równa się 2 32 - 1- Arytmetyka włączona
int
to modulo 2 32 (w zakresieINT_MIN
doINT_MAX
) std::numeric_limits<int>::is_modulo
jest prawdziwy- Rzutowanie unsigned
n
na int zachowuje wartość dla 0 <= n <= 32767 i daje zero w przeciwnym razie
W tej hipotetycznej implementacji istnieje dokładnie jedna int
wartość przystająca (mod UINT_MAX + 1) do każdej unsigned
wartości. Więc moje pytanie byłoby dobrze zdefiniowane.
Twierdzę, że ta hipotetyczna implementacja C ++ jest w pełni zgodna ze specyfikacjami C ++ 98, C ++ 03 i C ++ 11. Przyznaję, że nie zapamiętałem każdego słowa z nich wszystkich ... Ale wydaje mi się, że uważnie przeczytałem odpowiednie rozdziały. Jeśli więc chcesz, żebym zaakceptował twoją odpowiedź, musisz albo (a) zacytować specyfikację, która wyklucza tę hipotetyczną implementację, albo (b) poprawnie ją obsłużyć.
Rzeczywiście, prawidłowa odpowiedź musi uwzględniać każdą hipotetyczną implementację dozwoloną przez normę. To właśnie z definicji oznacza „powoływanie się tylko na zachowanie narzucone przez normy”.
Nawiasem mówiąc, pamiętaj, że std::numeric_limits<int>::is_modulo
jest to całkowicie bezużyteczne z wielu powodów. Po pierwsze, może tak być true
nawet wtedy, gdy rzutowania bez znaku do podpisania nie działają dla dużych wartości bez znaku. Po drugie, może to być true
nawet w systemach z dopełnieniem lub ze znakiem, jeśli arytmetyka jest po prostu modulo całego zakresu liczb całkowitych. I tak dalej. Jeśli Twoja odpowiedź zależy od is_modulo
tego, jest błędna.
[Aktualizacja 2]
Odpowiedź hvd nauczyła mnie czegoś: Moja hipotetyczna implementacja liczb całkowitych w C ++ nie jest dozwolona przez współczesne C. Standardy C99 i C11 są bardzo szczegółowe w zakresie reprezentacji liczb całkowitych ze znakiem; w rzeczywistości zezwalają tylko na uzupełnienie do dwóch, uzupełnienie do jedności i wielkość znaku (sekcja 6.2.6.2 akapit (2);).
Ale C ++ to nie C. Jak się okazuje, ten fakt leży w samym sercu mojego pytania.
Oryginalny standard C ++ 98 był oparty na znacznie starszym C89, który mówi (sekcja 3.1.2.5):
Dla każdego typu liczb całkowitych ze znakiem istnieje odpowiedni (ale inny) typ liczby całkowitej bez znaku (oznaczony słowem kluczowym bez znaku), który wykorzystuje tę samą ilość pamięci (w tym informacje o znakach) i ma takie same wymagania dotyczące wyrównania. Zakres nieujemnych wartości typu liczby całkowitej ze znakiem jest podzakresem odpowiedniego typu liczby całkowitej bez znaku, a reprezentacja tej samej wartości w każdym typie jest taka sama.
C89 nie mówi nic o posiadaniu tylko jednego bitu znaku lub dopuszczaniu tylko wielkości uzupełnienia do dwóch / dopełnienia jedynkowego / wielkości znaku.
Standard C ++ 98 przyjął ten język prawie dosłownie (sekcja 3.9.1 akapit (3)):
Dla każdego z typów liczb całkowitych ze znakiem istnieje odpowiedni (ale inny) typ liczby całkowitej bez znaku : "
unsigned char
", "unsigned short int
", "unsigned int
" i "unsigned long int
", z których każdy zajmuje taką samą ilość pamięci i ma takie same wymagania dotyczące wyrównania (3.9 ) jako odpowiedni typ liczby całkowitej ze znakiem; to znaczy, każdy typ liczby całkowitej ze znakiem ma taką samą reprezentację obiektu, jak odpowiadający mu typ liczby całkowitej bez znaku . Zakres nieujemnych wartości typu liczby całkowitej ze znakiem jest podzakresem odpowiedniego typu liczby całkowitej bez znaku, a reprezentacja wartości każdego odpowiedniego typu ze znakiem / bez znaku powinna być taka sama.
Standard C ++ 03 używa zasadniczo identycznego języka, podobnie jak C ++ 11.
Żadna standardowa specyfikacja C ++ nie ogranicza swoich reprezentacji liczb całkowitych ze znakiem do żadnej specyfikacji języka C, o ile wiem. I nie ma nic nakazującego ani jednego kawałka znaku ani niczego w tym rodzaju. Mówi się tylko, że nieujemne liczby całkowite ze znakiem muszą być podzakresem odpowiadających im liczb całkowitych bez znaku.
Tak więc ponownie twierdzę, że INT_MAX = 32767 z INT_MIN = -2 32 +32768 jest dozwolone. Jeśli twoja odpowiedź zakłada inaczej, jest nieprawidłowa, chyba że zacytujesz standard C ++, który udowodni, że się mylę.