>>> (float('inf')+0j)*1
(inf+nanj)
ทำไม? สิ่งนี้ทำให้เกิดข้อบกพร่องที่น่ารังเกียจในรหัสของฉัน
เหตุใดจึงไม่1
ระบุตัวตนแบบทวีคูณการให้(inf + 0j)
?
>>> (float('inf')+0j)*1
(inf+nanj)
ทำไม? สิ่งนี้ทำให้เกิดข้อบกพร่องที่น่ารังเกียจในรหัสของฉัน
เหตุใดจึงไม่1
ระบุตัวตนแบบทวีคูณการให้(inf + 0j)
?
คำตอบ:
ค่า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
1
คือส่งไปที่1 + 0j
ใด
array([inf+0j])*1
array([inf+nanj])
สมมติว่าการคูณจริงเกิดขึ้นที่ไหนสักแห่งในโค้ด C / C ++ นั่นหมายความว่าพวกเขาเขียนโค้ดที่กำหนดเองเพื่อเลียนแบบพฤติกรรม CPython แทนที่จะใช้ _Complex หรือ std :: complex หรือไม่?
numpy
มีคลาสกลางหนึ่งคลาสufunc
ซึ่งเกือบทุกตัวดำเนินการและฟังก์ชันได้มา ufunc
ดูแลการจัดการการแพร่ภาพความก้าวหน้าของผู้ดูแลระบบที่ยุ่งยากซึ่งทำให้การทำงานกับอาร์เรย์เป็นไปอย่างสะดวกสบาย การแบ่งแรงงานระหว่างผู้ปฏิบัติงานเฉพาะและเครื่องจักรทั่วไปอย่างแม่นยำยิ่งขึ้นคือผู้ปฏิบัติงานเฉพาะจะใช้ชุด "ลูปด้านในสุด" สำหรับการรวมกันขององค์ประกอบอินพุตและเอาท์พุตแต่ละประเภทที่ต้องการจัดการ เครื่องจักรทั่วไปดูแลลูปด้านนอกและเลือกลูปด้านในสุดที่ดีที่สุด ...
types
แอตทริบิวต์สำหรับ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"
ในทางกลไกคำตอบที่ได้รับการยอมรับคือถูกต้อง แต่ฉันขอยืนยันว่าสามารถให้คำตอบที่ลึกกว่า
ก่อนอื่นการชี้แจงคำถามเป็นประโยชน์ในขณะที่ @PeterCordes กล่าวในความคิดเห็น: "มีข้อมูลประจำตัวแบบคูณสำหรับจำนวนเชิงซ้อนที่ทำงานบน inf + 0j หรือไม่" หรือกล่าวอีกนัยหนึ่งคือสิ่งที่ OP เห็นจุดอ่อนในการใช้งานคอมพิวเตอร์ของการคูณที่ซับซ้อนหรือมีบางอย่างที่ไม่เป็นไปตามแนวคิดกับinf+0j
การใช้พิกัดเชิงขั้วเราสามารถดูการคูณที่ซับซ้อนเป็นการสเกลและการหมุน การหมุน "แขน" ที่ไม่มีที่สิ้นสุดแม้จะเป็น 0 องศาในกรณีของการคูณด้วยหนึ่งเราคาดไม่ถึงว่าจะวางปลายแขนด้วยความแม่นยำ จำกัด แน่นอนว่ามีบางอย่างที่ไม่ถูกต้องโดยพื้นฐานinf+0j
กล่าวคือทันทีที่เราอยู่ที่ระยะอนันต์การชดเชยที่ จำกัด จะไม่มีความหมาย
ความเป็นมา: "เรื่องใหญ่" ที่คำถามนี้วนเวียนอยู่คือเรื่องของการขยายระบบของตัวเลข (คิดว่าเป็นจำนวนจริงหรือจำนวนเชิงซ้อน) เหตุผลหนึ่งที่เราอาจต้องการทำเช่นนั้นคือการเพิ่มแนวคิดเรื่องอินฟินิตี้หรือ "กระชับ" ถ้าคนหนึ่งเป็นนักคณิตศาสตร์ ยังมีเหตุผลอื่น ๆ อีกด้วย ( https://en.wikipedia.org/wiki/Galois_theory , https://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 )
ในขณะที่การย่อขนาดจุดเดียวนั้นเรียบง่ายและฟังดูดีในเชิงคณิตศาสตร์ แต่ก็มีการค้นหาส่วนขยายที่ "สมบูรณ์กว่า" ซึ่งประกอบด้วย infinties หลาย ๆ มาตรฐาน IEEE 754 สำหรับตัวเลขทศนิยมจริงมี + inf และ -inf ( https://en.wikipedia.org/wiki/Extended_real_number_line ) ดูเป็นธรรมชาติและตรงไปตรงมา แต่บังคับให้เรากระโดดผ่านห่วงและประดิษฐ์สิ่งต่างๆเช่น-0
https://en.wikipedia.org/wiki/Signed_zero
แล้วส่วนขยายมากกว่าหนึ่ง inf ของระนาบเชิงซ้อนล่ะ?
ในคอมพิวเตอร์โดยทั่วไปจำนวนเชิงซ้อนจะถูกนำมาใช้โดยการรวมค่า fp สองตัวเข้าด้วยกันสำหรับค่าจริงและอีกค่าหนึ่งสำหรับส่วนจินตภาพ นั่นเป็นสิ่งที่ดีอย่างสมบูรณ์ตราบเท่าที่ทุกอย่างยังไม่สิ้นสุด อย่างไรก็ตามในไม่ช้าเมื่อ infinities ถูกมองว่าเป็นเรื่องยุ่งยาก
เครื่องบินซับซ้อนมีทรงกลดสมมาตรธรรมชาติซึ่งความสัมพันธ์ในอย่างกับเลขคณิตซับซ้อนเท่าคูณเครื่องบินทั้งทาง e ^ phij 0
เป็นเช่นเดียวกับพีหมุนรอบเรเดียน
ตอนนี้เพื่อให้สิ่งต่าง ๆ ง่ายและซับซ้อน fp เพียงแค่ใช้ส่วนขยาย (+/- inf, nan เป็นต้น) ของการใช้งานจำนวนจริงพื้นฐาน ตัวเลือกนี้อาจดูเป็นธรรมชาติมากจนไม่ถูกมองว่าเป็นทางเลือก แต่ลองมาดูความหมายกันดีกว่า การแสดงภาพอย่างง่ายของส่วนขยายของระนาบเชิงซ้อนนี้ดูเหมือน (I = infinite, f = finite, 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
ในการฉายภาพนี้เราเห็น "การกระจายที่ไม่สม่ำเสมอ" ของ infinities ที่ไม่เพียง แต่น่าเกลียด แต่ยังเป็นต้นตอของปัญหาที่ OP ได้รับความเดือดร้อน: infinities ส่วนใหญ่ (ในรูปแบบ (+/- inf, finite) และ (จำกัด , + / -inf) ถูกรวมเข้าด้วยกันที่ทิศทางหลักทั้งสี่ทิศทางอื่น ๆ ทั้งหมดจะแสดงด้วย infinities เพียงสี่ตัว (+/- inf, + -inf) ไม่น่าแปลกใจที่การขยายการคูณที่ซับซ้อนไปยังรูปเรขาคณิตนี้เป็นฝันร้าย .
ภาคผนวก G ของข้อมูลจำเพาะ C99 พยายามอย่างเต็มที่เพื่อให้ใช้งานได้รวมถึงการปรับเปลี่ยนกฎเกี่ยวกับวิธีการinf
และnan
การโต้ตอบ (โดยพื้นฐานแล้วสำคัญinf
กว่าnan
) ปัญหาของ OP ถูกหลีกเลี่ยงโดยการไม่ส่งเสริมการเรียลและเสนอประเภทจินตนาการล้วนๆให้ซับซ้อน แต่การที่ 1 จริงทำงานแตกต่างจากคอมเพล็กซ์ 1 ไม่ได้ทำให้ฉันเป็นทางออก กล่าวได้ว่าภาคผนวก G ไม่ได้ระบุอย่างเต็มที่ว่าผลิตภัณฑ์ของสอง infinities ควรเป็นเท่าใด
เป็นเรื่องที่น่าสนใจที่จะลองแก้ไขปัญหาเหล่านี้โดยการเลือกรูปทรงเรขาคณิตของ infinities ที่ดีกว่า ในการเปรียบเทียบกับเส้นจริงที่ขยายเราสามารถเพิ่มอินฟินิตี้หนึ่งตัวสำหรับแต่ละทิศทาง โครงสร้างนี้คล้ายกับระนาบโปรเจ็กต์ แต่ไม่รวมกันเป็นก้อนตรงข้ามกัน Infinities จะแสดงในพิกัดเชิงขั้ว inf xe ^ {2 omega pi i} การกำหนดผลิตภัณฑ์จะตรงไปตรงมา โดยเฉพาะอย่างยิ่งปัญหาของ OP จะได้รับการแก้ไขอย่างเป็นธรรมชาติ
แต่นี่คือจุดสิ้นสุดของข่าวดี ในวิธีที่เราสามารถเหวี่ยงกลับไปที่กำลังสองได้โดย --- ไม่เกินสมควร --- ต้องการให้ infinities รูปแบบใหม่ของเรารองรับฟังก์ชันที่ดึงส่วนจริงหรือจินตภาพของมันออกมา นอกจากนี้เป็นปัญหาอื่น การเพิ่ม infinities แบบ nonantipodal สองตัวเราจะต้องตั้งค่ามุมเป็น undefined เช่นnan
(เราสามารถโต้แย้งได้ว่ามุมจะต้องอยู่ระหว่างมุมอินพุตทั้งสอง แต่ไม่มีวิธีง่ายๆในการแสดงว่า "nan-ness บางส่วน")
ในมุมมองของทั้งหมดนี้การกระชับจุดเดียวแบบเก่าที่ดีเป็นสิ่งที่ปลอดภัยที่สุดที่ควรทำ บางทีผู้เขียนภาคผนวก G อาจรู้สึกเช่นเดียวกันเมื่อกำหนดฟังก์ชันcproj
ที่รวมความไม่สมบูรณ์ทั้งหมดเข้าด้วยกัน
นี่คือคำถามที่เกี่ยวข้องซึ่งได้รับคำตอบจากผู้ที่มีความสามารถในเรื่องนี้มากกว่าฉัน
nan != nan
ใช่เพราะ ฉันเข้าใจว่าคำตอบนี้เป็นเรื่องล้อเล่น แต่ฉันไม่เข้าใจว่าเหตุใดจึงควรเป็นประโยชน์กับ OP ตามที่เขียนไว้
==
(และเนื่องจากพวกเขายอมรับคำตอบอื่น ๆ ) ดูเหมือนว่าจะเป็นปัญหาในการที่ OP แสดงชื่อ ฉันเปลี่ยนชื่อเรื่องเพื่อแก้ไขความไม่สอดคล้องกันนั้น (จงใจยกเลิกครึ่งแรกของคำตอบนี้เพราะฉันเห็นด้วยกับ @cmaster นั่นไม่ใช่สิ่งที่คำถามนี้ถาม)
นี่คือรายละเอียดการใช้งานเกี่ยวกับการใช้การคูณที่ซับซ้อนใน CPython แตกต่างจากภาษาอื่น ๆ (เช่น C หรือ C ++) CPython ใช้วิธีที่ค่อนข้างง่าย:
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;
}
กรณีหนึ่งที่มีปัญหากับรหัสด้านบนคือ:
(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 ซึ่งอาจเป็นไปตาม C99 (gcc, clang) หรือไม่ (MSVC)
สำหรับตัวอย่าง "ที่เป็นปัญหา" ข้างต้นการใช้งานที่สอดคล้องกับ C99 (ซึ่งซับซ้อนกว่าสูตรของโรงเรียน) จะให้ผลลัพธ์ที่คาดหวัง( ดูสด ):
(0.0+1.0*j)*(inf+inf*j) = -inf + inf*j
แม้ว่าจะใช้มาตรฐาน C99 แต่ก็ไม่ได้กำหนดผลลัพธ์ที่ชัดเจนสำหรับอินพุตทั้งหมดและอาจแตกต่างกันแม้ในเวอร์ชันที่รองรับ C99
ผลข้างเคียงอีกประการหนึ่งของการfloat
ไม่ได้รับการโปรโมตcomplex
ใน C99 คือการคูณinf+0.0j
ด้วย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) จะไม่มีบทบาทที่นี่เพราะ nans ที่เงียบทั้งหมดมีค่าเท่ากัน (ดูสิ่งนี้ ) แม้บางส่วนจะมีการตั้งค่าบิตเครื่องหมาย (และพิมพ์เป็น "-" ให้ดูสิ่งนี้ ) และบางส่วนไม่ซึ่งอย่างน้อยก็ตอบโต้ได้ง่าย
กุญแจสำคัญของฉันที่นำออกไปจากมันคือไม่มีอะไรง่าย ๆ เกี่ยวกับการคูณ (หรือหาร) จำนวนเชิงซ้อน "ง่ายๆ" และเมื่อสลับระหว่างภาษาหรือแม้แต่คอมไพเลอร์เราต้องรั้งตัวเองเพื่อหาจุดบกพร่อง / ความแตกต่างที่ละเอียดอ่อน
printf
และการทำงานที่คล้ายกันกับ double: พวกเขาดูที่ sign-bit เพื่อตัดสินใจว่าควรพิมพ์ "-" หรือไม่ (ไม่ว่าจะเป็น nan หรือไม่ก็ตาม) คุณพูดถูกไม่มีความแตกต่างที่มีความหมายระหว่าง "nan" และ "-nan" แก้ไขคำตอบส่วนนี้ในไม่ช้า
คำจำกัดความตลก ๆ จาก 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
และค่าสัมบูรณ์หรือที่เรียกว่า the 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)
ฉันไม่ค่อยเข้าใจ ... แต่บางครั้งภาษาการเขียนโค้ดก็ถูกกำหนดในลักษณะที่แตกต่างจากที่เราใช้ในแต่ละวัน