C99 Strict - Dziwne zachowanie przy obliczeniach zmiennoprzecinkowych

C99 Strict - Dziwne zachowanie przy obliczeniach zmiennoprzecinkowych
KM
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 4 lata
  • Postów:473
0

Mógłby mi ktoś wytłumaczyć ten fenomen? http://ideone.com/cI3DBT

Kopiuj
#include "stdio.h"

int main() {
	double x = 0.478;
	double a = 1000.0;
	
	int ix;
	ix = (int) (a * x);
	double dx;
	dx = a * x;
	int idx;
	idx = (int) dx;
	
	printf("double x = %f, double a = %f\n\n", x, a);
	printf("int ix = (int) (a * x) = %d\n", ix);
	printf("double dx = a * x = %f\n", dx);
	printf("int idx = (int) dx = (int) (a * x) = %d\n", idx);
	return 0;
}

Wynik tego jest taki:

Kopiuj
double x = 0.478000, double a = 1000.000000

int ix = (int) (a * x) = 477
double dx = a * x = 478.000000
int idx = (int) dx = (int) (a * x) = 478

Nie rozumiem tego. Wiem, że operacje na liczbach zmiennoprzecinkowych nie są przemienne ani łączne. Problem w tym, że ja zachowuję tu przecież kolejność wykonywanych działań, a mimo to wychodzą różne wyniki! Jak C99 obsługuje liczby zmiennoprzecinkowe?

Z góry dzięki.

KM
Przykład wzięty stąd: http://www.mimuw.edu.pl/~marpe/arch/akrd.pdf , strona 15. Niestety slajd nie wyjaśnia, dlaczego.
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:3 miesiące
1
Kopiuj
if((int)477.9999999999999999999==497) printf("A czego się spodziewałeś?\n");

Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
Zobacz pozostałe 5 komentarzy
_13th_Dragon
Ja widzę różne wyniki, może mam coś z oczami?
Sopelek
Chodzi mi o to, że takie same jak autor tematu (czyli różne). No wygląda to dziwnie, bo te działania powinny być tożsame.
_13th_Dragon
Jak na mnie to wygląda jak ma wyglądać. Ponieważ ix = (int) (a * x); kompilator może obliczyć w czasie kompilacji.
SO
@_13th_Dragon Zobacz na linka co podał Sopelek, wartości wczytywane przez scanf więc w czasie kompilacji kompilator tego nie obliczy.
_13th_Dragon
Rzeczywiście, to mam w takim razie jeszcze bardziej zagmatwany przykład, może kogoś to naprowadzi: http://ideone.com/3VWob1
vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
0

wystarczy sprawdzic jak dziala kojwersja z double na int (zdaje sie ze nie zaokragla).

_13th_Dragon
zawsze odrzuca część ułamkową - tak zapisano w standardzie.
vpiotr
Dokladnie
vpiotr
To nie ma nic wspolnego z c99. Rozwiniecie 478 napisales wyzej a potem zadzialal strip csesci ulamkowej - ot i cala tajemnica.
_13th_Dragon
Z c99 ma wspólnego jedynie "%f" vs "%lf"
KM
Chyba jednak ma trochę wspólnego z C99, bo: http://ideone.com/ShthYu Delikatna zmiana standardu, wychodzą odmienne wyniki
vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
0

@kmph: ten kod jest bledny z definicji. ix bedzie rozne od dx bo kolejnosc operacji jest rozna a nie bez znaczenia jest miejsce wykonania
operacji int(double). Mozna sobie to tlumaczyc innym kompilatorem, standardem czy ustawieniami kompilatora ale tak naprawde przyczyna jest jedna (ww).

KM
Nadal nie rozumiem. Oczywiście, ix będzie różne od dx, ale tak jakby co z tego? Kolejność operacji nie jest różna - ix to jest (int) (a * x), czyli najpierw wymnażamy a * x a wynik konwertujemy do typu int; natomiast idx to jest (int) dx , a dx to jest właśnie a * x; czyli, znowu - najpierw wymnażamy a * x , a wynik konwertujemy do typu int. Jedyna różnica jaką widzę jest taka, że w drugim przypadku wynik pośredni zapisujemy w międzyczasie do zmiennej, a w pierwszym - nie. Rozumiem więc, że ix powinno być równe idx, ale nie jest, i nie wiem, dlaczego?
vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
1

Po prostu zajrzyj do wersji asm tego kodu. Najprosciej zrob dwie funkcje ktore roznie implementuja to wyrazenie. Moze sie nie udac - to bedzie troche inna wersja kodu.

edytowany 1x, ostatnio: vpiotr
KM
Tak trzeba będzie zrobić... Z tym, że zajmie mi to troszkę więcej czasu, bo będę musiał nauczyć się rozumieć asm... Prędzej czy później postaram się tym zająć.
MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:10 minut
2

Po pierwsze zdaj sobie sprawę, że 0.478 nie ma dokładnej reprezentacji binarnej tak samo jak 1/3 nie ma dokładnej reprezentacji dziesiętnej.

Kolejnym problemem są zaokrąglenia i rożna w precyzji podczas wykonywania obliczeń i po zapisaniu w pamięci.
Gdy obliczania są robione w koprocesorze kompilator wykorzystuje najbardziej dokładny i szybki "typ" danych, który w przypadku x86 ma chyba wielkość 80 bitów.
Twój wynik w przypadku pierwszym miał mniej więcej taką postać:
111011100.111111111111111001100101001
co dało w części całkowitej daje 477

Kopiując wynik do komórki pamięci tracisz na precyzji (64 bity), wiec musi nastąpić zaokrąglenie.
Po skróceniu mantysy (zapisaniu w zmiennej double) musiało dość do zaokrąglenia w górę co po odcięciu części całkowitej dało już oczekiwany wynik.

Można to poprawić w taki sposób:http://ideone.com/N38jxx


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
Endrju
80 bitów? Koprocesor? Jest 21 wiek. Używa się co najmniej SSE2. (https://gcc.gnu.org/wiki/FloatingPointMath i dalej)
KM
To chyba wyjaśnia sprawę, dzięki. Przyznam że nie przypuszczałem, żeby skracając mantysę zaokrąglało "po ludzku", sądziłbym raczej że znowu brutalnie odcina, no ale to tym lepiej.
MarekR22
przeczytaj dokładnie, a zrozumiesz, że zawsze zaokrągla zawsze zaokrągla "po ludzku".

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.