Math.Pow () được triển khai trong .NET Framework như thế nào?


432

Tôi đang tìm kiếm một cách tiếp cận hiệu quả để tính b (nói a = 2b = 50). Để bắt đầu mọi thứ, tôi quyết định xem xét việc thực hiện Math.Pow()chức năng. Nhưng trong .NET Reflector , tất cả những gì tôi tìm thấy là:

[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]
public static extern double Pow(double x, double y);

Một số tài nguyên trong đó tôi có thể thấy những gì đang diễn ra bên trong khi tôi gọi Math.Pow()chức năng là gì?


15
Giống như một FYI, nếu bạn nhầm lẫn về toàn bộ InternalCallvới một công cụ externsửa đổi (vì chúng có vẻ mâu thuẫn), vui lòng xem câu hỏi (và câu trả lời kết quả) mà tôi đã đăng về điều này rất giống nhau.
CraigTP

6
Đối với một 2^xhoạt động nếu xlà số nguyên kết quả là một hoạt động thay đổi. Vì vậy, có lẽ bạn có thể xây dựng kết quả bằng cách sử dụng một 2số mũ và số mũ của x.
ja72

@SurajJain bình luận của bạn thực sự là một câu hỏi bạn cần đăng riêng.
ja72

@SurajJain Tôi đồng ý với bạn. Tôi không phải là người điều hành nên tôi không thể làm gì nhiều ở đây. Có lẽ câu hỏi downvote có thể được hỏi tại meta.stackoverflow.com
ja72

Câu trả lời:


854

MethodImplOptions.InternalCall

Điều đó có nghĩa là phương thức này thực sự được thực hiện trong CLR, được viết bằng C ++. Trình biên dịch đúng lúc giới thiệu một bảng với các phương thức được triển khai bên trong và biên dịch lệnh gọi trực tiếp đến hàm C ++.

Có một cái nhìn vào mã yêu cầu mã nguồn cho CLR. Bạn có thể lấy nó từ bản phân phối SSCLI20 . Nó được viết xung quanh khung thời gian .NET 2.0, tôi đã tìm thấy các triển khai ở mức độ thấp, dường như Math.Pow()vẫn chính xác phần lớn cho các phiên bản sau của CLR.

Bảng tra cứu được đặt trong clr / src / vm / ecall.cpp. Phần có liên quan đến Math.Pow()như thế này:

FCFuncStart(gMathFuncs)
    FCIntrinsic("Sin", COMDouble::Sin, CORINFO_INTRINSIC_Sin)
    FCIntrinsic("Cos", COMDouble::Cos, CORINFO_INTRINSIC_Cos)
    FCIntrinsic("Sqrt", COMDouble::Sqrt, CORINFO_INTRINSIC_Sqrt)
    FCIntrinsic("Round", COMDouble::Round, CORINFO_INTRINSIC_Round)
    FCIntrinsicSig("Abs", &gsig_SM_Flt_RetFlt, COMDouble::AbsFlt, CORINFO_INTRINSIC_Abs)
    FCIntrinsicSig("Abs", &gsig_SM_Dbl_RetDbl, COMDouble::AbsDbl, CORINFO_INTRINSIC_Abs)
    FCFuncElement("Exp", COMDouble::Exp)
    FCFuncElement("Pow", COMDouble::Pow)
    // etc..
FCFuncEnd()

Tìm kiếm "COMDouble" sẽ đưa bạn đến clr / src / classlibnative / float / comfloat.cpp. Tôi sẽ cung cấp cho bạn mã, chỉ cần có một cái nhìn cho chính mình. Về cơ bản, nó kiểm tra các trường hợp góc, sau đó gọi phiên bản của CRT pow().

Chi tiết triển khai duy nhất khác thú vị là macro FCIntrinsic trong bảng. Đó là một gợi ý rằng jitter có thể thực hiện chức năng như một nội tại. Nói cách khác, thay thế lời gọi hàm bằng một lệnh mã máy dấu phẩy động. Đó không phải là trường hợp Pow(), không có hướng dẫn của FPU cho nó. Nhưng chắc chắn cho các hoạt động đơn giản khác. Đáng chú ý là điều này có thể làm cho phép toán dấu phẩy động trong C # nhanh hơn đáng kể so với cùng mã trong C ++, kiểm tra câu trả lời này để biết lý do tại sao.

Nhân tiện, mã nguồn cho CRT cũng có sẵn nếu bạn có phiên bản đầy đủ của thư mục vc / crt / src của Visual Studio. pow()Mặc dù vậy, bạn sẽ chạm tường , Microsoft đã mua mã đó từ Intel. Làm một công việc tốt hơn các kỹ sư Intel là không thể. Mặc dù danh tính cuốn sách cấp ba của tôi nhanh gấp đôi khi tôi thử nó:

public static double FasterPow(double x, double y) {
    return Math.Exp(y * Math.Log(x));
}

Nhưng không phải là sự thay thế thực sự bởi vì nó tích lũy lỗi từ 3 hoạt động dấu phẩy động và không xử lý các vấn đề miền kỳ lạ mà Pow () gặp phải. Giống như 0 ^ 0 và -Tất cả được nâng lên thành bất kỳ sức mạnh nào.


437
Câu trả lời tuyệt vời, StackOverflow cần nhiều thứ như thế này, thay vì 'Tại sao bạn muốn biết điều đó?' điều đó xảy ra quá thường xuyên
Tom W

16
@Blue - Tôi không biết, rút ​​ngắn niềm vui từ các kỹ sư Intel. Cuốn sách cấp ba của tôi có một vấn đề nâng cao sức mạnh của một tích phân tiêu cực. Pow (x, -2) hoàn toàn có thể tính toán được, Pow (x, -2.1) không xác định. Các vấn đề tên miền là một bitch để giải quyết.
Hans Passant

12
@ BlueRaja-DannyPflughoeft: Rất nhiều nỗ lực được dành để cố gắng đảm bảo rằng các phép toán dấu phẩy động càng gần càng tốt với giá trị được làm tròn chính xác. pownổi tiếng là khó thực hiện chính xác, là một chức năng siêu việt (xem Thế tiến thoái lưỡng nan của Table-Maker ). Nó dễ dàng hơn nhiều với một sức mạnh không thể thiếu.
porges

9
@Hans Passant: Tại sao Pow (x, -2.1) không được xác định? Pow toán học được xác định ở khắp mọi nơi cho tất cả x và y. Bạn có xu hướng nhận được các số phức cho âm x và không nguyên y.
Jules

8
@Jules pow (0, 0) không được xác định.
chạm nổi

110

Câu trả lời của Hans Passant là tuyệt vời, nhưng tôi muốn thêm rằng nếu blà một số nguyên, thì a^bcó thể được tính toán rất hiệu quả với phân tách nhị phân. Đây là phiên bản sửa đổi từ Hacker của Henry Warren :

public static int iexp(int a, uint b) {
    int y = 1;

    while(true) {
        if ((b & 1) != 0) y = a*y;
        b = b >> 1;
        if (b == 0) return y;
        a *= a;
    }    
}

Ông lưu ý rằng thao tác này là tối ưu (thực hiện số lượng tối thiểu các phép toán số học hoặc logic) cho tất cả b <15. Ngoài ra, không có giải pháp nào cho vấn đề chung là tìm một chuỗi các yếu tố tối ưu để tính toán a^bcho bất kỳ b nào ngoài phạm vi rộng Tìm kiếm. Đây là một vấn đề NP-Hard. Vì vậy, về cơ bản điều đó có nghĩa là phân tách nhị phân là tốt như nó được.


11
Thuật toán này (bình phương và nhân ) cũng được áp dụng nếu alà số dấu phẩy động.
CodeInChaos

14
Trong thực tế, có thể làm tốt hơn một chút so với phép nhân vuông và nhân. Ví dụ: chuẩn bị các bảng tra cứu cho các số mũ nhỏ để bạn có thể bình phương nhiều lần và chỉ sau đó nhân hoặc xây dựng chuỗi bổ sung hình vuông được tối ưu hóa cho các số mũ cố định. Loại vấn đề này không thể thiếu đối với các thuật toán mã hóa quan trọng, do đó, có khá nhiều công việc để tối ưu hóa nó. Độ cứng NP chỉ là về sự không triệu chứng trong trường hợp xấu nhất , chúng ta thường có thể tạo ra các giải pháp tối ưu hoặc gần tối ưu cho các trường hợp của vấn đề phát sinh trong thực tế.
CodeInChaos

Văn bản không đề cập đến aviệc là một số nguyên, nhưng mã thì có. Do đó, tôi tự hỏi về tính chính xác của kết quả tính toán "rất hiệu quả" của văn bản.
Andrew Morton

69

Nếu phiên bản C có sẵn miễn phípow là bất kỳ dấu hiệu nào, thì nó không giống bất cứ thứ gì bạn mong đợi. Bạn sẽ không giúp được gì nhiều cho việc tìm phiên bản .NET, bởi vì vấn đề mà bạn đang giải quyết (tức là vấn đề với số nguyên) là các đơn đặt hàng có cường độ đơn giản hơn và có thể được giải quyết trong một vài dòng mã C # theo cấp số nhân bằng thuật toán bình phương .


Cảm ơn câu trả lời của bạn. Liên kết đầu tiên làm tôi ngạc nhiên vì tôi không mong đợi triển khai kỹ thuật lớn như vậy của hàm Pow (). Mặc dù câu trả lời của Hans Passant xác nhận rằng nó cũng giống như vậy trong thế giới .Net. Tôi nghĩ rằng tôi có thể giải quyết vấn đề trong tay bằng cách sử dụng một số kỹ thuật được liệt kê trong liên kết thuật toán bình phương. Cảm ơn một lần nữa.
Pawan Mishra

2
Tôi không tin rằng mã này là hiệu quả. 30 biến cục bộ chỉ nên gập tất cả các thanh ghi. Tôi chỉ cho rằng đó là phiên bản ARM, nhưng trên x86 30 biến cục bộ trong phương thức là tuyệt vời.
Alex Zhukovskiy
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.