2015-12-21 7 views
6

Mam dwie zmienne (test1 i test2), oba niepodpisane. Muszę sprawdzić, który z nich jest większy.odejmowanie między wartościami bez znaku - nieoczekiwany wynik

Próbuję zrozumieć, co się stanie, jeśli wystąpi przepełnienie.

Moja pierwsza próba została wykonana z uint8_t typu danych (char):

#include <stdio.h> 
#include <stdint.h> 
#include <math.h> 

int main() 
{ 
    uint8_t test1 = 0; 
    printf("test1 = %d\n", test1); 

    uint8_t test2 = pow(2, 8 * sizeof(test1)) - 1; //max holdable value of uint8_t 
    printf("test2 = %d\n", test2); 

    uint8_t test3 = test1 - test2; 
    printf("test1 - test2 = %d\n", test3); 

    if ((test1 - test2) == 0) 
     printf("test1 == test2\n"); 
    if ((test1 - test2) > 0) 
     printf("test1 > test2\n"); 
    if ((test1 - test2) < 0) 
     printf("test1 < test2\n"); 

    if (test3 == 0) 
     printf("test1 == test2\n"); 
    if (test3 > 0) 
     printf("test1 > test2\n"); 
    if (test3 < 0) 
     printf("test1 < test2\n"); 

    return 0; 
} 

wyjściowa:

test1 = 0                                      
test2 = 255                                      
test1 - test2 = 1                                    
test1 < test2                                     
test1 > test2 

Co? Odejmowanie i zapisywanie w zmiennej, a następnie sprawdzanie, to inne niż sprawdzanie odejmowania w locie?

Drugie badanie przeprowadzono z uint32_t (długi) typu danych:

#include <stdio.h> 
#include <stdint.h> 
#include <math.h> 

int main() 
{ 
    uint32_t test1 = 0; 
    printf("test1 = %d\n", test1); 

    uint32_t test2 = pow(2, 8 * sizeof(test1)) - 1; //max holdable value of uint32_t 
    printf("test2 = %lu\n", test2); 

    uint32_t test3 = test1 - test2; 
    printf("test1 - test2 = %d\n", test3); 

    if ((test1 - test2) == 0) 
     printf("test1 == test2\n"); 
    if ((test1 - test2) > 0) 
     printf("test1 > test2\n"); 
    if ((test1 - test2) < 0) 
     printf("test1 < test2\n"); 

    if (test3 == 0) 
     printf("test1 == test2\n"); 
    if (test3 > 0) 
     printf("test1 > test2\n"); 
    if (test3 < 0) 
     printf("test1 < test2\n"); 

    return 0; 
} 

wyjściowa:

test1 = 0                                      
test2 = 4294967295                                    
test1 - test2 = 1                                    
test1 > test2                                     
test1 > test2 

Co ??? Teraz odejmowanie i zapisywanie w zmiennej, a następnie sprawdzanie, to tak samo jak sprawdzanie odejmowania w locie?

SO Spodziewałem się, że odejmowanie między wartościami unsigned (bez wyraźnego cast) zawsze zwraca wartość> = 0. ale robi odejmowanie wewnątrz IF prowadzi do nieoczekiwanych rezultatów.

Teraz jestem zdezorientowany. Czy ktoś może wyjaśnić mi to zachowanie?

+2

W trzeciej linii w treści głównej: 'uint8_t test2 = pow (2, 8 * sizeof (test1)) - 1; 'to nie jest naprawdę sensowne, jeśli weźmiesz 2 do potęgi' 8 * sizeof (test1) ', to przepełni' uint8_t' i uzyska wartość zero. Ponieważ jest niepodpisany, przepełnienie zawsze ma dobrze zdefiniowane wartości. Zamiast tego należy zamiast tego napisać "-1", dla jasności. –

+1

Robisz to bardzo ciężko - czy uważasz, że to pytanie można uprościć do samego przypisania testu 1 i testu 2, a następnie przykłady wyrażeń, które dają oczekiwany wynik, nie oczekuj? Wydaje się, że jest tu zbyt dużo niepotrzebnego kodu. – Clifford

+1

@ChrisBeck: lub 'uint8_t test1 = ~ 0;' a następnie przypisanie wartości ujemnej do typu bez znaku, lub jeszcze lepiej użyj 'UCHAR_MAX' lub' std :: numeric_limits :: max() ' – Clifford

Odpowiedz

5

powodu typu promocji, twój uint8 będzie promowany na platformie opartej int, który może być większa niż 8-bitowy (zwykle 32-bitowe) w większości dzisiejszych czasach Computer.

ta linia:

if ((test1 - test2) < 0) 

jeśli równoważne

if ((int)((int)test1 - (int)test2) < 0) = -255 < 0 

co jest prawdą.

Jednak dla tej linii,

uint8_t test3 = test1 - test2; 

jest to równoważne

uint8_t test3 = (uint8_t)((int)test1 - (int)test2); //(uint8_t)(-255) = 1!!; 

więc zarówno test1 - test2 < 0 i test3 > 0 są (witamy w świecie komputera!) Poprawne!

To wyjaśnia twój wynik.

Ale uint32, bo jest z „wyższej” rangi niż rodzimej int, więc to nie jest „promowane” i pozostaje jako uint32 cały czas, lub innymi słowy

ta linia:

if ((test1 - test2) > 0) 

jeśli odpowiednik

if ((uint32_t)((uint32_t)test1 - (uint32_t)test2) > 0) = large positive number > 0 

i tej linii

uint32_t test3 = test1 - test2; 

jest równoważna

uint32_t test3 = (uint32_t)test1 - (uint32_t)test2; //also large positive number 

więc masz zarówno test1 - test2 > 0 i test3 > 0.

W obu przypadkach oba mogą być poprawne tylko w przypadku komputerów 8-bitowych. Albo załóżmy, że w przyszłości, native int jest 64-bitowy, a następnie oba przypadki będą poprawne zbyt ...

+0

W pierwszym przykładzie po promocji wyrażenie to jest "-255 <0" – Clifford

+0

poprawione. Dzięki – Ian

+0

Podoba mi się odpowiedź @Clifford, ale wybieram tę, ponieważ ma dobry przykład na podstawie mojego kodu testowego. Dzięki wam wszystkim! – Suxsem

9

Obowiązuje nieco tajemniczy kod type promotion rules. W pierwszym przykładzie, gdzie argumenty są uint8_t, dla wyrażenia:

test1 - test2 

oba operandy są niejawnie promowany do intprzed odejmowania, a samo wyrażenie ma typ int.

+0

+10 bardzo proste wyjaśnienie – Ian

+0

Niestety, jest to również nieprawidłowe. "Reguły promocyjne typu arcane" w rzeczywistości nie mają zastosowania do "unsigned char" lub do "unsigned' 8-bitowych typów. W rzeczywistości jest to przypadek, w którym zastrzeżenie na dole dołączonego linku "Niestety, to nie jest dokładne określenie tych zasad". Operacje matematyczne na niepodpisanych typach używają arytmetyki modulo. – Peter

+0

@Peter: Tak jak powiedziałem, są one tajemnicze i radzę oczekiwać niespodziewanych i nigdy nie używać char lub krótkich typów jako obiektów arytmetycznych i unikania mieszanych podpisanych i unsigned wyrażeń. Powiedział, że nie jestem przekonany, że masz rację; od C99 6.3.1.1: * "Jeśli int może reprezentować wszystkie wartości oryginalnego typu, wartość jest konwertowana na int, w przeciwnym razie jest konwertowana na unsigned int." *. Tak więc, o ile prawdą jest, że arytmetyczna modulo występuje na typach niepodpisanych, do czasu oszacowania operatora '-' operandy nie są już unsigned. – Clifford

1

faktycznie podczas odejmowania jak

0 - 255; // It results -255 

i staramy się przechowywać go w unsigned int, więc przechowywane w następujący sposób:

-1 in Unsigned int (1 Byte) as 255 
-2 as 254 
-3 as 253 
. 
. 
. 
-255 as 1 

To dlatego masz to ans. Powodem tego jest to, że przechowuje on zmienne, w których używa 1 lub 2 komplementów, nie ma pojęcia, ale jest pewne, że jest on powiązany z tym

+0

To naprawdę nie wyjaśnia OP, dlaczego po 'test3 = test1 - test2',' test3! = Test1 - test2' (co jest prawdopodobnie o wiele prostsze i zwięzłe pytanie, które powinien był zadać). – Clifford

+0

Właściwie to wyjaśniałem, że jeśli obliczenia są wykonywane przy użyciu niepodpisanego bajtu, to będziesz miał wynik powyżej, ale kiedy bezpośrednio go wypróbujemy, jeśli (....), wtedy traktujemy go jako podwójny nie jako niepodpisany bajt ... Przepraszam, że mam nie wspominałem o tym .. To jest to, co wiem o tym. –

+0

Doceniam to, co mówiliście, ale to nie jest odpowiedź na to pytanie. I myślę, że masz na myśli int nie podwójny? – Clifford

Powiązane problemy