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:4 minuty
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".
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)