Trần nhanh của một phân chia số nguyên trong C / C ++


262

Cho các giá trị nguyên xy, cả C và C ++ đều trả về giá trị q = x/ysàn của điểm nổi tương đương. Thay vào đó, tôi quan tâm đến một phương pháp trả lại trần. Ví dụ, ceil(10/5)=2ceil(11/5)=3.

Cách tiếp cận rõ ràng liên quan đến một cái gì đó như:

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

Điều này đòi hỏi một sự so sánh và nhân rộng thêm; và các phương pháp khác mà tôi đã thấy (thực tế được sử dụng) liên quan đến việc chọn như là một floathoặc double. Có một phương pháp trực tiếp hơn để tránh phép nhân bổ sung (hoặc một phân chia thứ hai) và nhánh, và điều đó cũng tránh việc đúc như một số dấu phẩy động?


70
hướng dẫn phân chia thường trả về cả thương số và phần còn lại cùng một lúc, do đó không cần phải nhân, chỉ cần q = x/y + (x % y != 0);là đủ
phuclv

2
@ LưuViênPhúc rằng bình luận nên là câu trả lời được chấp nhận, imo.
Andreas Grapentin

1
@ LưuViênPhúc Nghiêm túc bạn cần thêm câu đó làm câu trả lời. Tôi chỉ sử dụng nó cho câu trả lời của tôi trong một bài kiểm tra về tính mã hóa. Nó hoạt động như một cơ duyên mặc dù tôi không chắc phần mod của câu trả lời hoạt động như thế nào nhưng nó đã làm được việc.
Zachary Kraus

2
@AndreasGrapentin câu trả lời dưới đây của Miguel Figueiredo đã được gửi gần một năm trước khi Lưu Vĩnh Phúc để lại bình luận ở trên. Mặc dù tôi hiểu giải pháp của Miguel hấp dẫn và thanh lịch như thế nào, tôi không có xu hướng thay đổi câu trả lời được chấp nhận vào ngày muộn này. Cả hai cách tiếp cận vẫn còn âm thanh. Nếu bạn cảm thấy đủ mạnh về điều đó, tôi khuyên bạn nên thể hiện sự ủng hộ của mình bằng cách bỏ phiếu trả lời của Miguel bên dưới.
andand

1
Thật kỳ lạ, tôi chưa thấy bất kỳ phép đo hay phân tích lành mạnh nào về các giải pháp được đề xuất. Bạn nói về tốc độ trên xương gần, nhưng không có thảo luận về kiến ​​trúc, đường ống, hướng dẫn phân nhánh và chu kỳ đồng hồ.
Rado

Câu trả lời:


394

Đối với số dương

unsigned int x, y, q;

Để làm tròn ...

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

hoặc (tránh tràn trong x + y)

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

6
@bitc: Đối với các số âm, tôi tin rằng C99 chỉ định làm tròn từ 0 đến 0, x/ytrần của phép chia cũng vậy. C90 không chỉ định cách làm tròn và tôi cũng không nghĩ rằng tiêu chuẩn C ++ hiện tại cũng vậy.
David Thornley


3
Lưu ý: Điều này có thể tràn. q = ((dài dài) x + y - 1) / y sẽ không. Mã của tôi chậm hơn, vì vậy nếu bạn biết rằng số của mình sẽ không bị tràn, bạn nên sử dụng phiên bản của Sparky.
Jørgen Fogh

1
@bitc: Tôi tin rằng quan điểm của David là bạn sẽ không sử dụng phép tính ở trên nếu kết quả là âm tính - bạn sẽ chỉ sử dụngq = x / y;
caf

12
Cái thứ hai có một vấn đề trong đó x là 0. ceil (0 / y) = 0 nhưng nó trả về 1.
Omry Yadan

78

Đối với số dương:

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

5
hướng dẫn phân chia kiến ​​trúc phổ biến nhất cũng bao gồm phần còn lại trong kết quả của nó vì vậy điều này thực sự chỉ cần một phân chia và sẽ rất nhanh
phuclv

58

Câu trả lời của Sparky là một cách tiêu chuẩn để giải quyết vấn đề này, nhưng như tôi cũng đã viết trong bình luận của mình, bạn có nguy cơ bị tràn. Điều này có thể được giải quyết bằng cách sử dụng loại rộng hơn, nhưng nếu bạn muốn chia long longs thì sao?

Câu trả lời của Nathan Ernst cung cấp một giải pháp, nhưng nó liên quan đến một lệnh gọi hàm, khai báo biến và điều kiện, làm cho nó không ngắn hơn mã OP và thậm chí có thể chậm hơn, vì khó tối ưu hóa hơn.

Giải pháp của tôi là thế này:

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

Nó sẽ nhanh hơn một chút so với mã OP, bởi vì modulo và phép chia được thực hiện bằng cách sử dụng cùng một lệnh trên bộ xử lý, bởi vì trình biên dịch có thể thấy rằng chúng tương đương nhau. Ít nhất gcc 4.4.1 thực hiện tối ưu hóa này với cờ -O2 trên x86.

Về lý thuyết, trình biên dịch có thể thực hiện lệnh gọi hàm trong mã của Ern Ernst và phát ra điều tương tự, nhưng gcc đã không làm điều đó khi tôi kiểm tra nó. Điều này có thể là do nó sẽ buộc mã được biên dịch thành một phiên bản duy nhất của thư viện chuẩn.

Như một lưu ý cuối cùng, không có gì trong số này quan trọng trên một máy hiện đại, ngoại trừ nếu bạn đang ở trong một vòng lặp cực kỳ chặt chẽ và tất cả dữ liệu của bạn nằm trong các thanh ghi hoặc bộ đệm L1. Mặt khác, tất cả các giải pháp này sẽ nhanh như nhau, ngoại trừ khả năng của Nathan Ernst, có thể chậm hơn đáng kể nếu chức năng phải được lấy từ bộ nhớ chính.


3
Có một cách dễ dàng hơn để khắc phục lỗi tràn, chỉ cần giảm bớt y / y:q = (x > 0)? 1 + (x - 1)/y: (x / y);
Ben Voigt

-1: đây là một cách không hiệu quả, vì nó giao dịch với giá rẻ * với% tốn kém; tồi tệ hơn phương pháp OP.
Yves Daoust

2
Không nó không. Như tôi đã giải thích trong câu trả lời, toán tử% là miễn phí khi bạn đã thực hiện phép chia.
Jørgen Fogh

1
Sau đó q = x / y + (x % y > 0);thì dễ hơn ? :biểu hiện?
Han

Nó phụ thuộc vào những gì bạn có nghĩa là "dễ dàng hơn." Nó có thể hoặc không thể nhanh hơn, tùy thuộc vào cách trình biên dịch dịch nó. Tôi đoán sẽ chậm hơn nhưng tôi sẽ phải đo nó để chắc chắn.
Jørgen Fogh

18

Bạn có thể sử dụng divchức năng trong cstdlib để lấy thương số & phần còn lại trong một cuộc gọi và sau đó xử lý trần riêng, như trong phần dưới đây

#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
Là một trường hợp thú vị của tiếng nổ đôi, bạn cũng có thể return res.quot + !!res.rem;:)
Sam Harwell

Không phải ldiv luôn thúc đẩy các cuộc tranh luận lâu dài sao? Và không có chi phí gì, đúc lên hoặc xuống?
einpoklum

12

Còn cái này thì sao? (yêu cầu y không âm, vì vậy đừng sử dụng điều này trong trường hợp hiếm hoi trong đó y là biến không có bảo đảm không âm)

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

Tôi giảm xuống còn y/ymột, loại bỏ thuật ngữ x + y - 1và với nó bất kỳ cơ hội nào để tràn.

Tôi tránh x - 1quấn quanh khi xlà một loại không dấu và chứa số không.

Đối với ký x, âm và không vẫn kết hợp thành một trường hợp duy nhất.

Có lẽ không phải là một lợi ích to lớn trên CPU đa năng hiện đại, nhưng điều này sẽ nhanh hơn nhiều trong một hệ thống nhúng so với bất kỳ câu trả lời đúng nào khác.


Người khác của bạn sẽ luôn trả về 0, không cần tính toán gì cả.
Ruud Althuizen

@Ruud: không đúng Xét x = -45 và y = 4
Ben Voigt

7

Có một giải pháp cho cả tích cực và tiêu cực xnhưng chỉ cho tích cực ychỉ với 1 bộ phận và không có chi nhánh:

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

Lưu ý, nếu xlà số dương thì phép chia là 0 và chúng ta nên thêm 1 nếu lời nhắc không bằng 0.

Nếu xlà âm thì phân chia là 0, đó là những gì chúng ta cần và chúng ta sẽ không thêm bất cứ điều gì vì x % ykhông tích cực


thú vị, bởi vì có những trường hợp phổ biến với y là không đổi
Sói

1
mod yêu cầu phân chia để nó không chỉ phân chia 1 ở đây, mà có lẽ trình biên dịch có thể tối ưu hóa hai phân chia tương tự thành một.
M.kazem Akhÿ

4

Điều này hoạt động cho các số dương hoặc âm:

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

Nếu có phần còn lại, hãy kiểm tra xem xy có cùng dấu và thêm vào không 1.


3

Tôi muốn có bình luận nhưng tôi không có một đại diện đủ cao.

Theo như tôi biết, đối với các đối số tích cực và ước số là lũy thừa của 2, đây là cách nhanh nhất (được thử nghiệm trong CUDA):

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

Đối với chỉ các đối số tích cực chung, tôi có xu hướng làm như vậy:

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

Sẽ rất thú vị khi xem làm thế nào q = x/y + !!(x % y);để chống lại q = x/y + (x % y == 0);và các q = (x + y - 1) / y;giải pháp hiệu quả khôn ngoan trong CUDA đương đại.
Greg Kramida


-2

Biên dịch với O3, Trình biên dịch thực hiện tối ưu hóa tốt.

q = x / y;
if (x % y)  ++q;
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.