เหตุใดจึงไม่มี `int pow (int base, int exponent)` ในไลบรารี C ++ มาตรฐาน


116

ฉันรู้สึกว่าฉันต้องไม่สามารถหามันเจอได้ มีเหตุผลใดที่powฟังก์ชันC ++ ไม่ใช้ฟังก์ชัน "power" สำหรับสิ่งใด ๆ ยกเว้นfloats และdoubles?

ฉันรู้ว่าการใช้งานนั้นไม่สำคัญฉันแค่รู้สึกว่าฉันกำลังทำงานที่ควรอยู่ในไลบรารีมาตรฐาน ฟังก์ชั่นการใช้พลังงานที่แข็งแกร่ง (เช่นจัดการกับการล้นด้วยวิธีที่ชัดเจนและสม่ำเสมอ) ไม่ใช่เรื่องสนุกที่จะเขียน


4
นี่เป็นคำถามที่ดีและฉันไม่คิดว่าคำตอบจะสมเหตุสมผลมากนัก เลขชี้กำลังติดลบไม่ทำงาน? ใช้ ints ที่ไม่ได้ลงชื่อเป็นเลขชี้กำลัง ปัจจัยการผลิตส่วนใหญ่ทำให้ล้น? exp กับ double pow ก็เหมือนกันไม่เห็นมีใครบ่น เหตุใดฟังก์ชันนี้จึงไม่เป็นมาตรฐาน
static_rtti

2
@static_rtti: "เช่นเดียวกับ exp และ double pow" เป็นเท็จทั้งหมด ฉันจะอธิบายอย่างละเอียดในคำตอบของฉัน
Stephen Canon

11
ไลบรารี C ++ มาตรฐานมีdouble pow(int base, int exponent)ตั้งแต่ C ++ 11 (§26.8 [c.math] / 11 bullet point 2)
Cubbi

คุณต้องตัดสินใจระหว่าง 'การนำไปใช้งานเป็นเรื่องเล็กน้อย' และ 'ไม่สนุกที่จะเขียน'
Marquis of Lorne

คำตอบ:


66

ในขณะC++11นี้มีการเพิ่มกรณีพิเศษในชุดฟังก์ชันพลังงาน (และอื่น ๆ ) C++11 [c.math] /11รัฐหลังจากแสดงรายการfloat/double/long doubleโอเวอร์โหลดทั้งหมด(เน้นและถอดความ):

นอกจากนี้จะมี overloads เพิ่มเติมเพียงพอที่จะให้แน่ใจว่าถ้าโต้แย้งใด ๆ ที่สอดคล้องกับdoubleพารามิเตอร์ที่มีประเภทdoubleหรือชนิดจำนวนเต็มแล้วขัดแย้งทั้งหมดสอดคล้องกับพารามิเตอร์ถูกโยนอย่างมีประสิทธิภาพเพื่อdoubledouble

ดังนั้นโดยพื้นฐานแล้วพารามิเตอร์จำนวนเต็มจะได้รับการอัปเกรดเป็นสองเท่าเพื่อดำเนินการ


ก่อนหน้าC++11(ซึ่งเป็นช่วงที่คุณถามคำถาม) ไม่มีการโอเวอร์โหลดจำนวนเต็ม

ตั้งแต่ผมได้รับค่าเชื่อมโยงอย่างใกล้ชิดกับผู้สร้างของCมิได้C++ในวันของการสร้างของพวกเขา ( แต่ผมรู้สึกค่อนข้างเก่า) หรือเป็นส่วนหนึ่งของคณะกรรมการมาตรฐาน ANSI / ISO ที่สร้างมาตรฐานนี้เป็นจำเป็นต้องมีความคิดเห็นในส่วนของฉัน ฉันอยากจะคิดว่ามันเป็นความคิดเห็นที่มีข้อมูลแต่อย่างที่ภรรยาของฉันจะบอกคุณ (บ่อยครั้งและไม่จำเป็นต้องให้กำลังใจมากนัก) ฉันเคยคิดผิดมาก่อน :-)

ข้อสันนิษฐานสำหรับสิ่งที่คุ้มค่ามีดังนี้

ฉันสงสัยว่าสาเหตุที่ pre-ANSI ดั้งเดิมCไม่มีคุณสมบัตินี้เป็นเพราะมันไม่จำเป็นโดยสิ้นเชิง ประการแรกมีวิธีที่ดีอย่างสมบูรณ์ในการสร้างเลขยกกำลังจำนวนเต็ม (ด้วยการเพิ่มเป็นสองเท่าจากนั้นเพียงแค่แปลงกลับเป็นจำนวนเต็มตรวจสอบจำนวนเต็มล้นและน้อยเกินไปก่อนที่จะแปลง)

ประการที่สองสิ่งที่คุณต้องจำไว้อีกประการหนึ่งคือเจตนาดั้งเดิมของCการเป็นภาษาโปรแกรมระบบและเป็นที่น่าสงสัยว่าจุดลอยตัวนั้นเป็นที่ต้องการหรือไม่ในเวทีนั้นหรือไม่

เนื่องจากหนึ่งในกรณีการใช้งานเริ่มต้นคือการเขียนโค้ด UNIX จุดลอยตัวจึงไร้ประโยชน์ BCPL ซึ่งเป็นฐานของ C ก็ไม่ได้ใช้พลังเช่นกัน (ไม่มีจุดลอยตัวเลยจากหน่วยความจำ)

นอกเหนือจากนั้นตัวดำเนินการกำลังหนึ่งอาจเป็นตัวดำเนินการไบนารีมากกว่าการเรียกไลบรารี คุณไม่ต้องเพิ่มจำนวนเต็มสองจำนวนx = add (y, z)แต่มีx = y + z- ส่วนหนึ่งของภาษาที่เหมาะสมแทนที่จะเป็นไลบรารี

ประการที่สามเนื่องจากการใช้งานอินทิกรัลพาวเวอร์เป็นเรื่องเล็กน้อยจึงเกือบจะแน่นอนว่าผู้พัฒนาภาษาจะใช้เวลาของพวกเขาในการจัดหาสิ่งที่เป็นประโยชน์มากขึ้นได้ดีกว่า (ดูความคิดเห็นด้านล่างเกี่ยวกับค่าเสียโอกาส)

ที่เกี่ยวข้องกับต้นฉบับC++ด้วย เนื่องจากการดำเนินงานได้อย่างมีประสิทธิภาพเดิมเป็นเพียงผู้แปลซึ่งผลิตรหัสก็ดำเนินการไปหลายของคุณลักษณะของC Cความตั้งใจเดิมคือ C-with-Class ไม่ใช่ C-with-Class-plus-a-bit-bit-extra-math-stuff

เหตุใดจึงไม่เคยเพิ่มมาตรฐานนี้มาก่อนC++11คุณต้องจำไว้ว่าหน่วยงานกำหนดมาตรฐานมีแนวทางเฉพาะที่ต้องปฏิบัติตาม ตัวอย่างเช่น ANSI Cได้รับมอบหมายให้เขียนโค้ดการปฏิบัติที่มีอยู่โดยเฉพาะไม่ใช่เพื่อสร้างภาษาใหม่ มิฉะนั้นพวกเขาอาจจะบ้าไปแล้วและให้ Ada กับเรา :-)

การทำซ้ำในภายหลังของมาตรฐานนั้นยังมีแนวทางที่เฉพาะเจาะจงและสามารถพบได้ในเอกสารเหตุผล (เหตุผลว่าทำไมคณะกรรมการจึงตัดสินใจบางอย่างไม่ใช่เหตุผลสำหรับภาษานั้น ๆ )

ตัวอย่างเช่นC99เอกสารเหตุผลโดยเฉพาะC89นำหลักการชี้นำสองประการมาใช้ซึ่ง จำกัด สิ่งที่สามารถเพิ่มได้:

  • ใช้ภาษาให้เล็กและเรียบง่าย
  • ให้วิธีเดียวในการดำเนินการ

มีการวางแนวทาง (ไม่จำเป็นต้องเป็นแนวทางเฉพาะเหล่านั้น) สำหรับแต่ละคณะทำงานและด้วยเหตุนี้จึง จำกัดC++คณะกรรมการ (และกลุ่ม ISO อื่น ๆ ทั้งหมด) ด้วย

นอกจากนี้หน่วยงานกำหนดมาตรฐานยังตระหนักดีว่ามีค่าเสียโอกาส (คำศัพท์ทางเศรษฐกิจหมายถึงสิ่งที่คุณต้องละทิ้งสำหรับการตัดสินใจ) สำหรับการตัดสินใจทุกครั้งที่พวกเขาทำ ตัวอย่างเช่นค่าเสียโอกาสในการซื้อเครื่องเกม uber มูลค่า 10,000 เหรียญนั้นเป็นความสัมพันธ์ที่จริงใจ (หรืออาจเป็นความสัมพันธ์ทั้งหมด ) กับอีกครึ่งหนึ่งของคุณเป็นเวลาประมาณหกเดือน

Eric Gunnerson อธิบายสิ่งนี้ได้ดีพร้อมกับคำอธิบาย -100 คะแนนของเขาว่าทำไมสิ่งต่างๆจึงไม่ถูกเพิ่มลงในผลิตภัณฑ์ของ Microsoft เสมอไปโดยทั่วไปแล้วคุณลักษณะจะเริ่มต้น 100 คะแนนในหลุมดังนั้นจึงต้องเพิ่มมูลค่าเล็กน้อยที่จะได้รับการพิจารณา

กล่าวอีกนัยหนึ่งคุณอยากจะมีตัวดำเนินการด้านพลังงานแบบบูรณาการ (ซึ่งจริงๆแล้ว coder ที่มีครึ่งเดียวสามารถแส้ได้ภายในสิบนาที) หรือเพิ่มมัลติเธรดลงในมาตรฐานหรือไม่? สำหรับตัวฉันเองฉันต้องการที่จะมีอย่างหลังและไม่ต้องยุ่งเกี่ยวกับการใช้งานที่แตกต่างกันภายใต้ UNIX และ Windows

ฉันต้องการดูคอลเลกชันหลายพันหลายพันรายการในห้องสมุดมาตรฐาน (แฮชต้นไม้ต้นไม้สีแดงดำพจนานุกรมแผนที่ตามอำเภอใจและอื่น ๆ ) เช่นกัน แต่ตามเหตุผล:

มาตรฐานคือสนธิสัญญาระหว่างผู้ใช้และโปรแกรมเมอร์

และจำนวนผู้ดำเนินการในหน่วยงานมาตรฐานก็มีมากกว่าจำนวนโปรแกรมเมอร์ (หรืออย่างน้อยโปรแกรมเมอร์ที่ไม่เข้าใจต้นทุนค่าเสียโอกาส) หากมีการเพิ่มสิ่งต่างๆทั้งหมดนั้นมาตรฐานถัดไปC++จะเป็นC++215xและอาจถูกนำไปใช้อย่างสมบูรณ์โดยนักพัฒนาคอมไพเลอร์สามร้อยปีหลังจากนั้น

อย่างไรก็ตามนั่นคือความคิด (ค่อนข้างใหญ่โต) ของฉันเกี่ยวกับเรื่องนี้ หากมีเพียงคะแนนโหวตเท่านั้นที่ได้รับจากปริมาณมากกว่าคุณภาพในไม่ช้าฉันจะเป่าคนอื่น ๆ ออกจากน้ำ ขอบคุณสำหรับการฟัง :-)


2
FWIW ฉันไม่คิดว่า C ++ ตาม "ให้วิธีเดียวในการดำเนินการ" เป็นข้อ จำกัด ถูกต้องเพราะตัวอย่างเช่นto_stringแลมบ์ดาเป็นสิ่งอำนวยความสะดวกสำหรับสิ่งต่างๆที่คุณทำได้อยู่แล้ว ฉันคิดว่าเราสามารถตีความ "วิธีเดียวในการดำเนินการ" อย่างหลวม ๆเพื่อให้ทั้งสองอย่างนี้และในขณะเดียวกันก็อนุญาตให้มีการทำงานซ้ำซ้อนเกือบทุกอย่างที่ใคร ๆ ก็สามารถจินตนาการได้โดยพูดว่า "aha! no! เพราะความสะดวกสบายทำให้ มันเป็นการดำเนินการที่แตกต่างกันเล็กน้อยจากทางเลือกที่เทียบเท่ากันอย่างแม่นยำ แต่ยืดยาวกว่า! ". ซึ่งเป็นเรื่องจริงของ lambdas
Steve Jessop

@ สตีฟใช่นั่นเป็นคำพูดที่ไม่ดีในส่วนของฉัน มีความถูกต้องมากกว่าที่จะกล่าวว่ามีแนวทางสำหรับคณะกรรมการแต่ละชุดแทนที่จะเป็นคณะกรรมการทั้งหมดที่ปฏิบัติตามแนวทางเดียวกัน ปรับคำตอบ clarifyl
paxdiablo

2
เพียงประเด็นเดียว (จากสองสามข้อ): "ลิงรหัสใด ๆ สามารถชักได้ในสิบนาที" แน่นอนและถ้าลิงรหัส 100 ตัว (คำดูถูกที่ดี BTW) ทำอย่างนั้นในแต่ละปี (อาจเป็นค่าประมาณที่ต่ำ) เราเสียเวลา 1,000 นาที มีประสิทธิภาพมากคุณไม่คิดเหรอ?
Jürgen A. Erhard

1
@ เจอร์เก้นมันไม่ได้หมายความว่าจะดูถูก (เนื่องจากฉันไม่ได้กำหนดป้ายกำกับให้ใครเจาะจง) มันเป็นเพียงการบ่งบอกว่าpowไม่ต้องใช้ทักษะอะไรมาก แน่นอนว่าฉันควรมีมาตรฐานให้บางสิ่งบางอย่างที่จะต้องมีจำนวนมากของทักษะและส่งผลให้เกิดการสูญเสียให้ห่างไกลมากขึ้นนาทีถ้าความพยายามที่จะต้องมีการทำซ้ำ
paxdiablo

2
@ eharo2 ให้แทนที่ "half coder" ในข้อความปัจจุบันด้วย "code monkey" ฉันไม่คิดว่ามันเป็นการดูถูกเช่นกัน แต่ฉันคิดว่าดีที่สุดที่จะระมัดระวังและพูดตามตรงว่าคำพูดในปัจจุบันข้ามความคิดเดียวกันไปแล้ว
paxdiablo

41

สำหรับอินทิกรัลประเภทความกว้างคงที่คู่อินพุตที่เป็นไปได้เกือบทั้งหมดจะล้นประเภท อะไรคือการใช้ฟังก์ชันมาตรฐานที่ไม่ได้ให้ผลลัพธ์ที่เป็นประโยชน์สำหรับอินพุตที่เป็นไปได้ส่วนใหญ่

คุณจำเป็นต้องมีประเภทจำนวนเต็มจำนวนมากเพื่อให้ฟังก์ชันมีประโยชน์และไลบรารีจำนวนเต็มขนาดใหญ่ส่วนใหญ่มีฟังก์ชัน


แก้ไข:ในความคิดเห็นเกี่ยวกับคำถาม static_rtti เขียนว่า "อินพุตส่วนใหญ่ทำให้ล้นหรือไม่เช่นเดียวกับ exp และ double pow ฉันไม่เห็นใครบ่น" สิ่งนี้ไม่ถูกต้อง

ลองออกจากกันexpเนื่องจากว่าของข้างจุด (แม้ว่ามันจะจริงจะทำให้กรณีของฉันแข็งแกร่ง) double pow(double x, double y)และมุ่งเน้นไปที่ ฟังก์ชันนี้มีประโยชน์สำหรับคู่ (x, y) สำหรับส่วนใดบ้าง (กล่าวคือไม่ใช่แค่ล้นหรือน้อยเกินไป)

ที่จริงฉันจะเน้นเฉพาะส่วนเล็ก ๆ ของคู่อินพุตที่powเหมาะสมเพราะนั่นจะเพียงพอที่จะพิสูจน์จุดของฉัน: ถ้า x เป็นบวกและ | y | <= 1 แล้วpowไม่ล้นหรือน้อยเกินไป ซึ่งประกอบด้วยเกือบหนึ่งในสี่ของคู่จุดลอยตัวทั้งหมด (ครึ่งหนึ่งของจำนวนจุดลอยตัวที่ไม่ใช่ NaN เป็นค่าบวกและน้อยกว่าครึ่งหนึ่งของจำนวนจุดลอยตัวที่ไม่ใช่ NaN ที่มีขนาดน้อยกว่า 1) เห็นได้ชัดว่ามีจำนวนมากของคู่ป้อนข้อมูลอื่น ๆ ที่powก่อให้เกิดผลลัพธ์ที่มีประโยชน์ แต่เราได้ตรวจสอบว่ามันเป็นอย่างน้อยหนึ่งในสี่ของปัจจัยการผลิตทั้งหมด

ตอนนี้เรามาดูที่ฟังก์ชันกำลังเลขจำนวนเต็มแบบความกว้างคงที่ (เช่นไม่ใช่บิกนัม) ปัจจัยการผลิตส่วนใดไม่เพียงแค่ล้น? ในการเพิ่มจำนวนคู่อินพุตที่มีความหมายสูงสุดควรลงนามฐานและเลขชี้กำลังไม่ได้ลงนาม สมมติว่าฐานและเลขชี้กำลังnกว้างทั้งสองบิต เราสามารถเชื่อมโยงกับส่วนของปัจจัยการผลิตที่มีความหมายได้อย่างง่ายดาย:

  • ถ้าเลขชี้กำลัง 0 หรือ 1 ฐานใด ๆ ก็มีความหมาย
  • ถ้าเลขชี้กำลังเป็น 2 หรือมากกว่าจะไม่มีฐานใดที่ใหญ่กว่า 2 ^ (n / 2) ให้ผลลัพธ์ที่มีความหมาย

ดังนั้นของคู่อินพุต 2 ^ (2n) น้อยกว่า 2 ^ (n + 1) + 2 ^ (3n / 2) ให้ผลลัพธ์ที่มีความหมาย หากเราดูสิ่งที่น่าจะเป็นการใช้งานทั่วไปคือจำนวนเต็ม 32 บิตนั่นหมายความว่าบางสิ่งที่อยู่ในลำดับ 1/1000 ของคู่อินพุตหนึ่งเปอร์เซ็นต์จะไม่ล้น


8
อย่างไรก็ตามทั้งหมดนี้คือการสงสัย เพียงเพราะฟังก์ชันไม่ถูกต้องสำหรับอินพุตบางส่วนหรือจำนวนมากไม่ได้ทำให้มีประโยชน์น้อยลง
static_rtti

2
@static_rtti: pow(x,y)ไม่เกินศูนย์สำหรับ x ใด ๆ ถ้า | y | <= 1 มีแถบอินพุตที่แคบมาก (x มาก, y มากเกือบ -1) ซึ่งเกิด underflow ขึ้น แต่ผลลัพธ์ยังคงมีความหมายในช่วงนั้น
Stephen Canon

2
เมื่อคิดให้มากขึ้นฉันก็เห็นด้วยกับสิ่งที่มากเกินไป ฉันยังคิดว่าสิ่งนี้ไม่เกี่ยวข้องกับคำถามนี้
static_rtti

7
@ybungalobill: ทำไมถึงเลือกเหตุผลนั้นล่ะ? ส่วนตัวฉันชอบประโยชน์สำหรับปัญหาและโปรแกรมเมอร์จำนวนมากความเป็นไปได้ที่จะสร้างเวอร์ชันที่ปรับให้เหมาะสมกับฮาร์แวร์ซึ่งเร็วกว่าการใช้งานที่ไร้เดียงสาโปรแกรมเมอร์ส่วนใหญ่อาจจะเขียนและอื่น ๆ เกณฑ์ของคุณดูเหมือนเป็นไปตามอำเภอใจโดยสิ้นเชิงและตรงไปตรงมาค่อนข้างไม่มีจุดหมาย
static_rtti

5
@StephenCanon: ในด้านสว่างอาร์กิวเมนต์ของคุณแสดงให้เห็นว่าการนำจำนวนเต็มไปใช้งานที่ถูกต้องและเหมาะสมอย่างเห็นได้ชัดpowนั้นเป็นเพียงตารางการค้นหาเล็ก ๆ :-)
R .. GitHub STOP HELPING ICE

11

เนื่องจากไม่มีทางที่จะแสดงพลังจำนวนเต็มทั้งหมดใน int ได้:

>>> print 2**-4
0.0625

3
สำหรับประเภทตัวเลขที่มีขนาด จำกัด ไม่มีทางที่จะแสดงพลังทั้งหมดของประเภทนั้นภายในประเภทนั้นเนื่องจากมีการล้น แต่ประเด็นของคุณเกี่ยวกับพลังเชิงลบนั้นใช้ได้มากกว่า
Chris Lutz

1
ฉันเห็นเลขชี้กำลังเป็นลบเป็นสิ่งที่การใช้งานมาตรฐานสามารถจัดการได้ไม่ว่าจะโดยการใช้ int ที่ไม่ได้ลงชื่อเป็นเลขชี้กำลังหรือส่งกลับศูนย์เมื่อเลขชี้กำลังเป็นลบถูกจัดเตรียมเป็นอินพุตและ int คือผลลัพธ์ที่คาดหวัง
Dan O

3
หรือแยกกันint pow(int base, unsigned int exponent)และfloat pow(int base, int exponent)
Ponkadoodle

4
พวกเขาสามารถประกาศได้ว่าเป็นพฤติกรรมที่ไม่ได้กำหนดให้ส่งจำนวนเต็มลบ
Johannes Schaub - litb

2
สำหรับการใช้งานสมัยใหม่ทั้งหมดสิ่งที่อยู่นอกเหนือไปจากint pow(int base, unsigned char exponent)นั้นก็ค่อนข้างไร้ประโยชน์อยู่ดี ฐานคือ 0 หรือ 1 และเลขชี้กำลังไม่สำคัญมันคือ -1 ซึ่งในกรณีนี้จะมีเพียงบิตสุดท้ายของเลขชี้กำลังเท่านั้นที่มีความสำคัญหรือbase >1 || base< -1ในกรณีใดexponent<256ที่มีการลงโทษของการล้น
MSalters

9

นั่นเป็นคำถามที่น่าสนใจจริงๆ อาร์กิวเมนต์หนึ่งที่ฉันไม่พบในการสนทนาคือการขาดค่าตอบแทนที่ชัดเจนสำหรับอาร์กิวเมนต์ ลองนับวิธีที่int pow_int(int, int)ฟังก์ชันhypthetical อาจล้มเหลว

  1. ล้น
  2. ไม่ได้กำหนดผลลัพธ์ pow_int(0,0)
  3. ไม่สามารถแสดงผลลัพธ์ได้ pow_int(2,-1)

ฟังก์ชันนี้มีโหมดความล้มเหลวอย่างน้อย 2 โหมด จำนวนเต็มไม่สามารถแสดงถึงค่าเหล่านี้ได้พฤติกรรมของฟังก์ชันในกรณีเหล่านี้จำเป็นต้องกำหนดโดยมาตรฐาน - และโปรแกรมเมอร์จะต้องทราบว่าฟังก์ชันจัดการกับกรณีเหล่านี้อย่างไร

โดยรวมแล้วการออกจากฟังก์ชั่นนี้ดูเหมือนเป็นตัวเลือกเดียวที่เหมาะสม โปรแกรมเมอร์สามารถใช้เวอร์ชันทศนิยมที่มีการรายงานข้อผิดพลาดทั้งหมดแทน


แต่สองกรณีแรกจะไม่ใช้กับpowระหว่างลอยด้วยหรือไม่? ใช้การลอยตัวขนาดใหญ่สองครั้งยกหนึ่งขึ้นสู่พลังของอีกอันหนึ่งและคุณจะมีโอเวอร์โฟล และpow(0.0, 0.0)จะทำให้เกิดปัญหาเดียวกันกับจุดที่ 2 ของคุณ จุดที่ 3 ของคุณคือความแตกต่างที่แท้จริงเพียงอย่างเดียวระหว่างการใช้ฟังก์ชันกำลังสำหรับจำนวนเต็มเทียบกับโฟล
numbermaniac

7

คำตอบสั้น ๆ :

ความเชี่ยวชาญของpow(x, n)ไปยังที่ที่nเป็นจำนวนธรรมชาติมักจะเป็นประโยชน์สำหรับเวลาการทำงาน แต่ทั่วไปของไลบรารีมาตรฐานpow()ยังคงใช้งานได้ดี ( น่าประหลาดใจ! ) สำหรับจุดประสงค์นี้และเป็นสิ่งสำคัญอย่างยิ่งที่จะรวมไว้ในไลบรารี C มาตรฐานให้น้อยที่สุดเท่าที่จะเป็นไปได้เพื่อให้สามารถพกพาได้และใช้งานง่ายที่สุด ในทางกลับกันนั่นไม่ได้หยุดอยู่ในไลบรารีมาตรฐาน C ++ หรือ STL ซึ่งฉันค่อนข้างมั่นใจว่าไม่มีใครวางแผนที่จะใช้ในแพลตฟอร์มแบบฝังบางประเภท

ตอนนี้สำหรับคำตอบยาว

pow(x, n)สามารถทำได้เร็วกว่ามากในหลาย ๆ กรณีโดยเชี่ยวชาญnกับจำนวนธรรมชาติ ฉันต้องใช้ฟังก์ชันนี้ของฉันเองสำหรับเกือบทุกโปรแกรมที่ฉันเขียน (แต่ฉันเขียนโปรแกรมทางคณิตศาสตร์จำนวนมากในภาษา C) การดำเนินการเฉพาะสามารถทำได้O(log(n))ทันเวลา แต่เมื่อnมีขนาดเล็กเวอร์ชันเชิงเส้นที่เรียบง่ายจะเร็วกว่า นี่คือการใช้งานทั้งสองอย่าง:


    // Computes x^n, where n is a natural number.
    double pown(double x, unsigned n)
    {
        double y = 1;
        // n = 2*d + r. x^n = (x^2)^d * x^r.
        unsigned d = n >> 1;
        unsigned r = n & 1;
        double x_2_d = d == 0? 1 : pown(x*x, d);
        double x_r = r == 0? 1 : x;
        return x_2_d*x_r;
    }
    // The linear implementation.
    double pown_l(double x, unsigned n)
    {
        double y = 1;
        for (unsigned i = 0; i < n; i++)
            y *= x;
        return y;
    }

(ฉันทิ้งไว้xและค่าส่งคืนเป็นสองเท่าเพราะผลลัพธ์ของpow(double x, unsigned n)จะพอดีกับสองเท่าบ่อยเท่าที่pow(double, double)จะทำได้)

(ใช่pownเป็นแบบวนซ้ำ แต่การทำลายสแต็กนั้นเป็นไปไม่ได้อย่างแน่นอนเนื่องจากขนาดสแต็กสูงสุดจะเท่ากับโดยประมาณlog_2(n)และnเป็นจำนวนเต็มหากnเป็นจำนวนเต็ม 64 บิตจะทำให้คุณมีขนาดสแต็กสูงสุดประมาณ 64 ไม่มีฮาร์ดแวร์ใดที่มีความสูงมากเช่นนี้ ข้อ จำกัด ของหน่วยความจำยกเว้น PIC ที่หลบหลีกบางตัวที่มีสแต็กฮาร์ดแวร์ที่ไปเพียง 3 ถึง 8 ฟังก์ชั่นเรียกแบบลึก)

สำหรับประสิทธิภาพการทำงานคุณจะประหลาดใจกับสิ่งที่หลากหลายสวนpow(double, double)มีความสามารถใน ฉันทดสอบการทำซ้ำร้อยล้านครั้งบน IBM Thinkpad อายุ 5 ปีของฉันด้วยxจำนวนการวนซ้ำและnเท่ากับ 10 ในสถานการณ์นี้pown_lได้รับรางวัล glibc pow()ใช้เวลา 12.0 วินาทีของผู้ใช้ใช้เวลาpown7.4 วินาทีของผู้ใช้และpown_lใช้เวลาเพียง 6.5 วินาทีของผู้ใช้ ไม่น่าแปลกใจเลย เราคาดหวังสิ่งนี้ไม่มากก็น้อย

จากนั้นฉันปล่อยให้xคงที่ (ฉันตั้งค่าเป็น 2.5) และฉันวนซ้ำnจาก 0 ถึง 19 เป็นร้อยล้านครั้ง คราวนี้ไม่คาดคิดเลยทีเดียว glibc powชนะและถล่มทลาย! ใช้เวลาเพียง 2.0 วินาทีของผู้ใช้ ของฉันpownใช้เวลา 9.6 วินาทีและpown_lใช้เวลา 12.2 วินาที เกิดอะไรขึ้นที่นี่? ฉันได้ทำการทดสอบอีกครั้งเพื่อหาคำตอบ

ฉันทำแบบเดียวกับข้างบนโดยมีxค่าเท่ากับหนึ่งล้าน ครั้งนี้pownชนะที่ 9.6 วินาที pown_lใช้เวลา 12.2 วินาทีและ glibc pow ใช้เวลา 16.3 วินาที ตอนนี้ชัดเจน! glibc powทำงานได้ดีกว่าสามเมื่อxต่ำ แต่แย่ที่สุดเมื่อxสูง เมื่อxใดสูงpown_lทำงานได้ดีที่สุดเมื่อnต่ำและpownทำงานได้ดีที่สุดเมื่อxสูง

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

double pown_auto(double x, unsigned n, double x_expected, unsigned n_expected) {
    if (x_expected < x_threshold)
        return pow(x, n);
    if (n_expected < n_threshold)
        return pown_l(x, n);
    return pown(x, n);
}

ตราบใดที่ค่าคงที่x_expectedและn_expectedเป็นค่าคงที่ตัดสินใจในเวลาคอมไพล์พร้อมกับข้อควรระวังอื่น ๆ คอมไพเลอร์การเพิ่มประสิทธิภาพที่คุ้มค่ากับเกลือจะลบการpown_autoเรียกใช้ฟังก์ชันทั้งหมดโดยอัตโนมัติและแทนที่ด้วยตัวเลือกที่เหมาะสมของอัลกอริทึมทั้งสาม (ตอนนี้ถ้าคุณจะพยายามใช้สิ่งนี้จริงๆคุณอาจจะต้องเล่นกับมันเล็กน้อยเพราะฉันไม่ได้พยายามรวบรวมสิ่งที่ฉันเขียนไว้ข้างต้นอย่างแน่นอน;))

ในทางกลับกัน glibc pow ทำงานได้และ glibc ก็ใหญ่พออยู่แล้ว มาตรฐาน C ควรเป็นแบบพกพารวมถึงอุปกรณ์ฝังตัวต่างๆ (ในความเป็นจริงนักพัฒนาแบบฝังทุกที่โดยทั่วไปยอมรับว่า glibc มีขนาดใหญ่เกินไปสำหรับพวกเขาแล้ว) และไม่สามารถพกพาได้หากสำหรับฟังก์ชันคณิตศาสตร์ง่ายๆทุกอย่างจำเป็นต้องรวมทุกๆ อัลกอริทึมทางเลือกที่อาจใช้งานได้ นั่นเป็นเหตุผลว่าทำไมจึงไม่อยู่ในมาตรฐาน C

เชิงอรรถ: ในการทดสอบประสิทธิภาพด้านเวลาฉันให้แฟล็กการเพิ่มประสิทธิภาพที่ค่อนข้างใจกว้าง ( -s -O2) ซึ่งน่าจะเทียบได้กับถ้าไม่แย่ไปกว่านั้นสิ่งที่น่าจะใช้ในการรวบรวม glibc ในระบบของฉัน (archlinux) ดังนั้นผลลัพธ์อาจเป็นไปได้ ธรรม สำหรับการทดสอบอย่างเข้มงวดมากขึ้นผมจะต้องรวบรวม glibc ตัวเองและฉันreeeallyไม่รู้สึกเหมือนทำที่ ฉันเคยใช้ Gentoo ดังนั้นฉันจำได้ว่าต้องใช้เวลานานแค่ไหนแม้ว่างานจะเป็นแบบอัตโนมัติก็ตาม ผลลัพธ์เป็นข้อสรุป (หรือค่อนข้างสรุปไม่ได้) เพียงพอสำหรับฉัน คุณสามารถทำสิ่งนี้ได้ด้วยตัวเอง

รอบโบนัส: ความเชี่ยวชาญของpow(x, n)จำนวนเต็มทั้งหมดเป็นเครื่องมือหากต้องการเอาต์พุตจำนวนเต็มที่แน่นอนซึ่งจะเกิดขึ้น พิจารณาจัดสรรหน่วยความจำสำหรับอาร์เรย์ N มิติที่มีองค์ประกอบ p ^ N การปิด p ^ N ทีละรายการจะส่งผลให้เกิดความผิดพลาดแบบสุ่ม


ฉันเดาว่าถ้าคุณกำจัดการเรียกซ้ำคุณจะประหยัดเวลาที่ต้องใช้ในการจัดสรรกองซ้อน และใช่เรามีสถานการณ์ที่พาวทำให้ทุกอย่างช้าลงและเราต้องใช้พาวของเราเอง
Sambatyon

"ไม่มีใครมีข้อ จำกัด ด้านความจำที่รุนแรงเช่นนี้" เป็นเท็จ PIC มักจะมี call stack ที่ จำกัด ได้สูงสุด 3 (เช่น PIC10F200) ถึง 8 (ตัวอย่างคือ 16F722A) การโทร (PIC ใช้สแต็กฮาร์ดแวร์สำหรับการเรียกใช้ฟังก์ชัน)
12431234123412341234123

โอ้ผู้ชายที่โหดฮ่า ๆ ตกลงดังนั้นมันจะใช้ไม่ได้กับ PIC เหล่านั้น
enigmaticPhysicist

สำหรับฐานจำนวนเต็มและกำลังเช่นเดียวกับคำถามที่ถามเกี่ยวกับคอมไพเลอร์ (gcc และ clang) จะสร้างลูปแบบไม่มีสาขาได้อย่างง่ายดายจากการใช้งานแบบวนซ้ำ (แทนการเรียกซ้ำ) นี้หลีกเลี่ยง mispredicts nสาขาจากบิตของแต่ละ godbolt.org/z/L9Kb98 gcc และ clang ล้มเหลวในการเพิ่มประสิทธิภาพคำจำกัดความแบบเรียกซ้ำของคุณให้เป็นลูปง่ายๆและทำแตกแขนงในแต่ละบิตของn. (สำหรับpown_iter(double,unsigned)พวกเขายังคงแตกแขนง แต่การใช้งาน SSE2 หรือ SSE4.1 แบบไม่มีสาขาควรเป็นไปได้ใน x86 asm หรือด้วย C intrinsics แต่ก็ยังดีกว่าการเรียกซ้ำ)
Peter Cordes

อึตอนนี้ฉันต้องทำเกณฑ์มาตรฐานอีกครั้งด้วยเวอร์ชันที่ใช้ลูปเพื่อให้แน่ใจ ฉันจะคิดเกี่ยวกับมัน
enigmaticPhysicist

6

เหตุผลหนึ่งที่ทำให้ C ++ ไม่มีโอเวอร์โหลดเพิ่มเติมคือต้องเข้ากันได้กับ C

C ++ 98 มีฟังก์ชันเหมือนdouble pow(double, int)แต่สิ่งเหล่านี้ถูกลบออกใน C ++ 11 พร้อมกับอาร์กิวเมนต์ที่ C99 ไม่ได้รวมไว้

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3286.html#550

การได้ผลลัพธ์ที่แม่นยำกว่าเล็กน้อยยังหมายถึงการได้ผลลัพธ์ที่แตกต่างกันเล็กน้อย


3

โลกมีการพัฒนาอย่างต่อเนื่องและภาษาโปรแกรมก็เช่นกัน หนึ่งในสี่ของ C TR ทศนิยม<math.h> ¹เพิ่มฟังก์ชั่นบางอย่างมากขึ้นในการ สองตระกูลของฟังก์ชันเหล่านี้อาจเป็นที่สนใจสำหรับคำถามนี้:

  • pownฟังก์ชั่นที่ใช้เวลาจำนวนจุดลอยและintmax_tสัญลักษณ์
  • powrฟังก์ชั่นที่ใช้เวลาสองลอยหมายเลขจุด ( xและy) และคำนวณxสู่อำนาจด้วยสูตรyexp(y*log(x))

ดูเหมือนว่าในที่สุดพวกมาตรฐานจะเห็นว่าคุณสมบัติเหล่านี้มีประโยชน์มากพอที่จะรวมอยู่ในไลบรารีมาตรฐาน อย่างไรก็ตามเหตุผลก็คือฟังก์ชันเหล่านี้แนะนำโดยมาตรฐาน ISO / IEC / IEEE 60559: 2011สำหรับเลขทศนิยมฐานสองและทศนิยม ฉันไม่สามารถพูดว่าสิ่งที่ "มาตรฐาน" ตามมาในเวลาที่ C89 แต่วิวัฒนาการของอนาคตของ<math.h>อาจจะได้รับอิทธิพลอย่างมากจากวิวัฒนาการในอนาคตของมาตรฐาน ISO / IEC / IEEE 60559มาตรฐาน

โปรดทราบว่าส่วนที่สี่ของทศนิยม TR จะไม่รวมอยู่ใน C2x (การแก้ไข C หลักถัดไป) และอาจรวมไว้ในภายหลังเป็นคุณสมบัติเสริม ฉันไม่รู้ว่าจะรวมส่วนนี้ของ TR ไว้ในการแก้ไข C ++ ในอนาคต


¹คุณสามารถดูเอกสารที่อยู่ระหว่างดำเนินการได้ที่นี่


มีการใช้งานที่เป็นไปได้หรือไม่ซึ่งการใช้pownด้วยเลขชี้กำลังที่มากกว่าที่LONG_MAXควรจะให้คุณค่าที่แตกต่างจากการใช้LONG_MAXหรือเมื่อค่าที่น้อยกว่าLONG_MINควรให้มูลค่าที่แตกต่างไปจากLONG_MINนี้ ฉันสงสัยว่าจะได้รับประโยชน์อะไรจากการใช้intmax_tเลขชี้กำลัง?
supercat

@supercat ไม่มีความคิดขออภัย
Morwenn

มันอาจจะคุ้มค่าที่จะพูดถึงว่าเมื่อมองไปที่ Standard ดูเหมือนว่าจะกำหนดฟังก์ชัน "crpown" ที่เป็นทางเลือกซึ่งหากกำหนดไว้ก็คือ "pown" เวอร์ชันที่ปัดเศษอย่างถูกต้อง มาตรฐานไม่ได้ระบุระดับความถูกต้องที่ต้องการ การใช้ "pown" ที่รวดเร็วและแม่นยำปานกลางเป็นเรื่องง่าย แต่การปัดเศษที่ถูกต้องในทุกกรณีมีแนวโน้มที่จะมีราคาแพงกว่ามาก
supercat

2

อาจเป็นเพราะ ALU ของโปรเซสเซอร์ไม่ได้ใช้ฟังก์ชันดังกล่าวสำหรับจำนวนเต็ม แต่มีคำสั่ง FPU (ดังที่ Stephen ชี้ให้เห็นว่ามันเป็นคู่) ดังนั้นมันจึงเร็วกว่าที่จะร่ายเป็นสองเท่าเรียกพาวด้วยคู่ผสมจากนั้นทดสอบการโอเวอร์โฟลและเหวี่ยงกลับแทนที่จะใช้มันโดยใช้เลขคณิตจำนวนเต็ม

(ประการหนึ่งลอการิทึมลดอำนาจในการคูณ แต่ลอการิทึมของจำนวนเต็มสูญเสียความแม่นยำอย่างมากสำหรับอินพุตส่วนใหญ่)

Stephen พูดถูกว่าในโปรเซสเซอร์สมัยใหม่สิ่งนี้ไม่เป็นความจริงอีกต่อไป แต่มาตรฐาน C เมื่อเลือกฟังก์ชันคณิตศาสตร์ (C ++ เพิ่งใช้ฟังก์ชัน C) ตอนนี้อายุ 20 ปีแล้ว?


5
ฉันไม่ทราบสถาปัตยกรรมปัจจุบันใด ๆ ที่มีคำสั่ง FPU สำหรับpow. x86 มีy log2 xคำสั่ง ( fyl2x) ที่สามารถใช้เป็นส่วนแรกของpowฟังก์ชัน แต่powฟังก์ชันที่เขียนด้วยวิธีนี้ใช้เวลาหลายร้อยรอบในการดำเนินการกับฮาร์ดแวร์ปัจจุบัน รูทีนการยกกำลังจำนวนเต็มที่เขียนดีนั้นเร็วกว่าหลายเท่า
Stephen Canon

ฉันไม่รู้ว่า "ร้อย" นั้นแม่นยำดูเหมือนว่าจะอยู่ที่ประมาณ 150 รอบสำหรับ fyl2x แล้วก็ f2xm1 ในซีพียูที่ทันสมัยที่สุดและนั่นก็เป็นไปตามคำแนะนำอื่น ๆ แต่คุณคิดถูกว่าการใช้งานจำนวนเต็มที่ปรับแต่งมาอย่างดีควรจะเร็วกว่ามาก (ในปัจจุบัน) เนื่องจาก IMUL ได้รับการเร่งความเร็วมากกว่าคำแนะนำทศนิยม ย้อนกลับไปเมื่อเขียนมาตรฐาน C แต่ IMUL ค่อนข้างแพงและการใช้แบบวนซ้ำอาจใช้เวลานานกว่าการใช้ FPU
Ben Voigt

2
เปลี่ยนการโหวตของฉันในแง่ของการแก้ไข; อย่างไรก็ตามโปรดทราบว่า (a) มาตรฐาน C ได้รับการแก้ไขครั้งใหญ่ (รวมถึงการขยายตัวของไลบรารีคณิตศาสตร์จำนวนมาก) ในปี 2542 และ (b) มาตรฐาน C ไม่ได้เขียนไว้ในสถาปัตยกรรมโปรเซสเซอร์ใด ๆ - การมีอยู่ หรือไม่มีคำแนะนำ FPU บน x86 โดยพื้นฐานแล้วไม่มีส่วนเกี่ยวข้องกับฟังก์ชันใดที่คณะกรรมการ C เลือกเพื่อสร้างมาตรฐาน
Stephen Canon

มันไม่ได้เชื่อมโยงกับสถาปัตยกรรมใด ๆ แต่เป็นค่าใช้จ่ายสัมพัทธ์ของการแก้ไขตารางการค้นหา (โดยทั่วไปจะใช้สำหรับการใช้จุดลอยตัว) เมื่อเทียบกับการคูณจำนวนเต็มมีการเปลี่ยนแปลงอย่างเท่าเทียมกันสำหรับสถาปัตยกรรมทั้งหมดที่ฉันคาดเดา
Ben Voigt

1

นี่คือการใช้งานO (log (n))อย่างง่าย ๆของ pow () ที่ใช้ได้กับตัวเลขทุกประเภทรวมถึงจำนวนเต็ม :

template<typename T>
static constexpr inline T pown(T x, unsigned p) {
    T result = 1;

    while (p) {
        if (p & 0x1) {
            result *= x;
        }
        x *= x;
        p >>= 1;
    }

    return result;
}

มันดีกว่าการใช้งาน O (log (n)) ของ enigmaticPhysicist เพราะไม่ใช้การเรียกซ้ำ

นอกจากนี้ยังเร็วกว่าการใช้งานเชิงเส้นเกือบตลอดเวลา (ตราบใดที่ p> ~ 3) เนื่องจาก:

  • ไม่ต้องใช้หน่วยความจำเพิ่มเติมใด ๆ
  • มันทำงานได้มากขึ้นประมาณ 1.5 เท่าต่อลูป
  • มันอัปเดตหน่วยความจำเพิ่มขึ้นประมาณ 1.25 เท่าต่อลูปเท่านั้น

-2

ตามความเป็นจริงมันไม่

เนื่องจาก C ++ 11 มีการใช้งานเทมเพลตของpow(int, int)--- และกรณีทั่วไปมากขึ้นโปรดดู (7) ใน http://en.cppreference.com/w/cpp/numeric/math/pow


แก้ไข: คนเจ้าระเบียบอาจโต้แย้งว่าสิ่งนี้ไม่ถูกต้องเนื่องจากมีการใช้การพิมพ์แบบ "เลื่อนขั้น" ไม่ทางใดก็ทางหนึ่งได้intผลลัพธ์ที่ถูกต้องหรือข้อผิดพลาดเกี่ยวกับintพารามิเตอร์


2
สิ่งนี้ไม่ถูกต้อง โอเวอร์โหลด (7) คือpow ( Arithmetic1 base, Arithmetic2 exp )สิ่งที่จะส่งไปยังdoubleหรือlong doubleหากคุณได้อ่านคำอธิบาย: "7) ชุดของโอเวอร์โหลดหรือเทมเพลตฟังก์ชันสำหรับการรวมกันของอาร์กิวเมนต์ทั้งหมดของประเภทเลขคณิตไม่ครอบคลุม 1-3) หากอาร์กิวเมนต์ใด ๆ มีประเภทอินทิกรัลมันจะถูกเหวี่ยงเป็นสองเท่าหากอาร์กิวเมนต์ใด ๆ ยาวเป็นสองเท่าประเภทผลตอบแทนที่เลื่อนระดับจะเป็นแบบยาวเป็นสองเท่ามิฉะนั้นประเภทผลตอบแทนจะเป็นสองเท่าเสมอ "
phuclv

อะไรไม่ถูกต้องที่นี่? ฉันแค่บอกว่าปัจจุบัน (ตั้งแต่ C ++ 11) เทมเพลตธาร ( , ) อยู่ในไลบรารีมาตรฐานซึ่งไม่เป็นเช่นนั้นในปี 2010
Dima Pasechnik

5
ไม่มันไม่ได้ Templeates ส่งเสริมประเภทเหล่านี้เป็นสองเท่าหรือยาวเป็นสองเท่า ดังนั้นจึงใช้ได้กับคู่ผสมที่อยู่ข้างใต้
Trismegistos

1
@Trismegistos มันยังคงอนุญาตพารามิเตอร์ int หากไม่มีเทมเพลตนี้การส่งผ่านพารามิเตอร์ int จะทำให้มันตีความบิตใน int เป็น float ทำให้เกิดผลลัพธ์ที่ไม่คาดคิดโดยพลการ สิ่งเดียวกันนี้เกิดขึ้นกับค่าอินพุตแบบผสม เช่นpow(1.5f, 3)= 1072693280but pow(1.5f, float(3))=3.375
Mark Jeronimus

2
สหกรณ์ถามหาint pow(int, int)แต่ C ++ 11 เพียง double pow(int, int)แต่ให้ ดูคำอธิบายของ @phuclv
xuhdev

-4

เหตุผลง่ายๆ:

5^-2 = 1/25

ทุกสิ่งในไลบรารี STL ขึ้นอยู่กับสิ่งที่แม่นยำและมีประสิทธิภาพที่สุดเท่าที่จะเป็นไปได้ แน่นอนว่า int จะกลับไปเป็นศูนย์ (จาก 1/25) แต่นี่จะเป็นคำตอบที่ไม่ถูกต้อง

ฉันยอมรับมันแปลกในบางกรณี


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