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


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

どうして?これにより、コードに厄介なバグが発生しました。

なぜ1乗法的アイデンティティは与え(inf + 0j)ないのですか?


1
あなたが探しているキーワードは「フィールド」だと思います。加算と乗算はデフォルトで単一のフィールド内で定義されます。この場合、コードに対応できる唯一の標準フィールドは複素数のフィールドです。そのため、演算が適切になる前に、デフォルトで両方の数値を複素数として扱う必要があります。定義された。つまり、これらの定義を拡張できなかったというわけはありませんが、明らかに標準的なものをそのまま使用しており、定義を拡張するために自分の道を離れる気になりませんでした。
user541686

1
ああ、そしてこれらの特異性がイライラしていてコンピューターをパンチしたいのなら、私の同情があります。
user541686

2
@Mehrdadは、これらの非有限要素を追加すると、フィールドではなくなります。確かに、乗法中立はもう存在しないので、定義上、フィールドになることはできません。
ポールパンツァー

@PaulPanzer:ええ、彼らは後でそれらの要素を押し込んだと思います。
user541686

1
浮動小数点数(無限大とNaNを除外した場合でも)はフィールドではありません。フィールドを保持するIDのほとんどは、浮動小数点数を保持しません。
プラグウォッシュ

回答:


95

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

8
「なぜ...?」という質問に答えるために、おそらく最も重要なステップは、1がにキャストされる最初のステップです1 + 0j
Warren Weckesser

5
C99は、複素数型(ドラフト標準のセクション6.3.1.8)を乗算するときに、実際の浮動小数点型が複素数に昇格されないことを指定していること、および私が知る限り、C ++のstd :: complexについても同様です。これは、パフォーマンス上の理由の一部である可能性がありますが、不要なNaNも回避されます。
benrg

@benrg NumPyでは、array([inf+0j])*1も評価されarray([inf+nanj])ます。実際の乗算がC / C ++コードのどこかで発生するとすると、これは、_Complexやstd :: complexを使用するのではなく、CPythonの動作をエミュレートするカスタムコードを作成したことを意味しますか?
marnix

1
@marnixはそれよりも複雑です。numpyにはufunc、ほぼすべての演算子と関数が派生する1つの中心的なクラスがあります。ufuncブロードキャストの管理は、アレイの操作を非常に便利にするトリッキーな管理をすべて処理します。より正確には、特定の演算子と一般的な機械の間の労働の分割は、特定の演算子が、処理したい入力要素と出力要素のタイプの組み合わせごとに、「最も内側のループ」のセットを実装することです。一般的な機械は、外側のループを処理し、最も内側のループに最も一致するものを選択します...
Paul Panzer

1
...必要に応じて、完全に一致しないタイプをプロモートします。このyields のtypes属性を介して、提供された内部ループのリストにアクセスできます。特に、float型とcomplex 型を混合するタイプがほとんどないことがわかります。np.multiply['??->?', 'bb->b', 'BB->B', 'hh->h', 'HH->H', 'ii->i', 'II->I', 'll->l', 'LL->L', 'qq->q', 'QQ->Q', 'ee->e', 'ff->f', 'dd->d', 'gg->g', 'FF->F', 'DD->D', 'GG->G', 'mq->m', 'qm->m', 'md->m', 'dm->m', 'OO->O']"efdg""FDG"
Paul Panzer

32

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

まず、@ 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)。自然で簡単なように見えますが、すでにフープを飛び越えてhttps://en.wikipedia.org/wiki/Signed_zeroのようなものを発明することを強制してい-0 ます

...複雑な平面の

複合平面の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    

この予測では、醜いだけでなく、OPの問題の根源でもある無限の「不均一な分布」が見られます。ほとんどの無限大(フォーム(+/- inf、finite)および(finite、+ / -inf)は4つの主要な方向にまとめられます。他のすべての方向は4つの無限大(+/- inf、+-inf)で表されます。複雑な乗算をこのジオメトリに拡張することは悪夢であることは驚くに値しません。 。

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

もっと上手にできる?

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

しかし、ここで朗報が終わります。ある意味では、私たちのニュースタイルの無限大が実数部または虚数部を抽出する関数をサポートすることを要求することにより、不当にではなく---正方形1に戻すことができます。追加は別の問題です。角度を未定義に設定する必要がある2つの非正反対の無限大を追加しますnan(つまり、角度は2つの入力角度の間にある必要があると主張できますが、「部分的な明るさ」を表す簡単な方法はありません)。

リーマンが救いに

これらすべてを考慮すると、古き良き1点コンパクト化は、最も安全な方法です。多分、Annex Gの作者は、cprojすべての無限をひとまとめにする関数を義務付けるときに同じことを感じました。


ここに、私よりも主題についてより有能な人々が回答する関連質問があります。


5
うん、のでnan != nan。私はこの答えが冗談だと​​理解していますが、それが書かれている方法でOPに役立つはずである理由がわかりません。
cmaster-モニカを復活させる'09年

質問の本文のコードが実際に使用されていなかった==(そして、他の回答を受け入れた)ことを考えると、OPがタイトルを表現する方法の問題にすぎなかったようです。その矛盾を修正するために、タイトルを書き換えました。(私が@cmasterに同意するため、この回答の前半を意図的に無効にしています。それは、この質問が求めていることではありません)。
Peter Cordes

3
@PeterCordes極座標を使用すると、複雑な乗算をスケーリングと回転として表示できるため、問題が発生します。1を掛けた場合のように、無限の「腕」を0度回転しても、有限の精度でその先端を配置することは期待できません。これは、私の意見では、受け入れられた説明よりも深い説明であり、nan!= nanルールに反響する説明でもあります。
Paul Panzer、

3
C99は、複素数型(ドラフト標準のセクション6.3.1.8)を乗算するときに、実際の浮動小数点型が複素数に昇格されないことを指定しています。私が知る限り、C ++のstd :: complexにも同じことが当てはまります。これは、1がそれらの言語のそれらの型の乗法的アイデンティティであることを意味します。Pythonも同じようにする必要があります。私はその現在の振る舞いを単にバグと呼びます。
benrg

2
@PaulPanzer:私はしませんが、基本的な概念は、1つのゼロ(これをZと呼びます)は常にx + Z = xおよびx * Z = Zを維持し、1 / Z = NaN、1(正infinitesimal)は1 / P = + INFを維持し、1(負の無限小)は1 / N = -INFを維持し、(unsigned infinitesimal)は1 / U = NaNを生成します。Xは、それがZを生じるであろうその場合に真の整数である場合を除き、一般的に、xxはUであろう
supercat

6

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

  1. int / floatは乗算で複素数に昇格されます
  2. 単純な学校の公式が使用されています。これは、無限の数が含まれるとすぐに望ましい/期待される結果を提供しません。
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標準の一部ではなく、C99にのみ付録Gとして含まれていました。これは、複雑な乗算の実行方法を説明しています。上記の学校の公式!C ++標準は複雑な乗算の動作を指定していないため、ほとんどのコンパイラー実装はC実装にフォールバックしています。C実装は、C99準拠(gcc、clang)かそうでない(MSVC)かです。

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

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

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

別の副作用floatに昇格されていないcomplexC99には掛けることであるinf+0.0j1.0か、すること1.0+0.0j(ここでライブを参照)異なる結果につながることができます:

  • (inf+0.0j)*1.0 = inf+0.0j
  • (inf+0.0j)*(1.0+0.0j) = inf-nanj、虚部があること-nanとないnan(参照すべての静かなNaNのが等価であるので、ここでは役割を果たしていない(CPythonのためのような)これを)、さらにいくつかのそれらのは、符号ビットのセットを(持っているので、として印刷された「 - 」を参照してくださいこれを)とそうでないものもあります。

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


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


私はナンビットパターンがたくさんあることを知っています。しかし、サインビットの事を知りませんでした。しかし、意味的には-nanはnanとどう違うのですか?それともナンとナンの違いはもっと違うのでしょうか?
Paul Panzer、

@PaulPanzerこれはprintf、doubleでの動作と同様の動作の単なる実装の詳細です。「-」を出力するかどうか(nanであるかどうかに関係なく)を決定するために符号ビットを調べます。だからあなたは正しい、「ナン」と「-ナン」の間に意味のある違いはなく、答えのこの部分をすぐに修正します。
ead

あぁ、いいね。fpについて知っていると思っていたすべてが実際には正しくないことを心配していました...
Paul Panzer

煩わしくて申し訳ありませんが、これは「架空の1.0、つまり、乗算に関して0.0 + 1.0jと同じではない1.0jがない」ことを確認します正しい?その附属書Gは純粋に虚数型(G.2)を指定し、それをどのように乗算するかなども規定しているようです(G.5.1)
Paul Panzer

@PaulPanzerいいえ、問題を指摘してくれてありがとう!c ++-coderとして、私はほとんどC99標準からC ++ glasesを見る-それは私の頭をすり抜けた、Cはここで一歩先を行っている-もう一度、あなたは明らかに正しい。
ead

3

Pythonからの面白い定義。私たちはペンと紙でこれを解決している場合は、私が期待される結果をだろうと言うでしょうexpected: (inf + 0j)、あなたが指摘したように、我々はの規範を意味することを知っているので、1そうします(float('inf')+0j)*1 =should= ('inf'+0j)

しかし、あなたが見ることができるようにそれはそうではありません...それを実行すると、私たちは次のようになります:

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

Pythonはこのことを理解し*1、複素数のない規範として1、それはとして解釈して*(1+0j)、私たちがやろうとするとエラーが表示されるinf * 0j = nanjようinf*0に解決することはできません。

あなたが実際にしたいこと(1が1の標準であると仮定):

場合、そのリコールz = x + iy実部Xと虚部Yの複素共役複素数であるzとして定義されz* = x − iy、その絶対値とも呼ばれるnorm of zように定義されます。

ここに画像の説明を入力してください

次のようなことをするの1が標準であると仮定1します。

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

あまり直感的ではありませんが、コーディング言語は、日常的に使用されているものとは異なる方法で定義されることがあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.