Math.Pow () ใช้งานอย่างไรใน. NET Framework


432

ฉันกำลังมองหาวิธีที่มีประสิทธิภาพสำหรับการคำนวณb (พูดa = 2และb = 50) ในการเริ่มต้นสิ่งต่างๆฉันตัดสินใจที่จะดูการใช้งานMath.Pow()ฟังก์ชั่น แต่ใน. NET Reflectorสิ่งที่ฉันพบคือ:

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

มีทรัพยากรอะไรบ้างที่ฉันเห็นว่าเกิดอะไรขึ้นเมื่อฉันเรียกMath.Pow()ใช้ฟังก์ชัน?


15
เช่นเดียวกับ FYI หากคุณสับสนเกี่ยวกับทั้งหมดInternalCallด้วยexternตัวปรับแต่ง (เนื่องจากดูเหมือนจะขัดแย้งกัน) โปรดดูคำถาม (และคำตอบที่เกิดขึ้น)ที่ฉันโพสต์เกี่ยวกับสิ่งเดียวกันนี้มาก
CraigTP

6
สำหรับการ2^xดำเนินการถ้าxเป็นจำนวนเต็มผลลัพธ์จะเป็นการดำเนินการแบบกะ ดังนั้นบางทีคุณอาจจะสร้างผลโดยใช้ mantissa ของและเป็นสัญลักษณ์ของ2 x
ja72

@SurajJain ความคิดเห็นของคุณเป็นคำถามที่คุณต้องโพสต์แยกต่างหาก
ja72

@SurajJain ฉันเห็นด้วยกับคุณ ฉันไม่ได้เป็นผู้ดูแลดังนั้นฉันไม่สามารถทำอะไรได้มากที่นี่ บางทีคำถาม downvote สามารถถามได้ที่ meta.stackoverflow.com
ja72

คำตอบ:


854

MethodImplOptions.InternalCall

นั่นหมายความว่าวิธีการดังกล่าวถูกนำไปใช้จริงใน CLR ซึ่งเขียนใน C ++ คอมไพเลอร์ทันเวลาให้คำปรึกษาตารางด้วยวิธีการที่ดำเนินการภายในและรวบรวมการเรียกไปยังฟังก์ชัน C ++ โดยตรง

การดูรหัสต้องใช้ซอร์สโค้ดสำหรับ CLR คุณจะได้รับจากการจัดจำหน่าย SSCLI20 มันถูกเขียนรอบกรอบเวลา. NET 2.0 ฉันพบการใช้งานระดับต่ำเช่นMath.Pow()ว่ายังคงมีความถูกต้องส่วนใหญ่สำหรับ CLR รุ่นที่ใหม่กว่า

ตารางการค้นหาอยู่ใน clr / src / vm / ecall.cpp ส่วนที่เกี่ยวข้องกับMath.Pow()มีลักษณะดังนี้:

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

การค้นหา "COMDouble" จะนำคุณไปยัง clr / src / classlibnative / float / comfloat.cpp ฉันจะขอรหัสให้คุณดูด้วยตัวคุณเอง มันเป็นพื้นการตรวจสอบสำหรับกรณีมุมแล้วเรียกรุ่น CRT pow()ของ

รายละเอียดการใช้งานอื่น ๆ ที่น่าสนใจคือแมโคร FCIntrinsic ในตาราง นั่นเป็นคำใบ้ที่กระวนกระวายใจอาจใช้ฟังก์ชั่นเป็นที่แท้จริง กล่าวอีกนัยหนึ่งแทนการเรียกใช้ฟังก์ชันด้วยคำสั่งรหัสเครื่องจุดลอย ซึ่งไม่ใช่ในกรณีที่Pow()ไม่มีคำสั่ง FPU สำหรับมัน แต่แน่นอนสำหรับการดำเนินงานที่เรียบง่ายอื่น ๆ สิ่งที่น่าสังเกตคือสิ่งนี้สามารถสร้างเลขทศนิยมในภาษา C # ได้เร็วกว่ารหัสเดียวกันใน C ++ ตรวจสอบคำตอบนี้ด้วยเหตุผลว่าทำไม

อย่างไรก็ตามซอร์สโค้ดสำหรับ CRT ก็มีให้เช่นกันหากคุณมีไดเรกทอรี Visual Studio vc / crt / src เวอร์ชันเต็ม คุณจะชนกำแพงเมื่อpow()ไมโครซอฟท์ซื้อรหัสนั้นจาก Intel ทำงานได้ดีกว่าวิศวกรของ Intel ไม่น่าเป็นไปได้ แม้ว่าตัวตนของหนังสือมัธยมของฉันจะเร็วเป็นสองเท่าเมื่อฉันลองทำ:

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

แต่ไม่ใช่สิ่งทดแทนที่แท้จริงเพราะมันสะสมข้อผิดพลาดจากการดำเนินการจุดลอยตัว 3 แห่งและไม่จัดการกับปัญหาโดเมนแปลก ๆ ที่ Pow () มี เช่น 0 ^ 0 และ - อินฟินิตี้ยกระดับพลังใด ๆ


437
คำตอบที่ดี StackOverflow ต้องการอะไรมากกว่านี้แทนที่จะเป็น 'ทำไมคุณถึงอยากรู้? ที่เกิดขึ้นทั้งหมดบ่อยเกินไป
Tom W

16
@Blue - ไม่รู้สิมันดีแค่ไหนที่ทำให้วิศวกรของ Intel สนุก หนังสือของโรงเรียนมัธยมของฉันมีปัญหาในการยกระดับบางสิ่งให้อยู่ในอำนาจของอินทิกรัลบ Pow (x, -2) สามารถคำนวณได้อย่างสมบูรณ์แบบ Pow (x, -2.1) ไม่ได้ถูกกำหนด ปัญหาโดเมนเป็นเรื่องเลวร้ายที่ต้องจัดการ
ฮันส์ Passant

12
@ BlueRaja-DannyPflughoeft: มีการใช้ความพยายามอย่างมากเพื่อให้แน่ใจว่าการดำเนินการจุดลอยตัวนั้นใกล้เคียงที่สุดกับค่าที่ปัดเศษอย่างถูกต้อง powเป็นการยากที่จะนำไปปฏิบัติอย่างถูกต้องเนื่องจากเป็นฟังก์ชันยอดเยี่ยม (ดูDilemma ของ Table-Maker ) มันง่ายกว่ามากด้วยพลังที่สมบูรณ์
porges

9
@Hans Passant: ทำไม Pow (x, -2.1) ถึงไม่ถูกกำหนด? ธารธารทางคณิตศาสตร์ถูกกำหนดไว้ทุกที่สำหรับ x และ y ทั้งหมด คุณมักจะได้จำนวนเชิงซ้อนสำหรับ x ลบและไม่ใช่จำนวนเต็ม y
Jules

8
ไม่ได้กำหนด @Jules pow (0, 0)
นูน

110

คำตอบของ Hans Passantนั้นยอดเยี่ยม แต่ฉันต้องการเพิ่มว่าถ้าbเป็นจำนวนเต็มa^bสามารถคำนวณได้อย่างมีประสิทธิภาพด้วยการย่อยสลายแบบไบนารี นี่เป็นเวอร์ชั่นที่ดัดแปลงจากDelight's Hacker's ของ Henry :

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;
    }    
}

เขาบันทึกว่าการดำเนินการนี้เหมาะสมที่สุด (ทำเลขคณิตขั้นต่ำหรือการดำเนินการทางตรรกะให้น้อยที่สุด) สำหรับ b <15 ทุกคนและยังไม่มีวิธีการแก้ปัญหาที่ทราบกันทั่วไปในการค้นหาลำดับของปัจจัยที่เหมาะสมที่สุดในการคำนวณa^bสำหรับ b อื่น ๆ ค้นหา. มันเป็นปัญหา NP-Hard นั่นก็หมายความว่าการย่อยสลายแบบไบนารี่ดีเท่าที่จะได้รับ


11
อัลกอริทึมนี้ ( สี่เหลี่ยมจัตุรัสและทวีคูณ ) ก็จะใช้เช่นกันถ้าaเป็นเลขทศนิยม
CodesInChaos

14
ในทางปฏิบัติมันเป็นไปได้ที่จะทำค่อนข้างดีกว่าสแควร์และคูณแบบดั้งเดิม ตัวอย่างเช่นการจัดทำตารางการค้นหาสำหรับ exponents ขนาดเล็กเพื่อให้คุณสามารถจัดตารางหลาย ๆ ครั้งและจากนั้นคูณหรือสร้างการเพิ่มโซ่สี่เหลี่ยมที่เพิ่มประสิทธิภาพสำหรับ exponents คงที่ ปัญหาประเภทนี้เป็นส่วนประกอบสำคัญของอัลกอริธึมการเข้ารหัสที่สำคัญดังนั้นจึงมีงานที่ต้องปรับปรุงเล็กน้อย ความแข็งของเอ็นพีเป็นเพียงเรื่องของซีมอยโทติคที่เลวร้ายที่สุดเท่านั้นเรามักจะสามารถหาวิธีแก้ปัญหาที่เหมาะสมหรือใกล้เคียงที่สุดสำหรับปัญหาที่เกิดขึ้นในทางปฏิบัติ
CodesInChaos

ข้อความไม่ได้กล่าวถึงaการเป็นจำนวนเต็ม แต่รหัสไม่ ด้วยเหตุนี้ฉันจึงสงสัยเกี่ยวกับความถูกต้องของผลลัพธ์ของการคำนวณ "ที่มีประสิทธิภาพ" ของข้อความ
Andrew Morton

69

หากบ่งชี้ใด ๆเวอร์ชัน C ที่ใช้ได้อย่างอิสระpowมันจะไม่เหมือนกับสิ่งที่คุณคาดหวัง มันจะไม่ช่วยคุณได้มากนักในการหาเวอร์ชั่น. NET เพราะปัญหาที่คุณกำลังแก้ไข (เช่นอันที่มีจำนวนเต็ม) เป็นคำสั่งของขนาดที่ง่ายกว่าและสามารถแก้ไขได้ในไม่กี่บรรทัดของรหัส C # ด้วยการยกกำลัง โดยขั้นตอนวิธีการ


ขอบคุณสำหรับคำตอบ. ลิงค์แรกทำให้ฉันประหลาดใจเพราะฉันไม่ได้คาดหวังว่าการใช้งานฟังก์ชั่นทางเทคนิคขนาดใหญ่ของฟังก์ชัน Pow () แม้ว่าคำตอบของฮันส์แพสแทนท์จะยืนยันว่ามันเหมือนกันในโลกของ. Net เช่นกัน ฉันคิดว่าฉันสามารถแก้ปัญหาด้วยการใช้เทคนิคบางอย่างที่ระบุไว้ในลิงค์อัลกอริทึมกำลังสอง ขอบคุณอีกครั้ง.
Pawan Mishra

2
ฉันไม่เชื่อว่ารหัสนี้มีประสิทธิภาพ 30 ตัวแปรท้องถิ่นควรชนทุกการลงทะเบียน ฉันแค่คิดว่ามันเป็นรุ่น ARM แต่ใน x86 30 ตัวแปรท้องถิ่นในวิธีการที่ยอดเยี่ยม
อเล็กซ์ Zhukovskiy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.