Tại sao (inf + 0j) * 1 đánh giá thành inf + nanj?


97
>>> (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 1là nhân dạng nhân, cho (inf + 0j)?


1
Tôi nghĩ từ khóa bạn đang tìm là " trường ". Phép cộng và phép nhân được xác định theo mặc định trong một trường duy nhất và trong trường hợp này, trường tiêu chuẩn duy nhất có thể chứa mã của bạn là trường số phức, do đó, cả hai số cần được coi là số phức theo mặc định trước khi hoạt động tốt- được xác định. Điều này không có nghĩa là họ không thể mở rộng các định nghĩa này, nhưng rõ ràng là họ chỉ làm theo tiêu chuẩn và không cảm thấy thôi thúc phải cố gắng mở rộng các định nghĩa.
user541686

1
Ồ, và nếu bạn thấy những đặc điểm riêng này khiến bạn bực bội và muốn đấm máy tính, bạn rất thông cảm .
user541686

2
@Mehrdad khi bạn thêm các phần tử không hữu hạn đó, nó sẽ không còn là một trường. Thật vậy, vì không còn trung lập nhân nữa nên theo định nghĩa, nó không thể là một trường.
Paul Panzer

@PaulPanzer: Vâng, tôi nghĩ sau đó họ chỉ đẩy những yếu tố đó vào.
user541686 22/09/19

1
số dấu phẩy động (ngay cả khi bạn loại trừ vô cực và NaN) không phải là một trường. Hầu hết các danh tính giữ cho các trường không giữ cho số dấu phẩy động.
plugwash

Câu trả lời:


95

Đầu tiên, 1được chuyển đổi thành một số phức 1 + 0j, sau đó dẫn đến một inf * 0phé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

8
Để trả lời câu hỏi "tại sao ...?", Có lẽ bước quan trọng nhất là bước đầu tiên, 1được truyền đến đâu 1 + 0j.
Warren Weckesser

5
Lưu ý rằng C99 chỉ định rằng các kiểu dấu phẩy động thực không được thăng cấp thành phức khi nhân với một kiểu phức (phần 6.3.1.8 của tiêu chuẩn nháp) và theo như tôi biết thì điều này cũng đúng với std :: complex của C ++. Điều này một phần có thể là vì lý do hiệu suất nhưng nó cũng tránh được các NaN không cần thiết.
benrg

@benrg Trong NumPy, array([inf+0j])*1cũ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?
marnix

1
@marnix nó liên quan nhiều hơn thế. numpycó một lớp trung tâm ufuncmà 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 ...
Paul Panzer

1
... quảng cáo bất kỳ loại không khớp chính xác nào theo yêu cầu. Chúng ta có thể truy cập danh sách các vòng bên trong được cung cấp thông qua typesthuộc tính cho kết quả np.multiplynà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".
Paul Panzer

32

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

Câu trả lời ngắn:

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.

Câu trả lời dài:

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.

Một điểm tổng hợp

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 ).

Các phần mở rộng khác ...

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

... của mặt phẳng phức tạp

Đ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.

Điều đó phụ lục G

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 infnantương tác (về cơ bản là infvượ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ì.

Chúng ta có thể làm tốt hơn không?

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")

Riemann đến giải cứu

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 cprojgộ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.


5
Vâng, bởi vì 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.
cmaster - Khôi phục monica

Cho rằng mã trong nội dung câu hỏi không thực sự được sử dụng ==(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).
Peter Cordes

3
@PeterCordes sẽ gây rắc rối vì sử dụng tọa độ cực, chúng ta có thể xem phép nhân phức tạp như một tỷ lệ và một 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. Theo ý kiến ​​của tôi, đây là một lời giải thích sâu sắc hơn một lời giải thích được chấp nhận, và cũng là một lời giải thích có tiếng vang trong quy tắc nan! = Nan.
Paul Panzer

3
C99 chỉ định rằng các kiểu dấu phẩy động thực không được thăng cấp thành phức khi nhân với một kiểu phức (phần 6.3.1.8 của tiêu chuẩn nháp) và theo như tôi biết thì điều này cũng đúng với std :: complex của C ++. Điều này có nghĩa là 1 là một định danh nhân cho các loại trong các ngôn ngữ đó. Python cũng nên làm như vậy. Tôi sẽ gọi hành vi hiện tại của nó đơn giản là một lỗi.
benrg

2
@PaulPanzer: Tôi không, nhưng khái niệm cơ bản sẽ là một số 0 (mà tôi sẽ gọi là Z) sẽ luôn đề cao x + Z = x và x * Z = Z, và 1 / Z = NaN, một (dương trong hệ thập phân) sẽ giữ nguyên 1 / P = + INF, một (thập phân âm) sẽ giữ nguyên 1 / N = -INF và (thập phân không dấu) sẽ mang lại 1 / U = NaN. Nói chung, xx sẽ là U trừ khi x là một số nguyên thực, trong trường hợp đó, nó sẽ mang lại Z.
supercat

6

Đâ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:

  1. ints / float được thăng cấp thành số phức trong phép nhân
  2. công thức trường học đơn giản được sử dụng , không cung cấp kết quả mong muốn / mong đợi ngay khi liên quan đến số vô hạ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*jkế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 floatkhông được quảng bá complextrong C99 là nhân inf+0.0jvới 1.0hoặc 1.0+0.0jcó 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ó -nanvà 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ỏ.


Tôi biết có rất nhiều mẫu nan bit. Tuy nhiên, tôi không biết một chút dấu hiệu. Nhưng ý tôi là về mặt ngữ nghĩa -nan khác với nan như thế nào? Hay phải nói khác hơn nan là từ nan?
Paul Panzer

@PaulPanzer Đây chỉ là chi tiết triển khai về cách thức printfhoạ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.
ead

À! Giỏi. Đang lo lắng cho một mo rằng tất cả mọi thứ tôi nghĩ tôi biết về fp là không thực sự chính xác ...
Paul Panzer

Xin lỗi vì đã làm phiền nhưng bạn có chắc điều này "không có 1.0 tưởng tượng, tức là 1.0j không giống với 0.0 + 1.0j đối với phép nhân." đúng? Phụ lục rằng G dường như chỉ định một loại thuần túy tưởng tượng (G.2) và cũng có thể quy định như thế nào nó nên được nhân vv (G.5.1)
Paul Panzer

@PaulPanzer Không, cảm ơn bạn đã chỉ ra vấn đề! Là lập trình viên c ++, tôi hầu như chỉ thấy tiêu chuẩn C99 thông qua C ++ - thoáng qua - điều đó khiến tôi suy nghĩ, rằng C đã đi trước một bước ở đây - rõ ràng là bạn đã đúng, một lần nữa.
ead

3

Đị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 1như 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 *1là 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 = nanjnhư inf*0khô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 + iylà 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à:

nhập mô tả hình ảnh ở đây

Giả định 1 là tiêu chuẩn của 1chú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.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.