>>> (float('inf')+0j)*1
(inf+nanj)
Tại sao? Điều này gây ra một lỗi khó chịu trong mã của tôi.
Tại sao không phải 1
là nhân dạng nhân, cho (inf + 0j)
?
>>> (float('inf')+0j)*1
(inf+nanj)
Tại sao? Điều này gây ra một lỗi khó chịu trong mã của tôi.
Tại sao không phải 1
là nhân dạng nhân, cho (inf + 0j)
?
Câu trả lời:
Đầu tiên, 1
được chuyển đổi thành một số phức 1 + 0j
, sau đó dẫn đến một inf * 0
phép nhân, dẫn đến 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
1
được truyền đến đâu 1 + 0j
.
array([inf+0j])*1
cũng đánh giá array([inf+nanj])
. Giả sử rằng phép nhân thực sự xảy ra ở đâu đó trong mã C / C ++, điều này có nghĩa là họ đã viết mã tùy chỉnh để mô phỏng hành vi CPython, thay vì sử dụng _Complex hoặc std :: complex?
numpy
có một lớp trung tâm ufunc
mà từ đó hầu hết mọi toán tử và hàm đều dẫn xuất. ufunc
đảm nhận các bước quản lý phát sóng tất cả những gì quản trị viên khó khăn làm cho việc làm việc với các mảng trở nên thuận tiện. Chính xác hơn, sự phân chia lao động giữa một người vận hành cụ thể và máy móc nói chung là người vận hành cụ thể thực hiện một tập hợp các "vòng trong cùng" cho mỗi tổ hợp các loại phần tử đầu vào và đầu ra mà nó muốn xử lý. Các máy móc nói chung sẽ chăm sóc của bất kỳ vòng lặp bên ngoài và lựa chọn phù hợp nhất trong cùng vòng ...
types
thuộc tính cho kết quả np.multiply
này, ['??->?', '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']
chúng ta có thể thấy rằng hầu như không có loại hỗn hợp nào, đặc biệt, không có loại nào trộn float "efdg"
với complex "FDG"
.
Về mặt cơ học, câu trả lời được chấp nhận tất nhiên là chính xác, nhưng tôi sẽ tranh luận rằng có thể đưa ra một ansswer sâu hơn.
Trước tiên, sẽ hữu ích khi làm rõ câu hỏi như @PeterCordes thực hiện trong một nhận xét: "Có nhận dạng nhân cho các số phức hoạt động trên inf + 0j không?" hay nói cách khác là OP nhận thấy điểm yếu trong việc thực hiện phép nhân phức tạp trên máy tính hoặc có điều gì đó không liên quan đến khái niệminf+0j
Sử dụng tọa độ cực, chúng ta có thể xem phép nhân phức tạp như một phép chia tỷ lệ và phép quay. Xoay một "cánh tay" vô hạn thậm chí bằng 0 độ như trong trường hợp nhân với một, chúng ta không thể mong đợi đặt mũi của nó với độ chính xác hữu hạn. Vì vậy, thực sự, có một cái gì đó về cơ bản không đúng với inf+0j
, đó là ngay khi chúng ta ở vô cùng, một phần bù hữu hạn trở nên vô nghĩa.
Bối cảnh: "Điều lớn" xoay quanh câu hỏi này là vấn đề mở rộng một hệ thống các số (nghĩ là số thực hoặc số phức). Một lý do mà người ta có thể muốn làm điều đó là thêm một số khái niệm về vô cực, hoặc để "compactify" nếu một người tình cờ là một nhà toán học. Cũng có những lý do khác ( https://en.wikipedia.org/wiki/Galois_theory , https://en.wikipedia.org/wiki/Non-standard_analysis ), nhưng chúng tôi không quan tâm đến những lý do đó ở đây.
Tất nhiên, một chút khó khăn về phần mở rộng như vậy là chúng tôi muốn những con số mới này phù hợp với số học hiện có. Cách đơn giản nhất là thêm một phần tử duy nhất vào vô cùng ( https://en.wikipedia.org/wiki/Alexandroff_extension ) và làm cho nó bằng bất kỳ thứ gì trừ 0 chia cho 0. Điều này hoạt động đối với thực ( https://en.wikipedia.org/wiki/Projaries_extended_real_line ) và số phức ( https://en.wikipedia.org/wiki/Riemann_sphere ).
Mặc dù việc phân tích một điểm là đơn giản và đúng về mặt toán học, các phần mở rộng "phong phú hơn" bao gồm nhiều thông tin đã được tìm kiếm. Tiêu chuẩn IEEE 754 cho số dấu phẩy động thực có + inf và -inf ( https://en.wikipedia.org/wiki/Extended_real_number_line ). Trông tự nhiên và đơn giản nhưng đã buộc chúng ta phải nhảy qua vòng và phát minh ra những thứ như -0
https://en.wikipedia.org/wiki/Signed_zero
Điều gì về phần mở rộng nhiều hơn một inf của mặt phẳng phức tạp?
Trong máy tính, số phức thường được thực hiện bằng cách gắn hai số thực fp lại với nhau, một cho phần thực và một cho phần ảo. Điều đó hoàn toàn tốt miễn là mọi thứ là hữu hạn. Tuy nhiên, ngay sau đó, vì số vô hạn được coi là mọi thứ trở nên phức tạp.
Mặt phẳng phức có một phép đối xứng quay tự nhiên, liên kết độc đáo với số học phức khi nhân toàn bộ mặt phẳng với e ^ phij giống như một phép quay phi radian xung quanh 0
.
Bây giờ, để giữ cho mọi thứ đơn giản, phức tạp fp chỉ cần sử dụng các phần mở rộng (+/- inf, nan, v.v.) của triển khai số thực cơ bản. Sự lựa chọn này có vẻ tự nhiên đến mức thậm chí không được coi là một sự lựa chọn, nhưng chúng ta hãy xem xét kỹ hơn ý nghĩa của nó. Hình dung đơn giản về phần mở rộng của mặt phẳng phức này trông giống như (I = vô hạn, f = hữu hạn, 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
Nhưng vì một mặt phẳng phức thực sự là một mặt phẳng tôn trọng phép nhân phức, một phép chiếu nhiều thông tin hơn sẽ là
III
I I
fffff
fffffff
fffffffff
I fffffffff I
I ffff0ffff I
I fffffffff I
fffffffff
fffffff
fffff
I I
III
Trong phép chiếu này, chúng ta thấy "sự phân bố không đồng đều" của các số vô hạn không chỉ xấu xí mà còn là gốc rễ của các vấn đề thuộc loại mà OP đã mắc phải: Phần lớn các số vô hạn (những dạng có dạng (+/- inf, hữu hạn) và (hữu hạn, + / -inf) được gộp lại với nhau ở bốn hướng chính, tất cả các hướng khác chỉ được biểu thị bằng bốn số vô hạn (+/- inf, + -inf). Không có gì ngạc nhiên khi mở rộng phép nhân phức cho hình học này là một cơn ác mộng .
Phụ lục G của thông số kỹ thuật C99 cố gắng hết sức để làm cho nó hoạt động, bao gồm cả việc bẻ cong các quy tắc về cách thức inf
và nan
tương tác (về cơ bản là inf
vượt trội nan
). Vấn đề của OP bị bỏ qua bằng cách không quảng cáo thực và một kiểu hoàn toàn tưởng tượng được đề xuất thành phức, nhưng việc 1 thực hành xử khác với phức 1 không khiến tôi coi đó là một giải pháp. Nói một cách rõ ràng, Phụ lục G dừng lại ở việc quy định đầy đủ sản phẩm của hai số vô hạn phải là gì.
Thật hấp dẫn để thử và khắc phục những vấn đề này bằng cách chọn một dạng hình học tốt hơn của các số vô hạn. Tương tự với đường thực mở rộng, chúng ta có thể thêm một vô cực cho mỗi hướng. Cấu trúc này tương tự như mặt phẳng xạ ảnh nhưng không gộp lại với nhau theo các hướng ngược nhau. Các số vô hạn sẽ được biểu diễn trong các tọa độ cực inf xe ^ {2 omega pi i}, việc xác định các sản phẩm sẽ đơn giản. Đặc biệt, vấn đề của OP sẽ được giải quyết khá tự nhiên.
Nhưng đây là nơi tin tốt kết thúc. Theo một cách nào đó, chúng ta có thể trở lại bình phương một cách --- không phải là vô lý --- yêu cầu rằng các hàm vô hạn kiểu mới của chúng ta hỗ trợ các hàm trích xuất các phần thực hoặc ảo của chúng. Bổ sung là một vấn đề khác; thêm hai số vô hạn không đối phương, chúng ta sẽ phải đặt góc thành không xác định, tức là nan
(người ta có thể lập luận rằng góc phải nằm giữa hai góc đầu vào nhưng không có cách đơn giản nào để biểu diễn "một phần nan-ness")
Theo quan điểm của tất cả những điều này, có lẽ việc phân tích một điểm cũ tốt là điều an toàn nhất để làm. Có thể các tác giả của Phụ lục G cũng cảm thấy như vậy khi yêu cầu một hàm cproj
gộp tất cả các số vô hạn lại với nhau.
Đây là một câu hỏi liên quan được trả lời bởi những người có thẩm quyền về chủ đề này hơn tôi.
nan != nan
. Tôi hiểu rằng câu trả lời này là nửa đùa nửa thật, nhưng tôi không hiểu tại sao nó lại hữu ích cho OP như cách nó được viết.
==
(và họ đã chấp nhận câu trả lời khác), có vẻ như vấn đề chỉ là cách OP diễn đạt tiêu đề. Tôi đã đặt lại tiêu đề để khắc phục sự không nhất quán đó. (Cố ý làm vô hiệu nửa đầu của câu trả lời này vì tôi đồng ý với @cmaster: đó không phải là điều mà câu hỏi này đang hỏi).
Đây là chi tiết triển khai về cách thực hiện phép nhân phức tạp trong CPython. Không giống như các ngôn ngữ khác (ví dụ: C hoặc C ++), CPython có một cách tiếp cận hơi đơn giản:
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;
}
Một trường hợp có vấn đề với đoạn mã trên sẽ là:
(0.0+1.0*j)*(inf+inf*j) = (0.0*inf-1*inf)+(0.0*inf+1.0*inf)j
= nan + nan*j
Tuy nhiên, người ta muốn có -inf + inf*j
kết quả như vậy.
Về mặt này, các ngôn ngữ khác cũng không vượt xa: phép nhân số phức từ lâu không thuộc tiêu chuẩn C, chỉ được đưa vào C99 dưới dạng phụ lục G, mô tả cách thực hiện một phép nhân phức - và nó không đơn giản như công thức trường ở trên! Tiêu chuẩn C ++ không chỉ định cách phép nhân phức tạp sẽ hoạt động như thế nào, do đó hầu hết các triển khai trình biên dịch đang quay trở lại triển khai C, có thể tuân theo C99 (gcc, clang) hoặc không (MSVC).
Đối với ví dụ "có vấn đề" ở trên, các triển khai tuân thủ C99 ( phức tạp hơn công thức trường học) sẽ cho ( xem trực tiếp ) kết quả mong đợi:
(0.0+1.0*j)*(inf+inf*j) = -inf + inf*j
Ngay cả với tiêu chuẩn C99, một kết quả rõ ràng không được xác định cho tất cả các đầu vào và nó có thể khác ngay cả đối với các phiên bản tuân thủ C99.
Một tác dụng phụ khác của việc float
không được quảng bá complex
trong C99 là nhân inf+0.0j
với 1.0
hoặc 1.0+0.0j
có thể dẫn đến các kết quả khác nhau (xem trực tiếp tại đây):
(inf+0.0j)*1.0 = inf+0.0j
(inf+0.0j)*(1.0+0.0j) = inf-nanj
, phần ảo có -nan
và không nan
(như đối với CPython) không đóng vai trò gì ở đây, bởi vì tất cả các khoảng trống yên tĩnh đều tương đương (xem phần này ), thậm chí một số trong số chúng có bộ bit dấu (và do đó được in thành "-", hãy xem phần này ) và một số thì không.Mà ít nhất là phản trực quan.
Điểm mấu chốt của tôi là: không có gì đơn giản về phép nhân (hoặc chia) số phức "đơn giản" và khi chuyển đổi giữa các ngôn ngữ hoặc thậm chí các trình biên dịch, người ta phải cố gắng tìm những lỗi / khác biệt nhỏ.
printf
hoạt động tương tự với double: họ nhìn vào bit dấu để quyết định xem "-" có nên được in hay không (bất kể nó có nan hay không). Vì vậy, bạn nói đúng, không có sự khác biệt ý nghĩa giữa "nan" và "-nan", sửa chữa phần này của câu trả lời sớm.
Định nghĩa hài hước từ Python. Nếu chúng ta giải quyết vấn đề này bằng bút và giấy, tôi sẽ nói rằng kết quả mong đợi sẽ expected: (inf + 0j)
như bạn đã chỉ ra bởi vì chúng tôi biết rằng chúng tôi có nghĩa là tiêu chuẩn của 1
như vậy (float('inf')+0j)*1 =should= ('inf'+0j)
:
Nhưng đó không phải là trường hợp như bạn có thể thấy ... khi chúng tôi chạy nó, chúng tôi nhận được:
>>> Complex( float('inf') , 0j ) * 1
result: (inf + nanj)
Python hiểu đây *1
là một số phức và không phải là tiêu chuẩn 1
để nó diễn giải như vậy *(1+0j)
và lỗi xuất hiện khi chúng tôi cố gắng làm inf * 0j = nanj
như inf*0
không thể được giải quyết.
Những gì bạn thực sự muốn làm (giả sử 1 là tiêu chuẩn của 1):
Nhớ lại rằng nếu z = x + iy
là một số phức với phần thực x và phần ảo y, thì liên hợp phức của z
được xác định là z* = x − iy
, và giá trị tuyệt đối, còn được gọi là giá trị norm of z
được định nghĩa là:
Giả định 1
là tiêu chuẩn của 1
chúng ta nên làm điều gì đó như:
>>> c_num = complex(float('inf'),0)
>>> value = 1
>>> realPart=(c_num.real)*value
>>> imagPart=(c_num.imag)*value
>>> complex(realPart,imagPart)
result: (inf+0j)
Tôi biết là không trực quan lắm ... nhưng đôi khi các ngôn ngữ mã hóa được định nghĩa theo một cách khác với những gì chúng ta được sử dụng hàng ngày.