Chcę zdefiniować funkcję, która przyjmuje unsigned intjako argument i zwraca intkongruentny 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 intjest zdefiniowane w implementacji dla niektórych danych wejściowych, rzutowanie z powrotem do unsignedjest 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 intbez 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_MAXwynosi 32767INT_MINrówna się -2 32 + 32768UINT_MAXrówna się 2 32 - 1- Arytmetyka włączona
intto modulo 2 32 (w zakresieINT_MINdoINT_MAX) std::numeric_limits<int>::is_modulojest prawdziwy- Rzutowanie unsigned
nna int zachowuje wartość dla 0 <= n <= 32767 i daje zero w przeciwnym razie
W tej hipotetycznej implementacji istnieje dokładnie jedna intwartość przystająca (mod UINT_MAX + 1) do każdej unsignedwartoś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_modulojest to całkowicie bezużyteczne z wielu powodów. Po pierwsze, może tak być truenawet wtedy, gdy rzutowania bez znaku do podpisania nie działają dla dużych wartości bez znaku. Po drugie, może to być truenawet 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_modulotego, 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ę.