2012-04-29 12 views
7

miałem problem kiedy dodając trzy zmiennoprzecinkowych i porównując je do 1.Czy jest dodawana funkcja Floating Point i funkcja mnożenia?

cout << ((0.7 + 0.2 + 0.1)==1)<<endl;  //output is 0 
cout << ((0.7 + 0.1 + 0.2)==1)<<endl;  //output is 1 

Dlaczego te wartości wyjdzie inaczej?

+0

Twój przykładowy kod różni się * * sprawnością *, a nie * asocjatywnością *. Wersja wykazująca asocjację będzie miała postać '(0.7 + (0.1 + 0.2))' –

+0

@MattMcNabb: + jest operacją binarną. Z operandami zmiennoprzecinkowymi jest to przemienne, ale nie asocjacyjne. Tak więc, jeśli masz dwa wyrażenia, które dają różne wyniki, nie możesz utworzyć jednego z drugiego, stosując tylko przemienność. – tmyklebu

+0

@tmyklebu OK, więc to sprawdza powiązanie wtedy i tylko wtedy, gdy wiadomo, że przechowuje się komutatywność. (Standard C++ nie wydaje się gwarantować przemienności). –

Odpowiedz

10

Dodawanie zmiennoprzecinkowe niekoniecznie jest asocjacyjne. Jeśli zmienisz kolejność dodawania elementów, może to zmienić wynik.

Standardowym papierem na ten temat jest What Every Computer Scientist Should Know about Floating Point Arithmetic. Podaje następujący przykład:

Inny szary obszar dotyczy interpretacji nawiasów. Z powodu błędów zaokrąglania, asocjacyjne prawa algebry niekoniecznie zawierają liczby zmiennoprzecinkowe. Na przykład wyrażenie (x + y) + z ma zupełnie inną odpowiedź niż x + (y + z), gdy x = 1e30, y = -1e30 i z = 1 (to jest 1 w pierwszym przypadku, 0 w drugim).

5

co może z aktualnie popularnych urządzeń i oprogramowania, jest

Kompilator kodowane jako 0x1.6666666666666p 0,7-1 (to jest w systemie szesnastkowym liczbowy 1,6666666666666 mnoży się przez 2 do potęgi -1) .2 jako 0x1.999999999999ap-3 i .1 jako 0x1.999999999999ap-4. Każda z nich jest liczbą reprezentowaną przez zmiennoprzecinkę, która jest najbliższa liczbie dziesiętnej, którą napisałeś.

Należy zauważyć, że każda z tych stałych szesnastkowych stała zmiennoprzecinkowych ma dokładnie 53 bity w swym znaczeniu (część "ułamek", często niedokładnie nazywana mantysą). Szesnastkowa liczba znaczników ma cyfrę "1" i trzynaście więcej cyfr szesnastkowych (cztery bity, 52, 53 włącznie z "1"), co zapewnia standard IEEE-754, dla 64-bitowego binarnego liczby punktowe.

Dodajmy liczby dla .7 i .2: 0x1.6666666666666p-1 i 0x1.999999999999ap-3. Najpierw przeskaluj wykładnik drugiej liczby, aby pasował do pierwszego. Aby to zrobić, pomnożymy wykładnik przez 4 (zmieniając "p-3" na "p-1") i pomnożymy znaczenie przez 1/4, podając 0x0.66666666666668p-1. Następnie dodaj 0x1.6666666666666p-1 i 0x0.66666666666668p-1, podając 0x1.ccccccccccccc8p-1. Zauważ, że liczba ta ma więcej niż 53 bity w znaczeniu: "8" to 14 cyfra po kropce. Zmienna zmienna nie może zwracać wyniku z tak wieloma bitami, więc musi być zaokrąglona do najbliższej reprezentowalnej liczby. W tym przypadku są dwie liczby, które są równie bliskie, 0x1.ccccccccccccp-1 i 0x1.ccccccccccccdp-1. W przypadku remisu stosuje się liczbę z zerem w najniższym bitwie znacznika. "c" jest parzyste, a "d" jest nieparzyste, więc użyte jest "c". Ostatecznym wynikiem dodania jest 0x1.cccccccccccccp-1.

Następnie dodaj numer dla .1 (0x1.999999999999ap-4). Znowu skalujemy, aby wykładniki pasowały do ​​siebie, więc 0x1.999999999999ap-4 staje się 0x.33333333333334p-1. Następnie dodaj to do 0x1.cccccccccccccp-1, podając 0x1.fffffffffffff4p-1. Zaokrąglenie tego do 53 bitów daje 0x1.fffffffffffffp-1, i jest to końcowy wynik ".7 + .2 + .1".

Teraz rozważ ".7 + .1 + .2". Dla ".7 + .1" dodaj 0x1.6666666666666p-1 i 0x1.999999999999ap-4. Przypomnijmy, że ta ostatnia jest skalowana do 0x.3333333333333p-1. Wtedy dokładna suma to 0x1.99999999999994p-1. Zaokrąglenie tego do 53 bitów daje 0x1.9999999999999p-1.

Następnie dodaj numer dla .2 (0x1.999999999999ap-3), który jest skalowany do 0x0.66666666666668p-1. Dokładna suma to 0x2.00000000000008p-1. Symbole zmiennoprzecinkowe są zawsze skalowane, tak aby zaczynały się od 1 (z wyjątkiem szczególnych przypadków: zero, nieskończoność i bardzo małe liczby na dole reprezentowalnego zakresu), więc dostosowujemy to do 0x1.00000000000004p0.Na koniec zaokrąglamy do 53 bitów, podając 0x1.0000000000000p0.

Z powodu błędów występujących podczas zaokrąglania, ".7 + .2 + .1" zwraca 0x1.fffffffffffffp-1 (bardzo nieznacznie mniej niż 1), a ".7 + .1 + .2" zwraca 0x1.0000000000000p0 (dokładnie 1).

1

Mnożenie zmiennoprzecinkowe nie jest asocjacyjne w C ani C++.

Dowód:

#include<stdio.h> 
#include<time.h> 
#include<stdlib.h> 
using namespace std; 
int main() { 
    int counter = 0; 
    srand(time(NULL)); 
    while(counter++ < 10){ 
     float a = rand()/100000; 
     float b = rand()/100000; 
     float c = rand()/100000; 

     if (a*(b*c) != (a*b)*c){ 
      printf("Not equal\n"); 
     } 
    } 
    printf("DONE"); 
    return 0; 
} 

W tym programie, około 30% czasu, (a*b)*c nie jest równa a*(b*c).

+3

lub 0% czasu, jeśli 'RAND_MAX <100000'! –

Powiązane problemy