Precision och precision i flyttalsberäkningar
Anteckning
Office 365 ProPlus byter namn till Microsoft 365-appar för företag. Mer information om den här ändringen finns i det här blogginlägget.
Ursprungligt KB-nummer: 125056
Sammanfattning
Det finns många situationer där precision, avrundning och precision i flyttalsberäkningar kan fungera för att generera resultat som är kvar hos programmeraren. De bör följa fyra allmänna regler:
I en beräkning som innefattar både enkel och dubbel precision blir resultatet vanligtvis inte mer exakt än enskild precision. Om dubbel precision krävs måste vissa termer i beräkningen, inklusive konstanter, anges med dubbel precision.
Anta aldrig att ett enkelt numeriskt värde representeras korrekt i datorn. De flesta flyttalsvärden kan inte exakt representeras som ett ändligt binärt värde. Är till exempel i binär form (den upprepas för alltid), så det kan inte representeras med fullständig exakthet på en dator med binär aritmetiska funktioner, som
.1omfattar alla.0001100110011...datorer.Anta aldrig att resultatet är korrekt det sista decimaltecknet. Det finns alltid små skillnader mellan svaret "sant" och vad som kan beräknas med den ändliga precisionen för en flyttalsbearbetningsenhet.
Jämför aldrig två flyttalsvärden för att se om de är lika eller inte lika. Det här är en följd av regel 3. Det kommer nästan alltid att finnas små skillnader mellan tal som "ska" vara lika. Kontrollera i stället alltid om talen är nästan lika. Med andra ord, kontrollera om skillnaden mellan dem är liten eller inte har någon betydelse.
Mer information
I allmänhet gäller reglerna som beskrivs ovan för alla språk, inklusive C, C++, och assembler. I exemplen nedan visas några av reglerna som använder FORTRAN PowerStation. Alla exempel har kompilerats med FORTRAN PowerStation 32 utan några alternativ, förutom det sista som skrivs i C.
Exempel 1
I det första exemplet visas två saker:
- FortRAN-konstanterna är enkel precision som standard (C-konstanter har dubbel precision som standard).
- Beräkningar som innehåller enskilda precisionstermer är inte mycket mer exakta än beräkningar där alla termer är enkel precision.
När y har initierats med 1,1 (en konstant med en precision) är y lika felaktig som en variabel med en precision.
x = 1.100000000000000 y = 1.100000023841858
Resultatet av att multiplicera ett värde med en enskild precision med ett exakt värde för dubbel precision är nästan lika dåligt som att multiplicera två värden med en precision. Båda beräkningarna har tusentals gånger så mycket fel som att multiplicera två värden med dubbel precision.
true = 1.320000000000000 (multiplying 2 double precision values)
y = 1.320000052452087 (multiplying a double and a single)
z = 1.320000081062318 (multiplying 2 single precision values)
Exempelkod
C Compile options: none
real*8 x,y,z
x = 1.1D0
y = 1.1
print *, 'x =',x, 'y =', y
y = 1.2 * x
z = 1.2 * 1.1
print *, x, y, z
end
Exempel 2
I exempel 2 används den kvadratiska ekvationen. It demonstrates that even double precision calculations are not perfect, and that the result of a calculation should be tested before it is depended on if small errors can have drastic results. Indata till kvadratrotfunktionen i exempel 2 är bara något negativ, men är fortfarande ogiltig. Om beräkningarna med dubbel precision inte hade små fel blir resultatet:
Root = -1.1500000000
I stället genereras följande fel:
körningsfel M6201: MATEMATIK
- rot: DOMÄN-fel
Exempelkod
C Compile options: none
real*8 a,b,c,x,y
a=1.0D0
b=2.3D0
c=1.322D0
x = b**2
y = 4*a*c
print *,x,y,x-y
print "(' Root =',F16.10)",(-b+dsqrt(x-y))/(2*a)
end
Exempel 3
Exempel 3 visar att på grund av optimeringar som inträffar även om optimering inte är aktiverad kan värden tillfälligt behålla en högre precision än förväntat och att det är okurt att testa två flyttalsvärden för likhet.
I det här exemplet är två värden både lika och inte lika. I den första OM-värdet står Z fortfarande på processorns stack och har samma precision som Y. Därför är X inte lika med Y och det första meddelandet skrivs ut. Vid tiden för det andra OM-meddelandet var Z tvungen att läsas in från minnet och hade därför samma precision och värde som X och det andra meddelandet skrivs också ut.
Exempelkod
C Compile options: none
real*8 y
y=27.1024D0
x=27.1024
z=y
if (x.ne.z) then
print *,'X does not equal Z'
end if
if (x.eq.z) then
print *,'X equals Z'
end if
end
Exempel 4
Den första delen av exempelkoden 4 beräknar minsta möjliga differens mellan två tal som ligger nära 1,0. Det gör du genom att lägga till en enkel bit i den binära motsvarigheten till 1,0.
x = 1.00000000000000000 (one bit more than 1.0)
y = 1.00000000000000000 (exactly 1.0)
x-y = .00000000000000022 (smallest possible difference)
Vissa versioner av FORTRAN avrundar talen när de visas så att det numeriska teckenprecisionen inte blir så uppenbart. Det är därför x och y ser likadana ut när de visas.
Den andra delen av exempelkoden 4 beräknar minsta möjliga differens mellan två tal som ligger nära 10,0. Det här gör den igen genom att en enkel bit läggs till i den binära motsvarigheten till 10,0. Observera att differensen mellan tal nära 10 är större än differensen mellan 1 och 1. Det här visar den generella principen att ju större absolutvärdet av ett tal, desto mindre exakt kan den lagras i ett givet antal bitar.
x = 10.00000000000000000 (one bit more than 10.0)
y = 10.00000000000000000 (exactly 10.0)
x-y = .00000000000000178
Den binära representationen av talen visas också för att visa att de skiljer sig med bara 1 bit.
x = 4024000000000001 Hex
y = 4024000000000000 Hex
Den sista delen av exempelkoden 4 visar att enkla icke-upprepade decimalvärden ofta kan representeras i binär form endast av ett återkommande bråktal. I det här fallet x=1,05, vilket kräver en upprepande faktor CCCCCCCC.... (Hex) i mantissa. I FORTRAN avrundas den sista siffran "C" uppåt till "D" för att bibehålla högsta möjliga precision:
x = 3FF0CCCCCCCCCCCD (Hex representation of 1.05D0)
Även efter avrundningen är resultatet inte perfekt. Det finns ett fel efter den minst signifikanta siffran, som vi kan se genom att ta bort den första siffran.
x-1 = .05000000000000004
Exempelkod
C Compile options: none
IMPLICIT real*8 (A-Z)
integer*4 i(2)
real*8 x,y
equivalence (i(1),x)
x=1.
y=x
i(1)=i(1)+1
print "(1x,'x =',F20.17,' y=',f20.17)", x,y
print "(1x,'x-y=',F20.17)", x-y
print *
x=10.
y=x
i(1)=i(1)+1
print "(1x,'x =',F20.17,' y=',f20.17)", x,y
print "(1x,'x-y=',F20.17)", x-y
print *
print "(1x,'x =',Z16,' Hex y=',Z16,' Hex')", x,y
print *
x=1.05D0
print "(1x,'x =',F20.17)", x
print "(1x,'x =',Z16,' Hex')", x
x=x-1
print "(1x,'x-1=',F20.17)", x
print *
end
Exempel 5
I C är flytande konstanter dubbla som standard. Använd "f" för att ange ett flytande värde, som i "89,95f".
/* Compile options needed: none
*/
#include <stdio.h>
void main()
{
float floatvar;
double doublevar;
/* Print double constant. */
printf("89.95 = %f\n", 89.95); // 89.95 = 89.950000
/* Printf float constant */
printf("89.95 = %f\n", 89.95F); // 89.95 = 89.949997
/*** Use double constant. ***/
floatvar = 89.95;
doublevar = 89.95;
printf("89.95 = %f\n", floatvar); // 89.95 = 89.949997
printf("89.95 = %lf\n", doublevar); // 89.95 = 89.950000
/*** Use float constant. ***/
floatvar = 89.95f;
doublevar = 89.95f;
printf("89.95 = %f\n", floatvar); // 89.95 = 89.949997
printf("89.95 = %lf\n", doublevar); // 89.95 = 89.949997
}