gcc přesnost bug?

hlasů
8

Mohu jen předpokládat, že se jedná o chybu. První tvrdit projde, zatímco druhý se nezdaří:

double sum_1 =  4.0 + 6.3;
assert(sum_1 == 4.0 + 6.3);

double t1 = 4.0, t2 = 6.3;

double sum_2 =  t1 + t2;
assert(sum_2 == t1 + t2);

Pokud tomu tak není chyba, proč?

Položena 27/08/2009 v 00:03
zdroj uživatelem
V jiných jazycích...                            


7 odpovědí

hlasů
13

Jste porovnávání čísel s plovoucí desetinnou čárkou. Nedělejte to, čísel s plovoucí desetinnou čárkou mají vlastní přesnosti chyby v některých případech. Místo toho, aby se absolutní hodnota rozdílu dvou hodnot, a tvrdí, že je tato hodnota nižší než nějaký malý počet (epsilon).

void CompareFloats( double d1, double d2, double epsilon )
{
    assert( abs( d1 - d2 ) < epsilon );
} 

To nemá nic společného s kompilátorem a vše, co souvisí s tím, jak čísel s plovoucí desetinnou čárkou jsou prováděny. Zde je IEEE spec:

http://www.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF

Odpovězeno 27/08/2009 v 00:06
zdroj uživatelem

hlasů
12

To je něco, co mě pokousal, taky.

Ano, čísla s plovoucí desetinnou čárkou by nikdy neměla být porovnány pro rovnost, protože zaokrouhlení chyby a pravděpodobně věděl, že.

Ale v tomto případě, že jste na počítači t1+t2a poté jej znovu computing. Jistě , že má vyrábět shodné výsledky?

Tady je to, co to asi děje. Vsadím se, že vedete to na x86 CPU, je to tak? X86 FPU používá 80 bitů na svých vnitřních registrů, ale hodnoty v paměti jsou uloženy jako 64bitové zdvojnásobí.

Takže t1+t2se nejprve počítal s 80 bitů přesnost, pak - předpokládám - uloženo ven paměti sum_2s 64 bity přesností - a že dojde k zaokrouhlení. Pro assert, to je vložen zpět do plovoucí bod registru, a t1+t2opět výpočet, opět s 80 bity přesností. Takže teď jste porovnávání sum_2, který byl předtím zaokrouhlí na plovoucí bodovou hodnotou 64-bit, s t1+t2, který byl vypočítán s vyšší přesností (80 bitů) - a to je důvod, proč tyto hodnoty nejsou úplně identické.

Úpravě Tak proč první test projít? V tomto případě kompilátor pravděpodobně vyhodnocuje 4.0+6.3v době kompilace a uloží ji jako 64-bit množství - a to jak pro přiřazení a pro assert. Takže identické hodnoty jsou porovnány a assert projde.

Druhý Edit Zde je sestava kód vygenerovaný pro druhou část kódu (GCC, x86), s poznámkami - do značné míry navazuje na scénář popsaný výše:

// t1 = 4.0
fldl    LC3
fstpl   -16(%ebp)

// t2 = 6.3
fldl    LC4
fstpl   -24(%ebp)

// sum_2 =  t1+t2
fldl    -16(%ebp)
faddl   -24(%ebp)
fstpl   -32(%ebp)

// Compute t1+t2 again
fldl    -16(%ebp)
faddl   -24(%ebp)

// Load sum_2 from memory and compare
fldl    -32(%ebp)
fxch    %st(1)
fucompp

Zajímavá poznámka po straně: To byl sestaven bez optimalizace. Když je sestaven s -O3kompilátor optimalizuje všechny kódu pryč.

Odpovězeno 27/08/2009 v 00:16
zdroj uživatelem

hlasů
3

Při porovnání s plovoucí desetinnou čárkou za blízkost obvykle chtějí změřit své relativní rozdíl, který je definován jako

if (abs(x) != 0 || abs(y) != 0) 
   rel_diff (x, y) = abs((x - y) / max(abs(x),abs(y))
else
   rel_diff(x,y) = max(abs(x),abs(y))

Například,

rel_diff(1.12345, 1.12367)   = 0.000195787019
rel_diff(112345.0, 112367.0) = 0.000195787019
rel_diff(112345E100, 112367E100) = 0.000195787019

Cílem je, aby měření počtu vedoucích platných číslic čísla mají společné; pokud budete mít -log10 z 0.000195787019 dostanete 3.70821611, což je o počtu vedoucího základny 10 číslic všechny příklady mají společné.

Potřebujete-li zjistit, zda dvě desetinná čísla jsou stejné byste měli udělat něco podobného

if (rel_diff(x,y) < error_factor * machine_epsilon()) then
  print "equal\n";

kde stroj epsilon je nejmenší číslo, které může být držen v mantise plovoucí bod hardwaru používán. Většina počítačových jazyky mají volání funkce získat tuto hodnotu. error_factor by měly být založeny na počtu platných číslic si myslíte, bude spotřebována zaokrouhlení chyby (a další) při výpočtech z čísel x a y. Například, když jsem věděl, že x a y byly výsledkem asi 1000 součtů a nevěděl nějaké meze o počtech jsou sečteny, tak bych nastavit error_factor až 100.

Se snažil přidat tyto jako odkazy, ale nešlo to, protože to je můj první příspěvek:

  • en.wikipedia.org/wiki/Relative_difference
  • en.wikipedia.org/wiki/Machine_epsilon
  • en.wikipedia.org/wiki/Significand (mantisa)
  • en.wikipedia.org/wiki/Rounding_error
Odpovězeno 27/08/2009 v 03:09
zdroj uživatelem

hlasů
3

Jsem duplikovány váš problém na mé Intel Core 2 Duo, a já jsem se podíval na assembleru. Tady je to, co se děje: když váš kompilátor vyhodnotí t1 + t2, to dělá

load t1 into an 80-bit register
load t2 into an 80-bit register
compute the 80-bit sum

Když se ukládá do sum_2ano

round the 80-bit sum to a 64-bit number and store it

Potom ==porovnání porovnává 80-bitový součet na 64bitové částky, a oni jsou odlišné, a to především proto, že nepatrná část 0.3 nemůže být reprezentován přesně pomocí binární číslo s plovoucí desetinnou čárkou, takže jste porovnáním ‚opakující desetinné‘ ( vlastně opakování binární), která byla zkrácena na dvou různých délkách.

Co je opravdu dráždí je, že pokud kompilátoru s gcc -O1nebo gcc -O2, gcc dělá špatnou aritmetiku v době kompilace, a problém zmizí. Možná, že to je v pořádku, v souladu s normou, ale je to jen jeden z důvodů, že gcc není můj oblíbený kompilátor.


PS Když říkám, že ==porovnává 80-bitového součtu s 64-bitovým Stručně řečeno, samozřejmě jsem vlastně znamená, že porovnává rozšířenou verzi 64bitové částky. Dobře si mohl udělat přemýšlet

sum_2 == t1 + t2

řeší

extend(sum_2) == extend(t1) + extend(t2)

a

sum_2 = t1 + t2

řeší

sum_2 = round(extend(t1) + extend(t2))

Vítejte v báječném světě plovoucí desetinnou čárkou!

Odpovězeno 27/08/2009 v 00:46
zdroj uživatelem

hlasů
2

Může se stát, že v jednom z případů, můžete skončit porovnáním 64-bit double k vnitřnímu registru 80-bit. Může být poučné podívat se na návodu k montáži GCC vyzařuje pro dva případy ...

Odpovězeno 27/08/2009 v 00:12
zdroj uživatelem

hlasů
1

Srovnání počtu double precision jsou ze své podstaty nepřesné. Například, můžete často najít 0.0 == 0.0vrací false . Toto je kvůli způsobu, jakým ukládá FPU a sleduje čísla.

Wikipedia říká :

Testování na rovnost je problematické. Dvě výpočetní sekvence, které jsou matematicky roven může také produkovat různé hodnoty s plovoucí desetinnou čárkou.

Budete muset používat deltu dát tolerance pro srovnávání, spíše než přesné hodnoty.

Odpovězeno 27/08/2009 v 00:08
zdroj uživatelem

hlasů
0

Tento „problém“ může být „pevná“ pomocí těchto možností:

-msse2 -mfpmath = sse

jak je uvedeno na této stránce:

http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html

Jednou jsem použil tyto možnosti, jak tvrdí prošel.

Odpovězeno 27/08/2009 v 22:52
zdroj uživatelem

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more