C ++ Cách tốt nhất để chia số nguyên và phần dư


103

Tôi chỉ đang tự hỏi, nếu tôi muốn chia a cho b và quan tâm đến cả kết quả c và phần dư (ví dụ: giả sử tôi có số giây và muốn chia nó thành phút và giây), cách tốt nhất là gì đi về nó?

Nó sẽ được

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

hoặc là

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

hoặc là

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

hoặc là

có thể có một chức năng kỳ diệu cung cấp cho một cả hai cùng một lúc?


5
tất cả các câu trả lời bên dưới đều có vẻ hợp lý, tôi chỉ muốn nói thêm rằng bất kỳ việc mua nào với double(mục cuối cùng của bạn) đối với tôi dường như là một ý tưởng tồi, bạn sẽ kết thúc với những con số không xếp hàng và có thể khiến bạn mất hiệu suất và kích thước thực thi (luôn là một vấn đề đối với tôi trên một số hệ thống nhúng nhất định).
nhập vào

2
Thứ ba là một tùy chọn BAD: nếu tmp = 54.999999999999943157 thì sao? Điều này cho thấy, đúc kiểu cũ không bao giờ là một điều thông minh để làm.
jimifiki

Câu trả lời:


98

Trên x86, phần còn lại là sản phẩm phụ của chính quá trình phân chia vì vậy bất kỳ trình biên dịch nào khá ổn đều có thể sử dụng nó (và không thực hiện divlại). Điều này có lẽ cũng được thực hiện trên các kiến ​​trúc khác.

Hướng dẫn: DIVsrc

Lưu ý: Phép chia không dấu. Chia bộ tích lũy (AX) cho "src". Nếu số chia là một giá trị byte, kết quả được đưa vào AL và phần còn lại là AH . Nếu số chia là một giá trị từ, thì DX: AX được chia cho "src" và kết quả được lưu trong AX và phần còn lại được lưu trong DX .

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

9
Tôi nghĩ rằng nhiều người hiểu rõ từ trường tiểu học rằng khi thực hiện một phép chia, bạn sẽ nhận được phần còn lại miễn phí. Câu hỏi thực sự là: các trình biên dịch của chúng ta có đủ thông minh để tận dụng lợi thế này không?

1
@jdv: Tôi sẽ không ngạc nhiên. Đó là một tối ưu hóa rất đơn giản.
Jon Purdy

68
Tôi đã thử một bài kiểm tra nhanh. Với g ++ 4.3.2 sử dụng -O2, đầu ra của trình hợp dịch hiển thị rõ ràng nó bằng một idivllệnh và sử dụng kết quả trong eax và edx. Tôi sẽ bị sốc nếu nó không xảy ra.
Fred Larson

2
@EuriPinhollow Đồng ý rằng đây không phải là câu hỏi về x86. Tôi chỉ đưa ra nó như một ví dụ, với giả định rất đáng ngờ rằng các kiến ​​trúc khác có thể làm điều gì đó tương tự.
cnicutar

3
Tuy nhiên, bạn cần yêu cầu trình biên dịch tối ưu hóa, ít nhất là đối với g ++. Một thử nghiệm đơn giản với g ++ 6.3.0 trên x86_64 cho thấy rằng không có tối ưu hóa nào cả, bạn nhận được hai idivlhướng dẫn, nhưng với -O1hoặc cao hơn, bạn sẽ nhận được một. Như sách hướng dẫn nói, “Không có bất kỳ tùy chọn tối ưu hóa nào… Các câu lệnh là độc lập” .
Tom Zych

80

std::div trả về một cấu trúc có cả kết quả và phần dư.


5
Tôi tò mò muốn biết liệu điều này có thực sự hiệu quả hơn, chẳng hạn như tùy chọn 1 trên một trình biên dịch hiện đại.
Greg Howell

2
Đẹp, tôi không biết. Nó có nhanh hơn không?

Đẹp. Bạn có tình cờ biết nếu một cái được thực hiện lâu dài ở đâu đó không?
Cookie

@Cookie: C ++ 03 không có khái niệm gì long long, nhưng rất có thể trình biên dịch của bạn long longquá tải std::divdưới dạng phần mở rộng.
ildjarn

@Greg: Tôi không nghĩ là như vậy, vì nó rất có thể phải ghi kết quả vào một cấu trúc trong bộ nhớ và trả lại nó, điều này (trừ khi nó biên dịch dưới dạng nội tuyến và quyền truy cập cấu trúc được tối ưu hóa) là một hình phạt lớn hơn việc làm một hướng dẫn chia thêm.
pezcode

28

Ít nhất trên x86, g ++ 4.6.1 chỉ sử dụng IDIVL và nhận được cả hai từ lệnh duy nhất đó.

Mã C ++:

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

mã x86:

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

Thứ tự có quan trọng không? Ví dụ: nếu bạn đang lặp lại một /=- bạn có thể cần sử dụng một biến tạm thời để giữ phép chia trước.
AnnanFay

@Annan Nó không thành vấn đề hồi đó, và ngày nay thì không. Trình biên dịch đủ thông minh để sắp xếp lại nó.
Sahsahae

10

Kiểm tra mã mẫu div () và phép chia & mod kết hợp. Tôi đã biên dịch những thứ này bằng gcc -O3, tôi phải thêm lệnh gọi doNothing để ngăn trình biên dịch tối ưu hóa mọi thứ (đầu ra sẽ là 0 cho giải pháp chia + mod).

Mang nó theo một hạt muối:

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

Đầu ra: 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);
}

Kết quả đầu ra: 25



3

Tất cả đều bình đẳng, giải pháp tốt nhất là giải pháp thể hiện rõ ràng ý định của bạn. Vì thế:

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

có lẽ là tốt nhất trong ba lựa chọn bạn đã trình bày. Tuy nhiên, như đã lưu ý trong các câu trả lời khác, divphương pháp sẽ tính toán cả hai giá trị cho bạn cùng một lúc.


3

Bạn không thể tin tưởng g ++ 4.6.3 ở đây với số nguyên 64 bit trên nền tảng intel 32 bit. a / b được tính bằng lệnh gọi divdi3 và a% b được tính bằng lệnh gọi moddi3. Tôi thậm chí có thể đưa ra một ví dụ tính toán a / b và ab * (a / b) với các lệnh gọi này. Vì vậy, tôi sử dụng c = a / b và ab * c.

Phương thức div đưa ra lời gọi hàm tính toán cấu trúc div, nhưng lệnh gọi hàm dường như không hiệu quả trên các nền tảng có hỗ trợ phần cứng cho kiểu tích phân (tức là số nguyên 64 bit trên nền tảng intel / amd 64 bit).


-4

Bạn có thể sử dụng một modulus để lấy phần còn lại. Mặc dù câu trả lời của @ cnicutar có vẻ rõ ràng hơn / trực tiếp hơn.


2
Vâng, người đăng ban đầu đã sử dụng toán tử mô-đun, câu hỏi là làm thế nào để làm cho nó hiệu quả.
Mark Lakata
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.