web-dev-qa-db-fra.com

Fonction permettant de déterminer si deux nombres sont presque égaux lorsqu'ils sont arrondis à n chiffres décimaux significatifs

On m'a demandé de tester une bibliothèque fournie par un tiers. La bibliothèque est connue pour être précise à n chiffres significatifs. Toutes les erreurs moins importantes peuvent être ignorées en toute sécurité. Je veux écrire une fonction pour m'aider à comparer les résultats:

def nearlyequal( a, b, sigfig=5 ):

Le but de cette fonction est de déterminer si deux nombres à virgule flottante (a et b) sont approximativement égaux. La fonction retournera True si a == b (correspondance exacte) ou si a et b ont la même valeur lorsqu'ils sont arrondis à sigfig chiffres significatifs lorsqu'ils sont écrits en décimal.

Quelqu'un peut-il suggérer une bonne mise en œuvre? J'ai écrit un mini-test unitaire. À moins que vous ne puissiez voir un bogue dans mes tests, une bonne implémentation devrait réussir ce qui suit:

assert nearlyequal(1, 1, 5) 
assert nearlyequal(1.0, 1.0, 5) 
assert nearlyequal(1.0, 1.0, 5) 
assert nearlyequal(-1e-9, 1e-9, 5) 
assert nearlyequal(1e9, 1e9 + 1 , 5) 
assert not nearlyequal( 1e4, 1e4 + 1, 5) 
assert nearlyequal( 0.0, 1e-15, 5 ) 
assert not nearlyequal( 0.0, 1e-4, 6 ) 

Notes complémentaires:

  1. Les valeurs a et b peuvent être de type int, float ou numpy.float64. Les valeurs a et b seront toujours du même type. Il est essentiel que la conversion n'introduise pas d'erreur supplémentaire dans la fonction.
  2. Permet de garder ce chiffre, donc les fonctions qui se convertissent en chaînes ou utilisent des astuces non mathématiques ne sont pas idéales. Ce programme sera audité par quelqu'un qui est un mathématicien qui voudra être en mesure de prouver que la fonction fait ce qu'elle est censée faire.
  3. Vitesse ... Je dois comparer beaucoup de chiffres, donc le plus vite sera le mieux.
  4. J'ai numpy, scipy et la bibliothèque standard. Il me sera difficile d'obtenir autre chose, surtout pour une si petite partie du projet.
41
Salim Fadhley

Il y a une fonction assert_approx_equal dans numpy.testing (source ici) qui peut être un bon point de départ.

def assert_approx_equal(actual,desired,significant=7,err_msg='',verbose=True):
    """
    Raise an assertion if two items are not equal up to significant digits.

    .. note:: It is recommended to use one of `assert_allclose`,
              `assert_array_almost_equal_nulp` or `assert_array_max_ulp`
              instead of this function for more consistent floating point
              comparisons.

    Given two numbers, check that they are approximately equal.
    Approximately equal is defined as the number of significant digits
    that agree.
23
dF.

À partir de Python 3.5, la façon standard de le faire (en utilisant la bibliothèque standard) est avec le math.isclose fonction.

Il a la signature suivante:

isclose(a, b, rel_tol=1e-9, abs_tol=0.0)

Un exemple d'utilisation avec une tolérance d'erreur absolue:

from math import isclose
a = 1.0
b = 1.00000001
assert isclose(a, b, abs_tol=1e-8)

Si vous le souhaitez avec une précision de n chiffres significatifs, remplacez simplement la dernière ligne par:

assert isclose(a, b, abs_tol=10**-n)
74
erb

Voici une prise.

def nearly_equal(a,b,sig_fig=5):
    return ( a==b or 
             int(a*10**sig_fig) == int(b*10**sig_fig)
           )
7
Triptych

Je crois que votre question n'est pas assez bien définie, et les tests unitaires que vous présentez le prouvent:

Si par "arrondir à N décimales sig-fig" vous voulez dire "N décimales à droite de la virgule décimale", alors le test assert nearlyequal(1e9, 1e9 + 1 , 5) devrait échouer, car même lorsque vous arrondissez 1000000000 et 1000000001 à 0,00001 précision, ils sont toujours différents.

Et si par "arrondir à N décimales sig-fig" vous voulez dire "Les N chiffres les plus significatifs, quel que soit le point décimal", alors le test assert nearlyequal(-1e-9, 1e-9, 5) devrait échouer, car 0.000000001 et -0.000000001 sont totalement différents vu de cette façon.

Si vous vouliez dire la première définition, alors la première réponse sur cette page (par Triptyque) est bonne. Si vous vouliez la deuxième définition, dites-la, je vous promets d'y penser :-)

6
Oren Shemesh

Il y a déjà beaucoup de bonnes réponses, mais voici une réflexion:

def closeness(a, b):
  """Returns measure of equality (for two floats), in unit
     of decimal significant figures."""
  if a == b:
    return float("infinity")
  difference = abs(a - b)
  avg = (a + b)/2
  return math.log10( avg / difference )


if closeness(1000, 1000.1) > 3:
  print "Joy!"
3

Les "chiffres significatifs" en décimal consistent à ajuster le point décimal et à le tronquer en un entier.

>>> int(3.1415926 * 10**3)
3141
>>> int(1234567 * 10**-3)
1234
>>>
2
S.Lott

Il s'agit d'un problème assez courant avec les nombres à virgule flottante. Je le résous sur la base de la discussion dans la section 1.5 de Demmel [1]. (1) Calculez l'erreur d'arrondi. (2) Vérifiez que l'erreur d'arrondi est inférieure à certains epsilon. Je n'ai pas utilisé python depuis un certain temps et n'ai que la version 2.4.3, mais j'essaierai de faire les choses correctement.

Étape 1. Erreur d'arrondi

def roundoff_error(exact, approximate):
    return abs(approximate/exact - 1.0)

Étape 2. Égalité en virgule flottante

def float_equal(float1, float2, epsilon=2.0e-9):
    return (roundoff_error(float1, float2) < epsilon)

Il y a quelques lacunes évidentes avec ce code.

  1. Division par erreur zéro si la valeur exacte est zéro.
  2. Ne vérifie pas que les arguments sont des valeurs à virgule flottante.

Révision 1.

def roundoff_error(exact, approximate):
    if (exact == 0.0 or approximate == 0.0):
        return abs(exact + approximate)
    else:
        return abs(approximate/exact - 1.0)

def float_equal(float1, float2, epsilon=2.0e-9):
    if not isinstance(float1,float):
        raise TypeError,"First argument is not a float."
    Elif not isinstance(float2,float):
        raise TypeError,"Second argument is not a float."
    else:
        return (roundoff_error(float1, float2) < epsilon)

C'est un peu mieux. Si la valeur exacte ou approximative est nulle, alors l'erreur est égale à la valeur de l'autre. Si quelque chose en plus d'une valeur à virgule flottante est fourni, une TypeError est déclenchée.

À ce stade, la seule chose difficile est de définir la valeur correcte pour epsilon. J'ai remarqué dans la documentation de la version 2.6.1 qu'il y a un attribut epsilon dans sys.float_info, donc j'utiliserais deux fois cette valeur comme epsilon par défaut. Mais la valeur correcte dépend à la fois de votre application et de votre algorithme.

[1] James W. Demmel, Algèbre linéaire numérique appliquée, SIAM, 1997.

2
tmh

Oren Shemesh a eu une partie du problème avec le problème comme indiqué, mais il y a plus:

affirmer presque égal (0,0, 1e-15, 5)

échoue également la deuxième définition (et c'est la définition que j'ai apprise à l'école.)

Quel que soit le nombre de chiffres que vous regardez, 0 ne sera pas égal à zéro. Cela pourrait s'avérer un casse-tête pour de tels tests si vous avez un cas dont la bonne réponse est zéro.

1
Loren Pechtel

Il y a une solution intéressante à cela par B. Dawson (avec le code C++) à "Comparing Floating Point Numbers" . Son approche repose sur une représentation IEEE stricte de deux nombres et sur l'ordre lexicographique imposé lorsque lesdits nombres sont représentés sous forme d'entiers non signés.

1
Dan M.

On m'a demandé de tester une bibliothèque fournie par un tiers

Si vous utilisez la valeur par défaut Python unittest framework , vous pouvez utiliser assertAlmostEqual

self.assertAlmostEqual(a, b, places=5)
0
serv-inc

Il existe de nombreuses façons de comparer deux nombres pour voir s'ils conviennent à N chiffres significatifs. En gros, vous voulez simplement vous assurer que leur différence est inférieure à 10 ^ -N fois le plus grand des deux nombres comparés. C'est assez simple.

Mais que se passe-t-il si l'un des nombres est zéro? Tout le concept de différences relatives ou de chiffres significatifs tombe en panne par rapport à zéro. Pour gérer ce cas, vous devez également avoir une différence absolue, qui doit être spécifiée différemment de la différence relative.

Je discute des problèmes de comparaison des nombres à virgule flottante - y compris un cas spécifique de gestion du zéro - dans cet article de blog:

http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

0
Bruce Dawson