Mengapa (inf + 0j) * 1 bernilai inf + nanj?

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

Mengapa? Ini menyebabkan bug buruk dalam kode saya.

Mengapa bukan 1identitas multiplikatif, memberi(inf + 0j) ?

marnix
sumber
1
Menurut saya kata kunci yang Anda cari adalah " bidang ". Penjumlahan dan perkalian secara default ditentukan dalam satu bidang, dan dalam hal ini satu-satunya bidang standar yang dapat mengakomodasi kode Anda adalah bidang bilangan kompleks, jadi kedua bilangan tersebut perlu diperlakukan sebagai bilangan kompleks secara default sebelum operasi berjalan dengan baik- ditentukan. Bukan berarti mereka tidak dapat memperluas definisi ini, tetapi tampaknya mereka hanya mengikuti hal yang standar dan tidak merasakan dorongan untuk keluar dari jalan mereka untuk memperluas definisi.
pengguna541686
1
Oh, dan jika Anda merasa keanehan ini membuat frustrasi dan ingin menghantam komputer Anda , saya bersimpati .
pengguna541686
2
@Mehrdad setelah Anda menambahkan elemen tidak terbatas itu berhenti menjadi bidang. Memang, karena tidak ada multiplikatif netral lagi, menurut definisi tidak bisa menjadi bidang.
Paul Panzer
@PaulPanzer: Ya, saya pikir mereka baru saja mendorong elemen-elemen itu sesudahnya.
pengguna541686
1
angka floating point (bahkan jika Anda mengecualikan infinity dan NaN) bukan bidang. Sebagian besar identitas yang menyimpan bidang tidak berlaku untuk bilangan floating point.
plugwash

Jawaban:

95

Pertama, 1diubah menjadi bilangan kompleks 1 + 0j, yang kemudian mengarah ke inf * 0perkalian, menghasilkan a 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
Marat
sumber
8
Untuk menjawab pertanyaan "mengapa ...?", Mungkin langkah terpenting adalah yang pertama, ke mana 1dilemparkan 1 + 0j.
Warren Weckesser
5
Perhatikan bahwa C99 menetapkan bahwa tipe floating-point nyata tidak dipromosikan menjadi kompleks saat mengalikan dengan tipe kompleks (bagian 6.3.1.8 dari standar draf), dan sejauh yang saya tahu hal yang sama berlaku untuk C ++ std :: complex. Ini mungkin sebagian karena alasan kinerja tetapi juga menghindari NaN yang tidak perlu.
benrg
@benrg Dalam NumPy, array([inf+0j])*1evaluasi juga ke array([inf+nanj]). Dengan asumsi bahwa perkalian sebenarnya terjadi di suatu tempat dalam kode C / C ++, apakah ini berarti bahwa mereka menulis kode kustom untuk meniru perilaku CPython, daripada menggunakan _Complex atau std :: complex?
marnix
1
@marnix lebih terlibat dari itu. numpymemiliki satu kelas pusat ufuncyang darinya hampir setiap operator dan fungsi berasal. ufuncmenangani penyiaran mengelola langkah semua admin rumit yang membuat bekerja dengan array begitu nyaman. Lebih tepatnya pembagian kerja antara operator tertentu dan mesin umum adalah bahwa operator tertentu menerapkan serangkaian "loop terdalam" untuk setiap kombinasi jenis elemen masukan dan keluaran yang ingin ditangani. Mesin umum menangani setiap loop luar dan memilih loop paling dalam yang paling cocok ...
Paul Panzer
1
... mempromosikan jenis yang tidak sama persis sesuai kebutuhan. Kita dapat mengakses daftar loop dalam yang disediakan melalui typesatribut untuk np.multiplyhasil ini. ['??->?', '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']Kita dapat melihat bahwa hampir tidak ada tipe campuran, khususnya, tidak ada yang menggabungkan float "efdg"dengan kompleks "FDG".
Paul Panzer
32

Secara mekanis, jawaban yang diterima, tentu saja, benar, tetapi saya berpendapat bahwa jawaban yang lebih dalam dapat diberikan.

Pertama, akan berguna untuk memperjelas pertanyaan seperti yang dilakukan @PeterCordes dalam komentar: "Apakah ada identitas perkalian untuk bilangan kompleks yang berfungsi pada inf + 0j?"atau dengan kata lain adalah apa yang OP melihat kelemahan dalam implementasi komputer perkalian kompleks atau adakah sesuatu yang secara konseptual tidak sehatinf+0j

Jawaban singkat:

Dengan menggunakan koordinat kutub kita dapat melihat perkalian kompleks sebagai penskalaan dan rotasi. Memutar "lengan" tak terhingga bahkan sebesar 0 derajat seperti dalam kasus mengalikan dengan satu, kita tidak dapat mengharapkan untuk menempatkan ujungnya dengan presisi terbatas. Jadi memang, ada sesuatu yang secara fundamental tidak benarinf+0j , yaitu bahwa begitu kita berada pada ketakterhinggaan offset yang terbatas menjadi tidak berarti.

Jawaban panjang:

Latar belakang: "Hal besar" yang melingkupi pertanyaan ini adalah masalah perluasan sistem bilangan (pikirkan bilangan real atau bilangan kompleks). Salah satu alasan seseorang mungkin ingin melakukan itu adalah menambahkan beberapa konsep tak terhingga, atau untuk "memadatkan" jika seseorang kebetulan adalah seorang ahli matematika. Ada alasan lain juga ( https://en.wikipedia.org/wiki/Galois_theory , https://en.wikipedia.org/wiki/Non-standard_analysis ), tetapi kami tidak tertarik dengan tersebut di sini.

Pemadatan satu titik

Hal yang rumit tentang ekstensi seperti itu, tentu saja, kita ingin angka-angka baru ini sesuai dengan aritmatika yang ada. Cara termudah adalah menambahkan satu elemen pada tak terhingga ( https://en.wikipedia.org/wiki/Alexandroff_extension ) dan membuatnya sama dengan apa pun kecuali nol dibagi nol. Ini berfungsi untuk real ( https://en.wikipedia.org/wiki/Projectively_extended_real_line ) dan bilangan kompleks ( https://en.wikipedia.org/wiki/Riemann_sphere ).

Ekstensi lain ...

Sementara pemadatan satu titik sederhana dan secara matematis terdengar, ekstensi "lebih kaya" yang terdiri dari beberapa infinties telah dicari. Standar IEEE 754 untuk bilangan floating point nyata memiliki + inf dan -inf ( https://en.wikipedia.org/wiki/Extended_real_number_line ). Tampak alami dan lugas tetapi sudah memaksa kita untuk melewati rintangan dan menciptakan hal-hal seperti-0 https://en.wikipedia.org/wiki/Signed_zero

... dari bidang kompleks

Bagaimana dengan ekstensi lebih dari satu inf dari bidang kompleks?

Di komputer, bilangan kompleks biasanya diimplementasikan dengan menempelkan dua real fp bersama-sama, satu untuk real dan satu untuk bagian imajiner. Itu baik-baik saja selama semuanya terbatas. Namun, segera, karena ketidakterbatasan dianggap hal-hal menjadi rumit.

Bidang kompleks memiliki simetri rotasi alami, yang terikat baik dengan aritmatika kompleks karena mengalikan seluruh bidang dengan e ^ phij sama dengan rotasi phi radian di sekitarnya 0 .

Benda annex G itu

Sekarang, untuk menjaga agar tetap sederhana, fp kompleks cukup menggunakan ekstensi (+/- inf, nan dll.) Dari implementasi bilangan real yang mendasarinya. Pilihan ini mungkin tampak begitu alami bahkan tidak dianggap sebagai pilihan, tapi mari kita lihat lebih dekat apa yang tersirat. Visualisasi sederhana dari perluasan bidang kompleks ini terlihat seperti (I = tak hingga, f = hingga, 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

Tetapi karena bidang kompleks yang sebenarnya adalah bidang yang menghormati perkalian kompleks, proyeksi yang lebih informatif akan menjadi

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

Dalam proyeksi ini kita melihat "distribusi tidak merata" dari ketidakterbatasan yang tidak hanya jelek tetapi juga akar masalah dari jenis yang diderita OP: Kebanyakan ketidakterbatasan (yang berbentuk (+/- inf, finite) dan (finite, + / -inf) disatukan di empat arah utama semua arah lainnya diwakili oleh hanya empat infinities (+/- inf, + -inf). Seharusnya tidak mengherankan bahwa memperluas perkalian kompleks ke geometri ini adalah mimpi buruk .

Lampiran G dari spesifikasi C99 mencoba yang terbaik untuk membuatnya berfungsi, termasuk membengkokkan aturan tentang bagaimana infdan nanberinteraksi (pada dasarnya infmengalahkan nan). Masalah OP dikesampingkan dengan tidak mempromosikan real dan tipe imajiner murni yang diusulkan ke kompleks, tetapi memiliki perilaku 1 yang nyata berbeda dari kompleks 1 tidak menurut saya sebagai solusi. Singkatnya, Lampiran G berhenti menjelaskan secara lengkap apa produk dari dua tak terbatas seharusnya.

Bisakah kita berbuat lebih baik?

Sangat menggoda untuk mencoba dan memperbaiki masalah ini dengan memilih geometri tak terbatas yang lebih baik. Dalam analogi garis real yang diperpanjang kita bisa menambahkan satu tak terhingga untuk setiap arah. Konstruksi ini mirip dengan bidang proyektif tetapi tidak saling bertemu berlawanan arah. Ketidakterbatasan akan direpresentasikan dalam koordinat kutub inf xe ^ {2 omega pi i}, mendefinisikan produk akan menjadi mudah. Secara khusus, masalah OP akan diselesaikan secara alami.

Tapi disinilah kabar baik berakhir. Di satu sisi, kita dapat dilemparkan kembali ke titik awal dengan --- bukan tidak masuk akal --- mengharuskan infinitas gaya baru kita mendukung fungsi yang mengekstrak bagian nyata atau imajinernya. Penambahan adalah masalah lain; menambahkan dua ketidakterbatasan nonantipodal kita harus mengatur sudut ke tidak terdefinisi yaitu nan(orang dapat berargumen bahwa sudut harus terletak di antara dua sudut masukan tetapi tidak ada cara sederhana untuk menyatakan bahwa "nan-ness parsial")

Riemann untuk menyelamatkan

Mengingat semua ini mungkin pemadatan satu titik yang baik adalah hal yang paling aman untuk dilakukan. Mungkin penulis Lampiran G merasakan hal yang sama ketika mengamanatkan fungsi cprojyang menggabungkan semua infinitas menjadi satu.


Berikut adalah pertanyaan terkait yang dijawab oleh orang-orang yang lebih kompeten pada materi pelajaran daripada saya.

Paul Panzer
sumber
5
Ya, karena nan != nan. Saya mengerti bahwa jawaban ini setengah bercanda, tetapi saya gagal untuk melihat mengapa itu harus membantu OP dalam cara penulisannya.
cmaster
Mengingat bahwa kode dalam badan pertanyaan tidak benar-benar digunakan ==(dan karena mereka menerima jawaban lain), tampaknya itu hanya masalah bagaimana OP mengungkapkan judulnya. Saya mengubah judul untuk memperbaiki ketidakkonsistenan itu. (Sengaja membatalkan paruh pertama jawaban ini karena saya setuju dengan @cmaster: bukan itu yang ditanyakan oleh pertanyaan ini).
Peter Cordes
3
@PeterCordes yang akan mengganggu karena menggunakan koordinat kutub kita dapat melihat perkalian kompleks sebagai penskalaan dan rotasi. Memutar "lengan" yang tak terhingga bahkan sebesar 0 derajat seperti dalam kasus mengalikan dengan satu, kita tidak dapat mengharapkan untuk menempatkan ujungnya dengan presisi yang terbatas. Ini menurut saya penjelasan yang lebih dalam daripada yang diterima, dan juga satu dengan gema dalam aturan nan! = Nan.
Paul Panzer
3
C99 menetapkan bahwa tipe floating-point nyata tidak dipromosikan menjadi kompleks ketika mengalikan dengan tipe kompleks (bagian 6.3.1.8 dari standar draf), dan sejauh yang saya tahu hal yang sama berlaku untuk C ++'s std :: complex. Artinya 1 adalah identitas perkalian untuk tipe-tipe tersebut dalam bahasa tersebut. Python harus melakukan hal yang sama. Saya akan menyebut perilakunya saat ini sebagai bug.
benrg
2
@PaulPanzer: Saya tidak, tetapi konsep dasarnya adalah bahwa satu nol (yang akan saya sebut Z) akan selalu menjunjung tinggi x + Z = x dan x * Z = Z, dan 1 / Z = NaN, satu (positif sangat kecil) akan mendukung 1 / P = + INF, satu (sangat kecil negatif) akan menjunjung 1 / N = -INF, dan (tidak bertanda sangat kecil) akan menghasilkan 1 / U = NaN. Secara umum, xx akan menjadi U kecuali x adalah bilangan bulat yang benar, dalam hal ini akan menghasilkan Z.
supercat
6

Ini adalah detail implementasi tentang bagaimana perkalian kompleks diimplementasikan di CPython. Tidak seperti bahasa lain (misalnya C atau C ++), CPython mengambil pendekatan yang agak sederhana:

  1. ints / float dipromosikan menjadi bilangan kompleks dalam perkalian
  2. rumus sekolah sederhana digunakan , yang tidak memberikan hasil yang diinginkan / diharapkan segera setelah jumlah tak terbatas terlibat:
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;
}

Satu kasus bermasalah dengan kode di atas adalah:

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

Namun, seseorang ingin mendapatkan -inf + inf*jhasilnya.

Dalam hal ini bahasa lain tidak jauh di depan: perkalian bilangan kompleks untuk waktu yang lama bukan bagian dari standar C, termasuk hanya dalam C99 sebagai lampiran G, yang menjelaskan bagaimana perkalian kompleks harus dilakukan - dan itu tidak sesederhana seperti rumus sekolah di atas! Standar C ++ tidak menentukan bagaimana perkalian kompleks harus bekerja, sehingga sebagian besar implementasi compiler kembali ke implementasi C, yang mungkin sesuai dengan C99 (gcc, clang) atau tidak (MSVC).

Untuk contoh "bermasalah" di atas, implementasi yang sesuai dengan C99 (yang lebih rumit daripada rumus sekolah) akan memberikan ( lihat langsung ) hasil yang diharapkan:

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

Bahkan dengan standar C99, hasil yang tidak ambigu tidak ditentukan untuk semua input dan mungkin berbeda bahkan untuk versi yang kompatibel dengan C99.

Efek samping lain dari floattidak dipromosikan complexdi C99 adalah bahwa mengalikan inf+0.0jdengan 1.0atau 1.0+0.0jdapat menghasilkan hasil yang berbeda (lihat di sini langsung):

  • (inf+0.0j)*1.0 = inf+0.0j
  • (inf+0.0j)*(1.0+0.0j) = inf-nanj, bagian imajiner menjadi -nandan bukan nan(seperti untuk CPython) tidak berperan di sini, karena semua nan tenang adalah setara (lihat ini ), bahkan beberapa di antaranya memiliki set tanda-bit (dan dengan demikian dicetak sebagai "-", lihat ini ) dan beberapa tidak.

Yang setidaknya kontra-intuitif.


Kuncinya adalah: tidak ada yang sederhana tentang perkalian (atau pembagian) bilangan kompleks yang "sederhana" dan ketika beralih antar bahasa atau bahkan penyusun, seseorang harus mempersiapkan diri untuk bug / perbedaan halus.

ead
sumber
Saya tahu ada banyak pola nan bit. Tidak tahu apa-apa tentang tanda itu. Tapi maksud saya secara semantik Bagaimana -nan berbeda dari nan? Atau haruskah saya katakan lebih berbeda dari nan dari nan?
Paul Panzer
@PaulPanzer Ini hanyalah detail implementasi tentang bagaimana printfdan serupa bekerja dengan ganda: mereka melihat tanda-bit untuk memutuskan apakah "-" harus dicetak atau tidak (tidak peduli apakah itu nan atau tidak). Jadi Anda benar, tidak ada perbedaan yang berarti antara "nan" dan "-nan", segera perbaiki bagian jawaban ini.
ead
Ah bagus. Sangat mengkhawatirkan bahwa semua yang saya pikir saya ketahui tentang fp sebenarnya tidak benar ...
Paul Panzer
Maaf mengganggu, tetapi apakah Anda yakin ini "tidak ada 1.0 imajiner, yaitu 1.0j yang tidak sama dengan 0.0 + 1.0j sehubungan dengan perkalian." benar? Lampiran G itu tampaknya menentukan tipe imajiner murni (G.2) dan juga untuk menentukan bagaimana itu harus dikalikan dll. (G.5.1)
Paul Panzer
@PaulPanzer Tidak, terima kasih telah menunjukkan masalahnya! Sebagai c ++ - coder, saya kebanyakan melihat C99-standard melalui C ++ - glases - itu meleset dari pikiran saya, bahwa C adalah selangkah lebih maju di sini - Anda jelas benar, sekali lagi.
ead
3

Definisi lucu dari Python. Jika kita memecahkan ini dengan pena dan kertas saya akan mengatakan bahwa hasil yang diharapkan akan expected: (inf + 0j)seperti yang Anda menunjukkan karena kita tahu bahwa kita maksud norma 1sehingga(float('inf')+0j)*1 =should= ('inf'+0j) :

Tapi bukan itu masalahnya seperti yang Anda lihat ... ketika kami menjalankannya, kami mendapatkan:

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

Python memahami ini *1sebagai bilangan kompleks dan bukan norma 1sehingga ia menafsirkan sebagai *(1+0j)dan kesalahan muncul ketika kita mencoba melakukan inf * 0j = nanjsebagaiinf*0 tidak dapat diselesaikan.

Apa yang sebenarnya ingin Anda lakukan (dengan asumsi 1 adalah norma 1):

Ingatlah bahwa jika z = x + iybilangan kompleks dengan bagian nyata x dan bagian imajiner y, konjugasi kompleks dari zdidefinisikan sebagai z* = x − iy, dan nilai absolut, juga disebut the norm of zdidefinisikan sebagai:

masukkan deskripsi gambar di sini

Dengan asumsi 1norma 1kita harus melakukan sesuatu seperti:

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

tidak terlalu intuitif, saya tahu ... tetapi terkadang bahasa pengkodean didefinisikan dengan cara yang berbeda dari apa yang kita gunakan sehari-hari.

costargc
sumber