Wynik zależy od języka

0

Kod jest taki:

float a=12345678.0f;
float b=12345679.0f;
float c=1.01233995f;
float p=(a+b+c)/2;
float result=(p-b)*p;
result=((a+b+c)/2-b)*b;

W trzech językach otrzymałem różne wyniki.
'C++ 0.0 76162.5
Java 0.0 0.0
C# 76152.5 76162.5'
Dlaczego Java i C# dają różne wyniki, chociaż typ float jest w obu 32-bitowy i ma taki sam zakres?

0

To ja jeszcze dorzucę wyniki z Delphi i Free Pascala:
Delphi.png
FreePascal.png
Kod do Delphi:

{$APPTYPE CONSOLE}
Program Test;
Var A, B, C, P, Result: Extended;
Begin
 A      := 12345678;
 B      := 12345679;
 C      := 1.01233995;
 P      := (a+b+c)/2;
 Result := (p-b)*p;
 WriteLn(Result);
 Result := ((a+b+c)/2-b)*b;
 WriteLn(Result);
 ReadLn;
End.

Kod do Free Pascala:

Program Test;
Var A, B, C, P, Result: Extended;
Begin
 A      := 12345678;
 B      := 12345679;
 C      := 1.01233995;
 P      := (a+b+c)/2;
 Result := (p-b)*p;
 WriteLn(Result);
 Result := ((a+b+c)/2-b)*b;
 WriteLn(Result);
 ReadLn;
End.

Wyniki dla "Single" zamiast "Extended":
Delphi:
Delphi2.png
FreePascal2.png

0

uruchomione na ideone.com

Pascal (fpc) (fpc 2.2.0)     0.000000000E+00         7.617253125E+04
Pascal (gpc) (gpc 20070904)  0.000000000000000e+00   7.617253125000000e+04

C (gcc-4.3.4)                0.000000                0.000000
C++ (gcc-4.3.4)              0.000000                0.000000
C++0x (gcc-4.5.1)            0.000000                0.000000
C99 strict (gcc-4.3.4)       0.000000                76172.531250

zmienne typu float volatile

C (gcc-4.3.4)                0.000000                76172.531250
C++ (gcc-4.3.4)              0.000000                76172.531250
C++0x (gcc-4.5.1)            0.000000                76172.531250
C99 strict (gcc-4.3.4)       0.000000                76172.531250 
0

Wyniki wg.Wolfram Alpha:
(p-b)*p=(12345679-12345679)*12345679=0
((a+b+c)/2-b)*b=((12345678+12345679+1.01233995)/2-12345679)*12345679=76172.530788025
Mogłem się gdzieś pomylić, lecz chyba jest dobrze.
Ech, faktycznie był błąd z "p".
Poprawione.

0

Od razu widać, że się pomyliłeś i źle wyliczyłeś p.

0

Przy takich danych i użyciu typu float w tych obliczeniach nie dostaniesz dobrego wyniku. Odejmowanie dwóch bardzo podobnych wielkości znacznie zmniejsza Ci dokładność wyniku. Różnica jest na 10 cyfrze znaczącej, co jest za mało, żeby pomieścić w typie float. Czyli ogólny wynik tego wyrażenia ma zerową dokładność, czyli obydwa wyniki są słuszne, bo i tak nigdzie ich nie możesz użyć (bo obydwa są bezsensowne). Jeżeli wynik znacząco zależy od dokładności obliczeń to znaczy, że robisz coś źle.

0

PARI/GP

<code=pari/gp>gp > a=12345678.0 12345678.000000000000000000000000000000000000000000 gp > b=12345679.0 12345679.000000000000000000000000000000000000000000 gp > c=1.01233995 1.0123399500000000000000000000000000000000000000000 gp > p=(a+b+c)/2 12345679.006169975000000000000000000000000000000000 gp > (p-b)*p 76172.530826093591500624999999999999999999999999994 gp > ((a+b+c)/2-b)*b 76172.530788024999999999999999999999999999999999994 ^ | +---- dokładność 57 cyfr a już tu różnica ``` w trakcie obliczeń za naszymi plecami dzieją się dwie rzeczy - typy zmiennoprzecinkowe podciągane w górę - optymalizator (różnie wyniki uzyska się też przy różnym stopniu optymalizacji, jak pokazałem dodanie volatile też wpływa na wyniki Za najlepszy wynik dla float czyli single uważam 0.0 i 0.0
0

Wyniki dla Python'a 2.6.6:

a=12345678.0
b=12345679.0
c=1.01233995
p=(a+b+c)/2
print (p-b)*p #wypisze: 76172.5284054
print ((a+b+c)/2-b)*b #wypisze: 76172.5283673
bogdans napisał(a)

Dlaczego Java i C# dają różne wyniki, chociaż typ float jest w obu 32-bitowy i ma taki sam zakres?

próbowałeś zamiast 2 wpisywać 2.0? Niektóre języki są na to wrażliwe, python w obu przypadkach daje te same wyniki. No i w sumie Python nie ma float'ów, wszystkie liczby zmiennoprzecinkowe są typu double.

0

Próbowałem. Kodu z 2.0 ani w Javie, ani w C# nie można skompilować. Kod z 2.0f daje takie same wyniki jak kod z 2

0

Tak dla porównania z resztą w przypadku php wyniki są za to zbliżone to tych z pythona i delphi, pari/gp (na 64bit systemie).


//na piechotę
ini_set('precision', 64);
$a=(float)12345678.0;//(float), (double), (real) są aliasami w php
$b=(float)12345679.0;
$c=(float)1.01233995;
$p=($a+$b+$c)/2;
$result=($p-$b)*$p;
var_dump($result);
$result=(($a+$b+$c)/2.0-$b)*$b;
var_dump($result);
//za pomocą bcmath
$a='12345678.0';
$b='12345679.0';
$c='1.01233995';
$precision = 64;
$p=
bcdiv(
  bcadd(
    bcadd($a,$b,$precision)
  ,$c,$precision)
,'2',$precision);
$result=
bcmul(
  bcsub($p,$b,$precision)
,$p,$precision);
var_dump($result);
$result=
bcmul(
  bcsub(
    bcdiv(
      bcadd(
        bcadd($a,$b,$precision)
      ,$c,$precision)
    ,'2',$precision)
  ,$b,$precision)
,$b,$precision);
var_dump($result);

//float(76172.52840540916076861321926116943359375)
//float(76172.528367340564727783203125)
//string(70) "76172.5308260935915006250000000000000000000000000000000000000000000000"
//string(70) "76172.5307880250000000000000000000000000000000000000000000000000000000"

A tak w ogóle do liczb zmiennoprzecinkowych, gdzie potrzeba dużej dokładności lepiej użyć jakiejś biblioteki typu Arbitrary Precision Arithmetic do znalezienia np. na http://en.wikipedia.org/wiki/Bignum#Libraries ;)
I taki tip jeszcze, nigdy nie ufaj wynikom w float co do dokładności i nigdy ich bezpośrednio nie porównuj.
Niektóre wartości w zapisie dziesiętnym po prostu nie mają reprezentacji w zapisie o podst. 2. Przykładowo 0.1, 0.7 zawsze będzie obdarzony błędem np.
wynikiem floor(0.1+0.7)*10 będzie 7 ponieważ zaokrąglać będzie coś w rodzaju 7.9999999999999991118... ;)
(powyższe zaczerpnięte z http://pl2.php.net/manual/en/language.types.float.php , ale jest słuszne i dla innych języków, np. c/c++, python, itd.)

A tak jeszcze wracając bardziej do tematu, nie wiem na ile to jest prawdziwe(może ktoś się zna lepiej na C#), ale wg
http://www.homeandlearn.co.uk/csharp/csharp_s2p6.html
w C# skoro określiłeś wartości jako float to zaokrągli ci na 7 cyfrze liczbę (1.01233995 == 1.012340) ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0