เพดานอย่างรวดเร็วของการหารจำนวนเต็มใน C / C ++


262

รับค่าจำนวนเต็มxและyC และ C ++ กลับทั้งสองเป็นผลหารหารq = x/yด้วยจำนวนจุดลอยตัว ฉันสนใจวิธีการคืนเพดานแทน ยกตัวอย่างเช่นและceil(10/5)=2ceil(11/5)=3

วิธีการที่ชัดเจนเกี่ยวข้องกับสิ่งที่ชอบ:

q = x / y;
if (q * y < x) ++q;

สิ่งนี้ต้องการการเปรียบเทียบและการคูณที่พิเศษ และวิธีการอื่น ๆ ที่ผมเคยเห็น (ใช้ในความเป็นจริง) เกี่ยวข้องกับการหล่อเป็นหรือfloat doubleมีวิธีที่ตรงกว่าที่จะหลีกเลี่ยงการเพิ่มการคูณ (หรือการหารที่สอง) และสาขาและหลีกเลี่ยงการร่ายเป็นเลขทศนิยมหรือไม่?


70
คำสั่งการหารมักจะส่งกลับทั้งผลหารและส่วนที่เหลือในเวลาเดียวกันดังนั้นจึงไม่จำเป็นต้องเพิ่มทวีคูณก็q = x/y + (x % y != 0);เพียงพอแล้ว
phuclv

2
@ LưuVĩnhPhúcที่ความคิดเห็นควรเป็นคำตอบที่ยอมรับ imo
Andreas Grapentin

1
@ LưuVĩnhPhúcอย่างจริงจังคุณต้องเพิ่มนั่นเป็นคำตอบ ฉันเพิ่งใช้คำตอบของฉันในระหว่างการทดสอบความคล่องแคล่ว มันใช้งานได้อย่างมีเสน่ห์แม้ว่าฉันจะไม่แน่ใจว่า mod ของคำตอบทำงานอย่างไร แต่มันก็ทำงานได้ดี
Zachary Kraus

2
@AndreasGrapentin คำตอบด้านล่างโดย Miguel Figueiredo ถูกส่งมาเกือบหนึ่งปีก่อนLưuVĩnhPhúcออกความคิดเห็นด้านบน ในขณะที่ฉันเข้าใจว่าโซลูชันของ Miguel น่าดึงดูดใจและสง่างามเพียงใดฉันไม่อยากเปลี่ยนคำตอบที่ยอมรับได้ในวันนี้ วิธีการทั้งสองยังคงอยู่ หากคุณรู้สึกว่าแข็งแกร่งเพียงพอฉันขอแนะนำให้คุณแสดงการสนับสนุนของคุณโดยคำตอบของ Miguel ที่ได้รับการโหวตด้านล่าง
andand

1
แปลกฉันไม่เคยเห็นการวัดหรือวิเคราะห์สติที่เสนอมา คุณพูดถึงความเร็วใกล้กับกระดูก แต่ไม่มีการพูดถึงสถาปัตยกรรมท่อคำแนะนำการแยกย่อยและวงจรนาฬิกา
Rado

คำตอบ:


394

สำหรับตัวเลขบวก

unsigned int x, y, q;

เพื่อปัดเศษขึ้น ...

q = (x + y - 1) / y;

หรือ (หลีกเลี่ยงการโอเวอร์โฟลว์ใน x + y)

q = 1 + ((x - 1) / y); // if x != 0

6
@ bitc: สำหรับตัวเลขติดลบฉันเชื่อว่า C99 ระบุค่าแบบ Round-to-Zero ดังนั้นx/yเพดานของการหาร C90 ไม่ได้ระบุวิธีการปัดเศษและฉันไม่คิดว่ามาตรฐาน C ++ ปัจจุบันจะทำเช่นนั้น
David Thornley

6
ดูโพสต์ของ Eric Lippert: stackoverflow.com/questions/921180/c-round-up/926806#926806
Mashmagar

3
หมายเหตุ: นี่อาจล้น q = ((ยาว) x + y - 1) / y จะไม่ รหัสของฉันช้าลงดังนั้นถ้าคุณรู้ว่าตัวเลขของคุณจะไม่ล้นคุณควรใช้เวอร์ชั่นของ Sparky
Jørgen Fogh

1
@ bitc: ฉันเชื่อว่าจุดของดาวิดคือคุณจะไม่ใช้การคำนวณข้างต้นหากผลลัพธ์เป็นลบ - คุณจะใช้q = x / y;
caf

12
อันที่สองมีปัญหาเมื่อ x คือ 0 ceil (0 / y) = 0 แต่มันจะคืนค่า 1
Omry Yadan

78

สำหรับตัวเลขบวก:

    q = x/y + (x % y != 0);

5
การเรียนการสอนแบ่งสถาปัตยกรรมที่พบมากที่สุดยังมีเหลืออยู่ในผลของมันดังนั้นนี้จริงๆต้องเพียงส่วนหนึ่งและจะเร็วมาก
phuclv

58

คำตอบของ Sparky เป็นวิธีมาตรฐานในการแก้ปัญหานี้ แต่ในขณะที่ฉันเขียนไว้ในความคิดเห็นของคุณคุณก็เสี่ยงต่อการล้น สิ่งนี้สามารถแก้ไขได้โดยใช้ชนิดที่กว้างขึ้น แต่ถ้าคุณต้องการหารlong longs?

คำตอบของ Nathan Ernst เป็นคำตอบเดียว แต่มันเกี่ยวข้องกับการเรียกฟังก์ชันการประกาศตัวแปรและเงื่อนไขซึ่งทำให้ไม่สั้นกว่ารหัส OPs และอาจช้ากว่าเพราะยากต่อการปรับให้เหมาะสม

ทางออกของฉันคือ:

q = (x % y) ? x / y + 1 : x / y;

มันจะเร็วกว่าโค้ด OPs เล็กน้อยเนื่องจากโมดูโลและการหารนั้นใช้คำสั่งเดียวกันบนโปรเซสเซอร์เนื่องจากคอมไพเลอร์สามารถเห็นได้ว่ามันเทียบเท่ากัน อย่างน้อย gcc 4.4.1 ทำการปรับให้เหมาะสมนี้ด้วยแฟล็ก -O2 บน x86

ในทางทฤษฎีแล้วผู้รวบรวมอาจอินไลน์การเรียกใช้ฟังก์ชันในโค้ดของ Nathan Ernst และปล่อยสิ่งเดียวกัน แต่ gcc ไม่ได้ทำอย่างนั้นเมื่อฉันทดสอบ อาจเป็นเพราะมันจะผูกรหัสที่คอมไพล์กับไลบรารี่มาตรฐานรุ่นเดียว

ในฐานะที่เป็นบันทึกสุดท้ายไม่มีสิ่งใดที่เกิดขึ้นกับเครื่องจักรที่ทันสมัยยกเว้นในกรณีที่คุณอยู่ในวงวนอย่างมากและข้อมูลทั้งหมดของคุณอยู่ในทะเบียน มิฉะนั้นโซลูชันเหล่านี้ทั้งหมดจะเร็วพอ ๆ กันยกเว้นอาจเป็นไปได้ของ Nathan Ernst ซึ่งอาจช้าลงอย่างมากหากฟังก์ชันต้องดึงข้อมูลจากหน่วยความจำหลัก


3
มีวิธีที่ง่ายกว่าในการแก้ไขปัญหาน้ำล้นเพียงลด y / y:q = (x > 0)? 1 + (x - 1)/y: (x / y);
Ben Voigt

-1: นี่เป็นวิธีที่ไม่มีประสิทธิภาพเนื่องจากมีการแลกเปลี่ยนราคาถูก * สำหรับค่าใช้จ่าย% เลวร้ายยิ่งกว่าวิธีการ OP
Yves Daoust

2
ไม่มันไม่ ตามที่ฉันได้อธิบายไว้ในคำตอบแล้วโอเปอเรเตอร์% จะว่างเมื่อคุณทำการแบ่งแล้ว
Jørgen Fogh

1
ถ้าอย่างนั้นq = x / y + (x % y > 0);ง่ายกว่า? :การแสดงออก?
ฮั่น

ขึ้นอยู่กับสิ่งที่คุณหมายถึงโดย "ง่ายขึ้น" มันอาจจะเร็วกว่าหรือไม่ก็ได้ขึ้นอยู่กับว่าคอมไพเลอร์แปลมันอย่างไร การเดาของฉันจะช้ากว่านี้ แต่ฉันต้องวัดมันเพื่อให้แน่ใจ
Jørgen Fogh

18

คุณสามารถใช้divฟังก์ชั่นใน cstdlib เพื่อให้ได้ค่าหาร & ส่วนที่เหลือในการโทรครั้งเดียวแล้วจัดการเพดานแยกต่างหากเช่นในด้านล่าง

#include <cstdlib>
#include <iostream>

int div_ceil(int numerator, int denominator)
{
        std::div_t res = std::div(numerator, denominator);
        return res.rem ? (res.quot + 1) : res.quot;
}

int main(int, const char**)
{
        std::cout << "10 / 5 = " << div_ceil(10, 5) << std::endl;
        std::cout << "11 / 5 = " << div_ceil(11, 5) << std::endl;

        return 0;
}

12
ในฐานะที่เป็นกรณีที่น่าสนใจของปังคู่คุณยังสามารถreturn res.quot + !!res.rem;:)
แซม Harwell

ldiv ไม่สนับสนุนข้อโต้แย้งในความยาวตลอดเวลาหรือไม่? และนั่นไม่ได้มีค่าใช้จ่ายอะไรเลย
einpoklum

12

แล้วเรื่องนี้ล่ะ (ต้องการ y ที่ไม่เป็นลบดังนั้นอย่าใช้สิ่งนี้ในกรณีที่หายากโดยที่ y เป็นตัวแปรที่ไม่มีการรับประกันที่ไม่ใช่ลบ)

q = (x > 0)? 1 + (x - 1)/y: (x / y);

ฉันลดเหลือy/yหนึ่งลบคำx + y - 1และมีโอกาสล้นใด ๆ

ฉันหลีกเลี่ยงการx - 1พันเมื่อxเป็นประเภทที่ไม่ได้ลงชื่อและมีค่าเป็นศูนย์

สำหรับเซ็นชื่อxเชิงลบและศูนย์ยังคงรวมกันเป็นกรณีเดียว

อาจไม่ใช่ประโยชน์อย่างมากสำหรับซีพียูที่ใช้งานทั่วไปที่ทันสมัย ​​แต่มันจะเร็วกว่าในระบบฝังตัวมากกว่าคำตอบที่ถูกต้องอื่น ๆ


ของคุณจะส่งคืน 0 เสมอโดยไม่จำเป็นต้องคำนวณอะไรเลย
Ruud Althuizen

@Ruud: ไม่จริง พิจารณา x = -45 และ y = 4
Ben Voigt

7

มีวิธีแก้ปัญหาสำหรับทั้งบวกและลบxแต่สำหรับบวกyมีเพียง 1 แผนกและไม่มีสาขา:

int ceil(int x, int y) {
    return x / y + (x % y > 0);
}

หมายเหตุถ้าxเป็นบวกแล้วการหารนั้นมีค่าเป็นศูนย์และเราควรเพิ่ม 1 ถ้าการเตือนไม่ใช่ศูนย์

ถ้าxเป็นลบแล้วการหารนั้นมีค่าเป็นศูนย์นั่นคือสิ่งที่เราต้องการและเราจะไม่เพิ่มอะไรเลยเพราะx % yไม่ใช่เชิงบวก


ที่น่าสนใจเพราะมีกรณีทั่วไปที่มีค่า y อยู่เสมอ
Wolf

1
mod จำเป็นต้องมีการหารดังนั้นมันจึงไม่ใช่แค่ 1 ส่วนที่นี่ แต่ผู้ประกอบการอาจเพิ่มประสิทธิภาพของสองแผนกที่คล้ายคลึงกันในที่เดียว
M.kazem Akhgary

4

ใช้งานได้กับจำนวนบวกหรือลบ:

q = x / y + ((x % y != 0) ? !((x > 0) ^ (y > 0)) : 0);

หากมีส่วนที่เหลือจะตรวจสอบเพื่อดูว่าxและyมีเครื่องหมายเดียวกันและเพิ่ม1ตาม


3

ฉันจะได้แสดงความคิดเห็นค่อนข้าง แต่ฉันไม่ได้มีตัวแทนสูงพอ

เท่าที่ฉันทราบสำหรับข้อโต้แย้งเชิงบวกและตัวหารซึ่งเป็นพลังของ 2 นี้เป็นวิธีที่เร็วที่สุด (ทดสอบใน CUDA):

//example y=8
q = (x >> 3) + !!(x & 7);

สำหรับข้อโต้แย้งเชิงบวกทั่วไปเท่านั้นฉันมักจะทำเช่นนั้น:

q = x/y + !!(x % y);

มันจะน่าสนใจที่จะเห็นว่าq = x/y + !!(x % y);สแต็คต่อต้านq = x/y + (x % y == 0);และq = (x + y - 1) / y;วิธีการ แก้ปัญหาประสิทธิภาพใน CUDA ร่วมสมัย
Greg Kramida

2

แบบฟอร์มทั่วไปที่ง่ายขึ้น

int div_up(int n, int d) {
    return n / d + (((n < 0) ^ (d > 0)) && (n % d));
} //i.e. +1 iff (not exact int && positive result)

สำหรับคำตอบทั่วไปเพิ่มเติมฟังก์ชัน C ++ สำหรับการหารจำนวนเต็มพร้อมกลยุทธ์การปัดเศษที่กำหนดไว้อย่างดี


-2

คอมไพล์ด้วย O3 คอมไพเลอร์ทำงานได้ดีที่สุด

q = x / y;
if (x % y)  ++q;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.