2011-11-09 15 views
7

Natknąłem się na następujący przykład na wikipedia (http://en.wikipedia.org/wiki/Type_conversion#Implicit_type_conversion).Niejawna konwersja typu w C

#include <stdio.h> 

int main() 
{ 
    int i_value = 16777217; 
    float f_value = 16777217.0; 
    printf("The integer is: %i\n", i_value); // 16777217 
    printf("The float is: %f\n", f_value); // 16777216.000000 
    printf("Their equality: %i\n", i_value == f_value); // result is 0 
} 

Ich wyjaśnienie: „To dziwne zachowanie spowodowane jest niejawny obsady i_value się unosić, gdy jest ona w porównaniu z f_value; odlew, który traci precyzję, dzięki czemu wartości są porównywane inny”

Czy to nie jest złe? Jeśli i_value zostały wyrzucone do float, to oba miałyby tę samą stratę w precyzji i byłyby równe. Tak więc i_value należy odrzucić, aby podwoić liczbę.

+0

Z g ++ (GCC 4.6.2) Otrzymuję "1" dla równości. –

+0

@Kerrek: I ja. W VS otrzymuję 0. –

+0

@OliCharlesworth: Jestem ciekawa zmiany literału na 'f' lub typ na' double' - otrzymuję '1' we wszystkich przypadkach ... –

Odpowiedz

7

Nie, w przypadku operatora równości, "zwykłe arytmetyczne konwersje" występują, które rozpoczynają się:

  • Po pierwsze, jeśli odpowiadające rzeczywistym typem obu argumentu jest long double The inny operand jest przekształcany, bez zmiany domeny typu , na typ, którego odpowiadającym rzeczywistym typem jest long double.
  • W przeciwnym razie, jeśli odpowiadający rzeczywisty typ dowolnego argumentu to double, drugi operand jest przekształcany, bez zmiany domeny typu , na typ, którego odpowiadającym rzeczywistym typem jest double.
  • W przeciwnym razie, jeśli odpowiadający rzeczywisty typ dowolnego argumentu to float, drugi operand jest przekształcany, bez zmiany domeny typu , w typ, którego odpowiadającym rzeczywistym typem jest float.

Ten ostatni przypadek dotyczy tutaj: i_value przekształca się float.

Dlatego, że widać dziwny wynik z porównania mimo to właśnie z powodu tego zastrzeżeniem zwykłych arytmetycznych konwersji:

Wartości pływających argumentów i wyników pływające Wyrażenia mogą być reprezentowane z większą precyzją i zasięgiem niż wymagane przez typ; typy nie są przez to zmieniane.

To, co się dzieje: typ przekształconego i_value wciąż float, ale w tym wyrażeniu kompilator jest wykorzystanie tej szerokości geograficznej i reprezentowanie go w większą precyzją niż float. Jest to typowe zachowanie kompilatora podczas kompilacji dla punktu zmiennoprzecinkowego zgodnego z 387, ponieważ kompilator pozostawia wartości tymczasowe na stosie liczb zmiennoprzecinkowych, który przechowuje liczby zmiennoprzecinkowe w formacie 80-bitowej rozszerzonej precyzji.

Jeśli Twój kompilator to gcc, możesz wyłączyć tę dodatkową precyzję, podając opcję wiersza polecenia -ffloat-store.

+0

Na x64 gcc używa jawnej instrukcji cvtsi2ssl, która konwertuje liczbę całkowitą na zmienną. Jednak na x86 tak właśnie się dzieje i ta większa precyzja jest nawet większa niż podwójna. –

+0

@ konrad.kruczynski: Tak, możesz uzyskać ten sam wynik na x86, podając opcję '-mfpmath = sse' (która wymaga również' -msse' lub opcji, która to oznacza). – caf

-1

Wierzę, że największą wartością całkowitą, jaką może pomieścić 32-bitowy zmiennoprzecinkowy IEEE, jest 1048576, który jest mniejszy niż liczba powyżej. Tak więc z całą pewnością prawdą jest, że wartość zmiennoprzecinkowa nie będzie miała dokładnie 16777217.

Część, której nie jestem pewien, to sposób, w jaki kompilator dokonuje porównania między dwoma różnymi typami liczb (np. Zmiennoprzecinkowe i int) . Mogę myśleć o trzech różnych sposobów, to może odbywać się:

1) Konwersja obie wartości do „unosić” (powinien to uczynić Wartości takie same, więc to nie to, co robi kompilator)

2) jest prawdopodobnie Konwertuj obie wartości na "int" (to może, ale nie musi, pokazywać je tak samo ... konwersja na wartość int jest często skracana, więc jeśli wartość zmiennoprzecinkowa wynosi 16777216.99999, wówczas konwersja na "int" zostanie obcięta)

3) Konwertuj obie wartości na "podwójne". Domyślam się, że to właśnie zrobiłby kompilator. Jeśli tak robi kompilator, te dwie wartości z pewnością będą inne. Podwójny może dokładnie pomieścić 16777217, a także dokładnie przedstawia wartość zmiennoprzecinkową, z której 16777217.0 konwertuje (co nie jest dokładnie 16777217.0).

+3

1048576 to 2^20, więc jest to niepoprawne. –

0

Istnieje kilka dobrych odpowiedzi tutaj. Musisz bardzo ostrożnie konwertować różne liczby całkowite i różne reprezentacje zmiennoprzecinkowe.

Generalnie nie testuję liczb zmiennoprzecinkowych pod kątem równości, szczególnie jeśli jeden z nich pochodzi z niejawnego lub jawnego rzutowania z typu całkowitego. Pracuję nad aplikacją, która jest pełna obliczeń geometrycznych. W miarę możliwości pracujemy ze znormalizowanymi liczbami całkowitymi (przez wymuszenie maksymalnej precyzji, którą zaakceptujemy w danych wejściowych). W przypadkach, w których musisz użyć zmiennoprzecinkowej, zastosujemy wartość bezwzględną do różnicy, jeśli potrzebujemy porównania.

Powiązane problemy