Matlab-precisie: eenvoudig aftrekken is niet nul

Ik bereken deze eenvoudige som op Matlab:

2*0.04-0.5*0.4^2 = -1.387778780781446e-017

maar het resultaat is niet nul. Wat kan ik doen?

1
toegevoegd de auteur Amro, de bron
ook een veel duidelijker voorbeeld: 0.3 - 0.1 * 3 die 5.5511e-017 geeft.
toegevoegd de auteur Amro, de bron
Lees op eps .
toegevoegd de auteur Mike DeSimone, de bron
5.498 + 0.001 ans = 5.499000000000001 het juiste antwoord zou 5.499 moeten zijn
toegevoegd de auteur James Do, de bron

5 antwoord

Aabaz en Jim Clay hebben goede uitleg over wat er aan de hand is.

It's often the case that, rather than exactly calculating the value of 2*0.04 - 0.5*0.4^2, what you really want is to check whether 2*0.04 and 0.5*0.4^2 differ by an amount that is small enough to be within the relevant numerical precision. If that's the case, than rather than checking whether 2*0.04 - 0.5*0.4^2 == 0, you can check whether abs(2*0.04 - 0.5*0.4^2) < thresh. Here thresh can either be some arbitrary smallish number, or an expression involving eps, which gives the precision of the numerical type you're working with.

BEWERK: Met dank aan Jim en Tal voor voorgestelde verbetering. Gewijzigd om de absolute waarde van het verschil te vergelijken met een drempel, in plaats van het verschil.

4
toegevoegd
Goed punt. De enige verandering die ik zou aanbrengen is dat je de absolute waarde van het verschil moet vergelijken met "dors".
toegevoegd de auteur Jim Clay, de bron

Matlab gebruikt drijvende-kommagetallen met dubbele precisie om echte getallen op te slaan. Dit zijn cijfers van de vorm m * 2 ^ e waarbij m een geheel getal is tussen 2 ^ 52 en 2 ^ 53 (de mantisse ) en e is de exponent. Laten we een getal een drijvende-kommawaarde noemen als dit van deze vorm is.

Alle getallen die in berekeningen worden gebruikt, moeten drijvende-kommawaarden zijn. Dit kan vaak precies worden gedaan, zoals met 2 en 0.5 in uw uitdrukking. Maar voor andere getallen, met name de meeste cijfers met cijfers achter de komma, is dit niet mogelijk en moet een schatting worden gebruikt. Wat er in dit geval gebeurt, is dat het getal wordt afgerond naar het dichtstbijzijnde drijvende-kommagetal.

So, whenever you write something like 0.04 in Matlab, you're really saying "Get me the floating-point number that is closest to 0.04. In your expression, there are 2 numbers that need to be approximated: 0.04 and 0.4.

Bovendien is het exacte resultaat van bewerkingen zoals optellen en vermenigvuldigen op drijvende-kommagetallen mogelijk geen drijvende-kommawaarde. Hoewel het altijd van de vorm m * 2 ^ e is, kan de mantisse te groot zijn. U krijgt dus een extra fout door de resultaten van bewerkingen af ​​te ronden.

Aan het einde van de dag is een eenvoudige expressie als de uwe ongeveer 2 ^ -52 keer zo groot als de operands, of ongeveer 10 ^ -17.

Samenvattend: de reden dat je expressie niet naar nul evalueert is tweevoudig:

  1. Sommige cijfers waarmee u begint, verschillen (benaderingen) van de exacte cijfers die u heeft opgegeven.
  2. De tussentijdse resultaten kunnen ook een benadering zijn van de exacte resultaten.
2
toegevoegd

Ik weet niet of dit van toepassing is op uw probleem, maar vaak is de eenvoudigste oplossing om uw gegevens te schalen.

Bijvoorbeeld:

a=0.04;
b=0.2;
a-0.2*b
ans=-6.9389e-018
c=a/min(abs([a b]));
d=b/min(abs([a b]));
c-0.2*d
ans=0

EDIT: of course I did not mean to give a universal solution to these kind of problems but it is still a good practice that can make you avoid a few problems in numerical computation (curve fitting, etc ...). See Jim Clay's answer for the reason why you are experiencing these problems.

1
toegevoegd
Is er een reden dat dit werkt, of is het gewoon ad-hoc code die toevallig het "juiste" ding doet in dit geval?
toegevoegd de auteur Oliver Charlesworth, de bron
Ik weet het eerlijk gezegd niet, maar het gaat zeker in de goede richting om dit soort problemen op te lossen.
toegevoegd de auteur Aabaz, de bron
Werkt dit altijd of slechts een deel van de tijd?
toegevoegd de auteur Jim Clay, de bron
Dit zal niet altijd werken zoals te zien is door f = @ (x) 1-x/x ^ 2 * x en vervolgens f (rand ()) een paar tijden. Soms is het 0; andere keren is het epsilon. Schalen helpt u betere resultaten te krijgen, maar de nauwkeurigheid is nog steeds binnen epsilon. Een ander alternatief is het schalen van epsilon om overeen te komen met de schaal van de andere waarden in het probleem.
toegevoegd de auteur stardt, de bron

Wat u ziet, is kwantisatiefout . Matlab gebruikt dubbelen om getallen voor te stellen, en hoewel ze in staat zijn tot veel precisie, kunnen ze nog steeds niet alle reële getallen voorstellen omdat er een oneindig aantal reële getallen zijn. Ik ben niet zeker van de truc van Aabaz, maar in het algemeen zou ik zeggen dat er niets is dat je kunt doen, behalve misschien dat je je invoer masseert met dubbele vriendelijke nummers.

1
toegevoegd

Ik ben er vrij zeker van dat dit een geval is van problemen met floating point nauwkeurigheid.

Hebt u de 1e-17 nauwkeurigheid nodig? Is dit slechts een kwestie van 'mooie' uitvoer willen? In dat geval kunt u gewoon een geformatteerde sprintf gebruiken om het aantal significante cijfers weer te geven dat u wilt.

Realiseer je dat dit geen matlab-probleem is, maar een fundamentele beperking van hoe aantallen worden weergegeven in binair getal.

Voor de lol, bereken wat .1 is in binair ...

Enkele referenties: http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems http://www.mathworks.com/support/tech-notes/1100/1108.html

1
toegevoegd