C ++ วิธีที่ดีที่สุดในการรับการหารจำนวนเต็มและส่วนที่เหลือ


103

ฉันแค่สงสัยว่าถ้าฉันต้องการหาร a ด้วย b และสนใจทั้งในผลลัพธ์ c และส่วนที่เหลือ (เช่นบอกว่าฉันมีจำนวนวินาทีและต้องการแบ่งเป็นนาทีและวินาที) วิธีที่ดีที่สุดในการ ไปเลย?

มันจะเป็นอย่างไร

int c = (int)a / b;
int d = a % b;

หรือ

int c = (int)a / b;
int d = a - b * c;

หรือ

double tmp = a / b;
int c = (int)tmp;
int d = (int)(0.5+(tmp-c)*b);

หรือ

อาจจะมีฟังก์ชันวิเศษที่ให้ทั้งสองอย่างพร้อมกัน?


5
คำตอบทั้งหมดด้านล่างดูสมเหตุสมผลฉันแค่อยากจะเพิ่มว่าการโกงdouble(รายการสุดท้ายของคุณ) สำหรับฉันดูเหมือนเป็นความคิดที่ไม่ดีคุณจะจบลงด้วยตัวเลขที่ไม่เข้ากันและอาจทำให้คุณเสียค่าใช้จ่ายในการปฏิบัติงานและ ขนาดที่ปฏิบัติการได้ (มักจะเป็นปัญหาสำหรับฉันในระบบฝังตัวบางระบบ)
เมื่อ

2
ตัวที่สามคือตัวเลือกที่ไม่ดี: ถ้า tmp = 54.999999999999943157 ล่ะ? กล่าวเช่นนี้การหล่อแบบเก่าไม่เคยเป็นสิ่งที่ฉลาดที่จะทำ
jimifiki

คำตอบ:


98

บน x86 ส่วนที่เหลือเป็นผลพลอยได้จากการหารดังนั้นคอมไพเลอร์ที่มีค่าครึ่งหนึ่งควรจะสามารถใช้งานได้ (และไม่ต้องดำเนินการdivอีกครั้ง) สิ่งนี้อาจทำได้ในสถาปัตยกรรมอื่นด้วย

คำแนะนำ: DIVsrc

หมายเหตุ: การแบ่งที่ไม่ได้ลงนาม แบ่งตัวสะสม (AX) ด้วย "src" ถ้าหารคุ้มค่าไบต์ผลนำไป AL และส่วนที่เหลือจะ AH หากหารเป็นค่าคำแล้ว DX: ขวานโดยแบ่งเป็น "src" และผลที่ถูกเก็บไว้ในขวานและส่วนที่เหลือจะถูกเก็บไว้ใน DX

int c = (int)a / b;
int d = a % b; /* Likely uses the result of the division. */

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

1
@jdv: ฉันจะไม่แปลกใจ เป็นการเพิ่มประสิทธิภาพที่ง่ายมาก
Jon Purdy

68
ฉันลองทดสอบอย่างรวดเร็ว ด้วย g ++ 4.3.2 โดยใช้ -O2 เอาต์พุตแอสเซมเบลอร์จะแสดงอย่างชัดเจนโดยใช้idivlคำสั่งเดียวและใช้ผลลัพธ์ใน eax และ edx ฉันคงตกใจมากถ้าไม่เป็นเช่นนั้น
Fred Larson

2
@EuriPinhollow เห็นด้วยนี่ไม่ใช่คำถามเกี่ยวกับ x86 ฉันยกให้เป็นตัวอย่างเท่านั้นโดยมีข้อสันนิษฐานที่น่าสงสัยอย่างยิ่งว่าสถาปัตยกรรมอื่น ๆ อาจทำอะไรคล้าย ๆ กัน
cnicutar

3
คุณต้องบอกคอมไพเลอร์ให้ปรับให้เหมาะสมอย่างน้อยสำหรับ g ++ การทดลองง่ายๆกับ g ++ 6.3.0 บน x86_64 พบว่าหากไม่มีการปรับให้เหมาะสมเลยคุณจะได้idivlรับคำสั่งสองคำสั่ง แต่จะมี-O1หรือมากกว่านั้นก็ได้ ในฐานะที่เป็นคู่มือกล่าวว่า“ถ้าไม่มีตัวเลือกในการเพิ่มประสิทธิภาพใด ๆ ... งบมีความเป็นอิสระ”
Tom Zych

81

std::div ส่งคืนโครงสร้างที่มีทั้งผลลัพธ์และส่วนที่เหลือ


5
ฉันอยากรู้ว่าสิ่งนี้มีประสิทธิภาพมากกว่าพูดตัวเลือก 1 ในคอมไพเลอร์สมัยใหม่หรือไม่
Greg Howell

2
ดีฉันไม่รู้ เร็วกว่ามั้ย?

ดี. คุณจะรู้หรือไม่ว่ามีการใช้งานเป็นเวลานานหรือไม่?
คุกกี้

@Cookie: C ++ 03 ไม่มีแนวคิดlong longแต่มีโอกาสสูงที่คอมไพเลอร์ของคุณจะมีlong longโอเวอร์โหลดstd::divเป็นส่วนขยาย
ildjarn

@Greg: ฉันไม่คิดว่ามันเป็นเพราะมันมักจะต้องเขียนผลลัพธ์ไปยังโครงสร้างในหน่วยความจำและส่งคืนซึ่ง (เว้นแต่จะรวบรวมเป็นแบบอินไลน์และการเข้าถึงโครงสร้างได้รับการปรับให้เหมาะสมที่สุด) เป็นโทษที่ใหญ่กว่าการทำ คำสั่งการหารพิเศษ
pezcode

28

บน x86 เป็นอย่างน้อย g ++ 4.6.1 ใช้ IDIVL และรับทั้งสองคำสั่งจากคำสั่งเดียว

รหัส C ++:

void foo(int a, int b, int* c, int* d)
{
  *c = a / b;
  *d = a % b;
}

รหัส x86:

__Z3fooiiPiS_:
LFB4:
    movq    %rdx, %r8
    movl    %edi, %edx
    movl    %edi, %eax
    sarl    $31, %edx
    idivl   %esi
    movl    %eax, (%r8)
    movl    %edx, (%rcx)
    ret

คำสั่งซื้อมีความสำคัญหรือไม่? ตัวอย่างเช่นหากคุณทำซ้ำบน a /=- คุณอาจต้องใช้ตัวแปรชั่วคราวเพื่อเก็บการหารไว้ก่อน
AnnanFay

@ อันนันมันไม่สำคัญในตอนนั้นและมันก็ไม่ได้เกิดขึ้นในปัจจุบัน คอมไพเลอร์ฉลาดพอที่จะจัดลำดับใหม่
Sahsahae

10

ตัวอย่างการทดสอบโค้ด div () และการหารรวมและ mod ฉันรวบรวมสิ่งเหล่านี้ด้วย gcc -O3 ฉันต้องเพิ่มการโทร to do ไม่มีอะไรที่จะหยุดคอมไพเลอร์จากการปรับแต่งทุกอย่างให้เหมาะสม (เอาต์พุตจะเป็น 0 สำหรับโซลูชันการหาร + mod)

ใช้เกลือเม็ด:

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

extern doNothing(int,int); // Empty function in another compilation unit

int main() {
    int i;
    struct timeval timeval;
    struct timeval timeval2;
    div_t result;
    gettimeofday(&timeval,NULL);
    for (i = 0; i < 1000; ++i) {
        result = div(i,3);
        doNothing(result.quot,result.rem);
    }
    gettimeofday(&timeval2,NULL);
    printf("%d",timeval2.tv_usec - timeval.tv_usec);
}

เอาท์พุต: 150

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

extern doNothing(int,int); // Empty function in another compilation unit

int main() {
    int i;
    struct timeval timeval;
    struct timeval timeval2;
    int dividend;
    int rem;
    gettimeofday(&timeval,NULL);
    for (i = 0; i < 1000; ++i) {
        dividend = i / 3;
        rem = i % 3;
        doNothing(dividend,rem);
    }
    gettimeofday(&timeval2,NULL);
    printf("%d",timeval2.tv_usec - timeval.tv_usec);
}

ผลลัพธ์: 25


5

นอกเหนือจากฟังก์ชันstd :: divดังกล่าวแล้วยังมีฟังก์ชันstd :: remquoส่งกลับrem -ainder และรับquo -tient ผ่านตัวชี้ที่ส่งผ่าน

[แก้ไข:]ดูเหมือนว่ามาตรฐาน :: remquo ไม่ได้โดดกลับหารหลังจากทั้งหมด


3

สิ่งอื่น ๆ เท่าเทียมกันทางออกที่ดีที่สุดคือวิธีที่แสดงเจตนาของคุณอย่างชัดเจน ดังนั้น:

int totalSeconds = 453;
int minutes = totalSeconds / 60;
int remainingSeconds = totalSeconds % 60;

อาจเป็นตัวเลือกที่ดีที่สุดในสามตัวเลือกที่คุณนำเสนอ ตามที่ระบุไว้ในคำตอบอื่น ๆdivวิธีการนี้จะคำนวณค่าทั้งสองให้คุณพร้อมกัน


3

คุณไม่สามารถเชื่อถือ g ++ 4.6.3 ที่นี่ด้วยจำนวนเต็ม 64 บิตบนแพลตฟอร์ม Intel 32 บิต a / b คำนวณโดยการเรียกไปยัง divdi3 และ% b คำนวณโดยการเรียกไปยัง moddi3 ฉันยังสามารถหาตัวอย่างที่คำนวณ a / b และ ab * (a / b) ด้วยการเรียกเหล่านี้ ดังนั้นฉันจึงใช้ c = a / b และ ab * c

วิธี div ให้การเรียกใช้ฟังก์ชันที่คำนวณโครงสร้าง div แต่การเรียกฟังก์ชันดูเหมือนจะไม่มีประสิทธิภาพบนแพลตฟอร์มที่มีการสนับสนุนฮาร์ดแวร์สำหรับประเภทอินทิกรัล (เช่นจำนวนเต็ม 64 บิตบนแพลตฟอร์ม 64 บิต intel / amd)


-4

คุณสามารถใช้โมดูลัสเพื่อหาส่วนที่เหลือ แม้ว่าคำตอบของ @ cnicutar จะดูสะอาด / ตรงประเด็นกว่า


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