web-dev-qa-db-fra.com

Pourquoi (inf + 0j) * 1 est-il évalué comme inf + nanj?

>>> (float('inf')+0j)*1
(inf+nanj)

Pourquoi? Cela a provoqué un bogue désagréable dans mon code.

Pourquoi n'est-ce pas 1 l'identité multiplicative, donnant (inf + 0j)?

94
marnix

Le 1 est d'abord converti en un nombre complexe, 1 + 0j, ce qui conduit alors à un inf * 0 multiplication, résultant en nan.

(inf + 0j) * 1
(inf + 0j) * (1 + 0j)
inf * 1  + inf * 0j  + 0j * 1 + 0j * 0j
#          ^ this is where it comes from
inf  + nan j  + 0j - 0
inf  + nan j
93
Marat

Mécaniquement, la réponse acceptée est, bien sûr, correcte, mais je dirais qu'une réponse plus approfondie peut être donnée.

Tout d'abord, il est utile de clarifier la question comme le fait @PeterCordes dans un commentaire: "Existe-t-il une identité multiplicative pour les nombres complexes qui fonctionne sur inf + 0j?" ou en d'autres termes est ce que OP voit une faiblesse dans l'implémentation informatique de la multiplication complexe ou y a-t-il quelque chose de conceptuellement mauvais avec inf+0j

Réponse courte:

En utilisant les coordonnées polaires, nous pouvons voir la multiplication complexe comme une mise à l'échelle et une rotation. En faisant tourner un "bras" infini même de 0 degré comme dans le cas de la multiplication par un, nous ne pouvons pas nous attendre à placer sa pointe avec une précision finie. En effet, il y a quelque chose de fondamentalement incorrect avec inf+0j, À savoir que dès que nous sommes à l'infini, un décalage fini devient vide de sens.

Longue réponse:

Contexte: La "grande chose" autour de laquelle cette question tourne est la question de l'extension d'un système de nombres (pensez aux réels ou aux nombres complexes). Une raison pour laquelle on pourrait vouloir faire cela est d'ajouter un concept de l'infini, ou de "se compactifier" si l'on se trouve être un mathématicien. Il y a aussi d'autres raisons ( https://en.wikipedia.org/wiki/Galois_theory , https://en.wikipedia.org/wiki/Non-standard_analysis ), mais cela ne nous intéresse pas.

Compactification en un point

La partie délicate d'une telle extension est, bien sûr, que nous voulons que ces nouveaux nombres s'intègrent dans l'arithmétique existante. Le moyen le plus simple consiste à ajouter un seul élément à l'infini ( https://en.wikipedia.org/wiki/Alexandroff_extension ) et à le rendre égal à tout sauf zéro divisé par zéro. Cela fonctionne pour les réels ( https://en.wikipedia.org/wiki/Projectively_extended_real_line ) et les nombres complexes ( https://en.wikipedia.org/wiki/Riemann_sphere ).

Autres extensions ...

Alors que la compactification en un point est simple et mathématiquement valable, des extensions "plus riches" comprenant de multiples infinties ont été recherchées. La norme IEEE 754 pour les nombres réels à virgule flottante a + inf et -inf ( https://en.wikipedia.org/wiki/Extended_real_number_line ). Cela semble naturel et simple, mais nous oblige déjà à sauter à travers des cerceaux et à inventer des choses comme -0https://en.wikipedia.org/wiki/Signed_zero

... du plan complexe

Qu'en est-il des extensions plus d'un inf du plan complexe?

Dans les ordinateurs, les nombres complexes sont généralement implémentés en collant deux réels fp l'un pour le réel et l'autre pour la partie imaginaire. C'est parfaitement bien tant que tout est fini. Mais dès que l'on considère les infinis, les choses deviennent délicates.

Le plan complexe a une symétrie de rotation naturelle, qui s'accorde bien avec l'arithmétique complexe car la multiplication du plan entier par e ^ phij est la même chose qu'une rotation phi radian autour de 0.

Cette annexe G chose

Maintenant, pour garder les choses simples, fp complexe utilise simplement les extensions (+/- inf, nan etc.) de l'implémentation sous-jacente du nombre réel. Ce choix peut sembler si naturel qu'il n'est même pas perçu comme un choix, mais regardons de plus près ce qu'il implique. Une visualisation simple de cette extension du plan complexe ressemble à (I = infini, f = fini, 0 = 0)

I IIIIIIIII I

I fffffffff I
I fffffffff I
I fffffffff I
I fffffffff I
I ffff0ffff I
I fffffffff I
I fffffffff I
I fffffffff I
I fffffffff I

I IIIIIIIII I

Mais comme un vrai plan complexe est celui qui respecte la multiplication complexe, une projection plus informative serait

     III    
 I         I  
    fffff    
   fffffff   
  fffffffff  
I fffffffff I
I ffff0ffff I
I fffffffff I
  fffffffff  
   fffffff   
    fffff    
 I         I 
     III    

Dans cette projection, nous voyons la "distribution inégale" des infinis qui est non seulement laide mais aussi la racine des problèmes du type OP a souffert: La plupart des infinis (ceux des formes (+/- inf, finie) et (finie, +/-inf) sont regroupés dans les quatre directions principales toutes les autres directions sont représentées par seulement quatre infinis (+/- inf, + -inf) .Il ne faut pas s'étonner que l'extension de la multiplication complexe à cette géométrie soit un cauchemar .

L'annexe G de la spécification C99 fait de son mieux pour le faire fonctionner, notamment en modifiant les règles d'interaction entre inf et nan (essentiellement inf l'emporte nan) . Le problème d'OP est contourné en ne promouvant pas les réels et un type proposé purement imaginaire au complexe, mais avoir le réel 1 se comporte différemment du complexe 1 ne me semble pas être une solution. Il est révélateur que l'annexe G ne précise pas entièrement ce que devrait être le produit de deux infinis.

Pouvons-nous faire mieux?

Il est tentant d'essayer de résoudre ces problèmes en choisissant une meilleure géométrie des infinis. Par analogie avec la ligne réelle étendue, nous pourrions ajouter un infini pour chaque direction. Cette construction est similaire au plan projectif mais ne s'emboîte pas dans des directions opposées. Les infinités seraient représentées en coordonnées polaires inf x e ^ {2 oméga pi i}, la définition des produits serait simple. En particulier, le problème de l'OP serait résolu tout naturellement.

Mais c'est là que se termine la bonne nouvelle. D'une certaine manière, nous pouvons être renvoyés à la case départ en --- non déraisonnablement --- en exigeant que nos infinités newstyle prennent en charge des fonctions qui extraient leurs parties réelles ou imaginaires. L'ajout est un autre problème; en ajoutant deux infinis non antipodaux, il faudrait régler l'angle sur undefined, c'est-à-dire nan (on pourrait faire valoir que l'angle doit se situer entre les deux angles d'entrée mais il n'y a pas de moyen simple de représenter cette "nanité partielle")

Riemann à la rescousse

Compte tenu de tout cela, la bonne vieille compactification en un point est peut-être la chose la plus sûre à faire. Peut-être que les auteurs de l'annexe G ont ressenti la même chose lorsqu'ils ont mandaté une fonction cproj qui regroupe tous les infinis ensemble.


Voici ne question connexe répondu par des personnes plus compétentes sur le sujet que moi.

31
Paul Panzer

Il s'agit d'un détail d'implémentation de la façon dont la multiplication complexe est implémentée dans CPython. Contrairement à d'autres langages (par exemple C ou C++), CPython adopte une approche quelque peu simpliste:

  1. les ints/flottants sont promus en nombres complexes dans la multiplication
  2. le simple la formule scolaire est utilisée , qui ne fournit pas les résultats souhaités/attendus dès que des nombres infinis sont impliqués:
Py_complex
_Py_c_prod(Py_complex a, Py_complex b)
{
    Py_complex r;
    r.real = a.real*b.real - a.imag*b.imag;
    r.imag = a.real*b.imag + a.imag*b.real;
    return r;
}

Un cas problématique avec le code ci-dessus serait:

(0.0+1.0*j)*(inf+inf*j) = (0.0*inf-1*inf)+(0.0*inf+1.0*inf)j
                        =  nan + nan*j

Cependant, on aimerait avoir -inf + inf*j à la suite.

À cet égard, d'autres langages ne sont pas loin: la multiplication des nombres complexes n'a longtemps pas fait partie de la norme C, incluse uniquement dans C99 en annexe G, qui décrit comment une multiplication complexe doit être effectuée - et ce n'est pas aussi simple que la formule scolaire ci-dessus! La norme C++ ne spécifie pas comment la multiplication complexe devrait fonctionner, donc la plupart des implémentations du compilateur retombent dans l'implémentation C, qui peut être conforme à C99 (gcc, clang) ou non (MSVC).

Pour l'exemple "problématique" ci-dessus, les implémentations conformes C99 (qui sont plus compliquées que la formule scolaire) donneraient ( voir en direct ) le résultat attendu:

(0.0+1.0*j)*(inf+inf*j) = -inf + inf*j 

Même avec la norme C99, un résultat sans ambiguïté n'est pas défini pour toutes les entrées et il peut être différent même pour les versions conformes C99.

Un autre effet secondaire de la non-promotion de float en complex en C99 est que la multiplicationinf+0.0j avec 1.0 ou 1.0+0.0j peut conduire à des résultats différents (voir ici en direct):

  • (inf+0.0j)*1.0 = inf+0.0j
  • (inf+0.0j)*(1.0+0.0j) = inf-nanj, la partie imaginaire étant -nan et non nan (comme pour CPython) ne joue pas de rôle ici, car tous les nans silencieux sont équivalents (voir this ), même certains d'entre eux ont un bit de signe défini (et donc imprimé comme "-", voir this ) et certains non.

Ce qui est au moins contre-intuitif.


Ma clé à retenir est: il n'y a rien de simple à propos de la multiplication (ou division) de nombres "simple" complexe et lors du basculement entre les langues ou même les compilateurs, il faut se préparer à de subtils bugs/différences.

6
ead

Définition drôle de Python. Si nous résolvons cela avec un stylo et du papier, je dirais que le résultat attendu serait expected: (inf + 0j) comme vous l'avez souligné parce que nous savons que nous voulons dire la norme de 1 Donc (float('inf')+0j)*1 =should= ('inf'+0j):

Mais ce n'est pas le cas comme vous pouvez le voir ... lorsque nous l'exécutons, nous obtenons:

>>> Complex( float('inf') , 0j ) * 1
result: (inf + nanj)

Python comprend ce *1 Comme un nombre complexe et non comme la norme de 1 Donc il interprète comme *(1+0j) et l'erreur apparaît lorsque nous essayons de faire inf * 0j = nanj car inf*0 ne peut pas être résolu.

Ce que vous voulez réellement faire (en supposant que 1 est la norme de 1):

Rappelons que si z = x + iy Est un nombre complexe avec la partie réelle x et la partie imaginaire y, le conjugué complexe de z est défini comme z* = x − iy, Et la valeur absolue, également appelée norm of z Est défini comme:

enter image description here

En supposant que 1 Est la norme de 1, Nous devrions faire quelque chose comme:

>>> c_num = complex(float('inf'),0)
>>> value = 1
>>> realPart=(c_num.real)*value
>>> imagPart=(c_num.imag)*value
>>> complex(realPart,imagPart)
result: (inf+0j)

pas très intuitif je sais ... mais parfois les langages de codage sont définis différemment de ce que nous utilisons au quotidien.

3
costargc