web-dev-qa-db-ja.com

(inf + 0j)* 1がinf + nanjと評価されるのはなぜですか?

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

どうして?これは私のコードに厄介なバグを引き起こしました。

なぜ1乗法的アイデンティティ、(inf + 0j)

94
marnix

1は最初に複素数に変換され、1 + 0j、その後inf * 0乗算。結果は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

機構的には、受け入れられた答えはもちろん正しいですが、より深い回答が与えられると私は主張します。

最初に、コメントで@PeterCordesが行うように質問を明確にすることが役立ちます: "inf + 0jで機能する複素数の乗法的なアイデンティティはありますか?"または言い換えれば、OPが複雑な乗算のコンピューター実装で弱点を認識するものであるか、inf+0jで概念的に不健全なものがあります

短い答え:

極座標を使用すると、複雑な乗算をスケーリングおよび回転として表示できます。 1で乗算する場合のように、無限の「アーム」を0度でも回転させると、有限の精度で先端を配置することは期待できません。確かに、inf+0jには根本的に正しくないものがあります。つまり、無限に近づくと、有限のオフセットは無意味になります。

長い答え:

背景:この問題が展開する「大きなこと」は、数字の体系を拡張することです(実数または複素数を考えてください)。そうしたい理由の1つは、無限の概念を追加すること、または数学者である場合に「コンパクト化」することです。他の理由もあります( https://en.wikipedia.org/wiki/Galois_theoryhttps://en.wikipedia.org/wiki/Non-standard_analysis )、しかし、ここにあるものには興味がありません。

ワンポイントコンパクト化

もちろん、このような拡張に関する注意が必要なのは、これらの新しい数値を既存の算術に適合させることです。最も簡単な方法は、無限大に単一の要素を追加し( https://en.wikipedia.org/wiki/Alexandroff_extension )、ゼロで除算したゼロ以外のすべての要素に等しくすることです。これは実数( https://en.wikipedia.org/wiki/Projectively_extended_real_line )および複素数( https://en.wikipedia.org/wiki/Riemann_sphere )。

その他の拡張機能...

ワンポイントのコンパクト化は単純で数学的に健全ですが、複数の無限を含む「よりリッチな」拡張が求められてきました。実浮動小数点数のIEEE 754標準には、+ infと-infがあります( https://en.wikipedia.org/wiki/Extended_real_number_line )。自然でわかりやすいように見えますが、既にフープを飛び越えて-0のようなものを発明することを既に強いています https://en.wikipedia.org/wiki/Signed_zero

...複雑な平面の

複雑なプレーンの1つ以上のINF拡張はどうですか?

コンピュータでは、通常、2つのfp実数を1つを実数部に、もう1つを虚数部に貼り付けることにより、複素数が実装されます。すべてが有限である限り、それはまったく問題ありません。ただし、無限と見なされるとすぐに物事が難しくなります。

複素平面には自然な回転対称性があり、平面全体にe ^ phijを掛けることは0を中心としたファイラジアン回転と同じであるため、複素数演算とうまく結びつきます。

その別館Gのこと

さて、物事をシンプルに保つために、複雑なfpは基礎となる実数実装の拡張(+/- inf、nanなど)を使用するだけです。この選択は非常に自然に思えるかもしれませんが、選択として認識されることさえありませんが、それが意味するものを詳しく見てみましょう。複雑な平面のこの拡張の簡単な視覚化は、次のようになります(I =無限、f =有限、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

しかし、真の複素平面は複素乗算を尊重する平面なので、より有益な投影は

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

この予測では、ugいだけでなく、OPが受けた問題の根源である無限の「不均等な分布」を確認します。ほとんどの無限(形式(+/- inf、有限)および(有限、+/-inf)は4つの主な方向にまとめられ、他のすべての方向は4つの無限大(+/- inf、+-inf)で表されます。複雑な乗算をこのジオメトリに拡張するのは悪夢です。 。

C99仕様のAnnex Gは、infnanの相互作用(本質的にinfnanに勝る)のルールを曲げることを含め、それを機能させるために最善を尽くしています。 。 OPの問題は、実数と提案された純粋な虚数型を複素数に昇格させないことによって回避されますが、実数1が複素数1と異なる動作をすることは、解決策として私を悩ませません。明らかに、Annex Gは、2つの無限大の積がどうあるべきかを完全に特定するまでには至っていません。

もっと良くできますか?

無限のより良いジオメトリを選択することにより、これらの問題を試して修正しようとするのは魅力的です。拡張された実線と同様に、各方向に1つの無限大を追加できます。この構造は射影平面に似ていますが、反対方向をひとまとめにしません。無限は極座標inf x e ^ {2 omega pi i}で表され、積の定義は簡単です。特に、OPの問題は自然に解決されます。

しかし、これが良いニュースの終わりです。ある意味では、私たちの新しいスタイルの無限大は、実部または虚部を抽出する関数をサポートすることを必要とすることで、不当にではなく、1から2に戻すことができます。追加は別の問題です。対角でない2つの無限大を追加して、角度を未定義に設定する必要があります(つまり、nan(角度は2つの入力角度の間になければならないが、その「部分的なナンネス」を表す簡単な方法はありません)

リーマンは救助に

これらすべてを考慮すると、古き良きワンポイントコンパクト化が最も安全なことです。たぶん、Annex Gの著者は、すべての無限大をひとまとめにする関数cprojを義務付けるときに同じと感じました。


関連する質問 私よりも主題に関して有能な人々によって答えられています。

31
Paul Panzer

これは、CPythonで複雑な乗算を実装する方法の実装の詳細です。他の言語(CやC++など)とは異なり、CPythonはやや単純なアプローチを採用しています。

  1. int/floatは乗算で複素数に昇格されます
  2. 単純な school-formulaが使用されます 。無限の数が含まれるとすぐに望ましい/期待される結果を提供しません。
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;
}

上記のコードに関する問題の1つは次のとおりです。

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

ただし、-inf + inf*j結果として。

この点で他の言語はそれほど先を行っていません:複素数乗算は長い間C標準の一部ではなく、付録GとしてC99にのみ含まれていました。これは複素乗算の実行方法を説明します。上記の学校の公式! C++標準では、複雑な乗算の動作方法が指定されていないため、ほとんどのコンパイラ実装は、C99準拠(gcc、clang)または非準拠(MSVC)のC実装にフォールバックしています。

上記の「問題のある」例の場合、C99準拠の実装(学校の公式よりも 複雑な )は、期待される結果( liveを参照 )になります。

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

C99標準を使用しても、すべての入力に対して明確な結果が定義されるわけではなく、C99準拠バージョンでも異なる場合があります。

C_99でfloatに昇格されないcomplexのもう1つの副作用は、乗算inf+0.0j with 1.0または1.0+0.0jは異なる結果につながる可能性があります(ライブを参照):

  • (inf+0.0j)*1.0 = inf+0.0j
  • (inf+0.0j)*(1.0+0.0j) = inf-nanj、虚数部は-nanではなくnan(CPythonのように)はここでは役割を果たしません。なぜなら、すべてのquiet nanは同等であるためです( this を参照)。 (したがって、「-」として印刷されます。 this を参照してください)およびそうでないものもあります。

これは少なくとも直感に反します。


私の重要なポイントは、「単純な」複素数の乗算(または除算)について簡単なことはないことです。また、言語やコンパイラーを切り替えるときには、微妙なバグや相違に備えなければなりません。

6
ead