Làm thế nào để làm tròn kết quả của phép chia số nguyên?


335

Tôi đang suy nghĩ cụ thể về cách hiển thị các điều khiển phân trang, khi sử dụng ngôn ngữ như C # hoặc Java.

Nếu tôi có x mục mà tôi muốn hiển thị theo từng khối y trên mỗi trang thì sẽ cần bao nhiêu trang?


1
Tui bỏ lỡ điều gì vậy? y / x + 1 hoạt động rất tốt (miễn là bạn biết / toán tử luôn làm tròn xuống).
rikkit

51
@rikkit - nếu y và x bằng nhau, y / x + 1 là quá cao.
Ian Nelson

1
Đối với bất cứ ai bây giờ tìm thấy điều này, câu trả lời cho câu hỏi lừa đảo này tránh được việc chuyển đổi không cần thiết thành gấp đôi tránh những lo ngại tràn ra ngoài việc cung cấp một lời giải thích rõ ràng.
ZX9

2
@IanNelson nói chung hơn nếu xchia hết cho y, y/x + 1sẽ là một mức quá cao.
Ohad Schneider

1
@ ZX9 Không, nó không tránh được những lo ngại tràn. Đó chính xác là giải pháp tương tự như Ian Nelson đăng ở đây.
user247702

Câu trả lời:


478

Tìm thấy một giải pháp thanh lịch:

int pageCount = (records + recordsPerPage - 1) / recordsPerPage;

Nguồn: Chuyển đổi số, Roland Backhouse, 2001


12
-1 vì lỗi tràn được chỉ ra bởi Brandon DuRette
vây

30
Ông Rõ ràng nói: Hãy nhớ đảm bảo rằng recordsPerPage không phải là số không
Adam Gent

7
Làm tốt lắm, tôi không thể tin C # không có trần nguyên.
gosukiwi

2
Yup, ở đây tôi đang ở giữa năm 2017 tình cờ thấy câu trả lời tuyệt vời này sau khi thử một vài cách tiếp cận phức tạp hơn nhiều.
Mifo

1
Đối với các ngôn ngữ có toán tử phân chia Euclidian thích hợp như Python, một cách tiếp cận thậm chí đơn giản hơn sẽ là pageCount = -((-records) // recordsPerPage).
supercat

194

Chuyển đổi sang điểm nổi và trở lại có vẻ như là một sự lãng phí rất lớn thời gian ở cấp độ CPU.

Giải pháp của Ian Nelson:

int pageCount = (records + recordsPerPage - 1) / recordsPerPage;

Có thể được đơn giản hóa để:

int pageCount = (records - 1) / recordsPerPage + 1;

AFAICS, đây không phải là lỗi tràn mà Brandon DuRette đã chỉ ra và vì nó chỉ sử dụng một lần, nên bạn không cần lưu trữ recordsPerPage đặc biệt nếu nó xuất phát từ một hàm đắt tiền để lấy giá trị từ tệp cấu hình hoặc một cái gì đó

Tức là điều này có thể không hiệu quả, nếu config.fetch_value sử dụng tra cứu cơ sở dữ liệu hoặc một cái gì đó:

int pageCount = (records + config.fetch_value('records per page') - 1) / config.fetch_value('records per page');

Điều này tạo ra một biến bạn không thực sự cần, có thể có ý nghĩa bộ nhớ (nhỏ) và chỉ cần gõ quá nhiều:

int recordsPerPage = config.fetch_value('records per page')
int pageCount = (records + recordsPerPage - 1) / recordsPerPage;

Đây là tất cả một dòng và chỉ tìm nạp dữ liệu một lần:

int pageCount = (records - 1) / config.fetch_value('records per page') + 1;

5
+1, vấn đề về hồ sơ bằng 0 vẫn trả về 1 trang Số tiền thực sự tiện dụng, vì tôi vẫn muốn có 1 trang, hiển thị hàng giữ chỗ / hàng giả "không có hồ sơ phù hợp với tiêu chí của bạn", giúp tránh mọi vấn đề "đếm 0 trang" trong bất cứ điều gì kiểm soát phân trang bạn sử dụng.
Timothy Walters

27
Xin lưu ý rằng hai giải pháp không trả về cùng một trangCount cho các bản ghi bằng không. Phiên bản đơn giản hóa này sẽ trả về 1 pageCount cho các bản ghi bằng 0, trong khi phiên bản Roland Backhouse trả về 0 pageCount. Tốt nếu đó là những gì bạn mong muốn, nhưng hai phương trình không tương đương khi được thực hiện bằng phép chia số nguyên kiểu C # / Java.
Ian Nelson

10
Chỉnh sửa nhỏ để rõ ràng cho những người quét nó và thiếu các dấu tích khi thay đổi mô phỏng từ giải pháp Nelson (giống như tôi đã làm lần đầu tiên!), đơn giản hóa với dấu ngoặc là ... int pageCount = ((records - 1) / recordsPerPage) + 1;
dave heywood

1
Bạn nên thêm dấu ngoặc đơn vào phiên bản đơn giản hóa để nó không phụ thuộc vào một thứ tự hoạt động cụ thể. tức là, ((records - 1) / recordsPerPage) + 1.
Martin

1
@Ian, câu trả lời này không LUÔN trả về 1. Nó có thể trả về 0 nếu recordsPerPage của bạn là "1" và có 0 bản ghi : -1 / 1 + 1 = 0. Mặc dù đó không phải là một sự xuất hiện siêu phổ biến, nhưng điều quan trọng cần ghi nhớ nếu bạn cho phép người dùng điều chỉnh kích thước trang. Vì vậy, không cho phép người dùng có kích thước trang là 1, hãy kiểm tra kích thước trang hoặc cả hai (có thể tốt hơn để tránh hành vi không mong muốn).
Michael

81

Đối với C #, giải pháp là truyền các giá trị thành gấp đôi (vì Math.Cading mất gấp đôi):

int nPages = (int)Math.Ceiling((double)nItems / (double)nItemsPerPage);

Trong java, bạn nên làm tương tự với Math.ceil ().


4
Tại sao câu trả lời này lại đi xuống khi op yêu cầu C # một cách rõ ràng!
felickz

2
Bạn cũng cần truyền đầu ra tới intMath.Ceiling trả về a doublehoặc decimal, tùy thuộc vào loại đầu vào.
DanM7

14
bởi vì nó cực kỳ kém hiệu quả
Zar Shardan

6
Nó có thể không hiệu quả nhưng nó cực kỳ dễ hiểu. Việc tính toán số lượng trang thường được thực hiện một lần cho mỗi yêu cầu, mọi tổn thất về hiệu suất sẽ không thể đo lường được.
Jared Kells

1
Nó hầu như không dễ đọc hơn cái này "(cổ tức + (số chia - 1)) / số chia;" cũng chậm và yêu cầu thư viện toán học.
cuộn

68

Điều này sẽ cung cấp cho bạn những gì bạn muốn. Bạn chắc chắn sẽ muốn x mục chia cho y mục trên mỗi trang, vấn đề là khi số lượng không đồng đều xuất hiện, vì vậy nếu có một trang chúng tôi cũng muốn thêm một trang.

int x = number_of_items;
int y = items_per_page;

// with out library
int pages = x/y + (x % y > 0 ? 1 : 0)

// with library
int pages = (int)Math.Ceiling((double)x / (double)y);

5
x / y + !! (x% y) tránh nhánh cho các ngôn ngữ giống như C. Odds là tốt, tuy nhiên, trình biên dịch của bạn vẫn đang làm điều đó.
Rhys Ulerich

2
+1 vì không tràn ra như các câu trả lời ở trên ... mặc dù việc chuyển đổi số nguyên thành gấp đôi chỉ cho Math.ceiling và sau đó quay lại là một ý tưởng tồi trong mã nhạy cảm hiệu năng.
Bánh răng

3
@RhysUlerich không hoạt động trong c # (không thể chuyển đổi trực tiếp int thành bool). Tôi nghĩ rằng giải pháp của rjmunro là cách duy nhất để tránh phân nhánh.
smead

18

Giải pháp toán học số nguyên mà Ian cung cấp là tốt, nhưng bị lỗi tràn số nguyên. Giả sử các biến là tất cả int, giải pháp có thể được viết lại để sử dụng longtoán học và tránh lỗi:

int pageCount = (-1L + records + recordsPerPage) / recordsPerPage;

Nếu recordslà một long, lỗi vẫn còn. Các giải pháp mô-đun không có lỗi.


4
Tôi không nghĩ rằng bạn thực sự sẽ gặp lỗi này trong kịch bản được trình bày. 2 ^ 31 hồ sơ là khá nhiều để có trang thông qua.
rjmunro


@finnw: AFAICS, không có ví dụ trong thế giới thực trên trang đó, chỉ là một báo cáo về việc người khác tìm thấy lỗi trong một kịch bản lý thuyết.
rjmunro

5
Vâng, tôi đã được mô phạm trong việc chỉ ra lỗi. Nhiều lỗi có thể tồn tại vĩnh viễn mà không bao giờ gây ra bất kỳ vấn đề. Một lỗi có dạng tương tự đã tồn tại trong quá trình triển khai binarySearch của JDK trong khoảng chín năm, trước khi ai đó báo cáo ( googleresearch.blogspot.com/2006/06/ trên ). Tôi đoán câu hỏi là, bất kể bạn có thể gặp phải lỗi này như thế nào, tại sao không khắc phục nó trước?
Brandon DuRette

4
Ngoài ra, cần lưu ý rằng không chỉ số lượng phần tử được phân trang là vấn đề, mà còn là kích thước trang. Vì vậy, nếu bạn đang xây dựng thư viện và ai đó chọn không trang bằng cách chuyển 2 ^ 31-1 (Integer.MAX_VALUE) làm kích thước trang, thì lỗi được kích hoạt.
Brandon DuRette

7

Một biến thể của câu trả lời của Nick Berardi mà tránh một nhánh:

int q = records / recordsPerPage, r = records % recordsPerPage;
int pageCount = q - (-r >> (Integer.SIZE - 1));

Lưu ý: (-r >> (Integer.SIZE - 1))bao gồm bit dấu của r, được lặp lại 32 lần (nhờ phần mở rộng dấu của >>toán tử.) Điều này ước tính thành 0 nếu rbằng 0 hoặc âm, -1 nếu rlà dương. Vì vậy, trừ nó ra qcó tác dụng thêm 1 nếu records % recordsPerPage > 0.


4

Đối với các bản ghi == 0, giải pháp của rjmunro cho 1. Giải pháp đúng là 0. Điều đó nói rằng, nếu bạn biết rằng các bản ghi> 0 (và tôi chắc chắn rằng tất cả chúng ta đều giả sử recordsPerPage> 0), thì giải pháp rjmunro cho kết quả chính xác và không có bất kỳ vấn đề tràn.

int pageCount = 0;
if (records > 0)
{
    pageCount = (((records - 1) / recordsPerPage) + 1);
}
// no else required

Tất cả các giải pháp toán học số nguyên sẽ hiệu quả hơn bất kỳ giải pháp dấu phẩy động nào.


Phương pháp này không chắc là một nút cổ chai hiệu suất. Và nếu có, bạn cũng nên xem xét chi phí của chi nhánh.
vây

4

Cần một phương pháp mở rộng:

    public static int DivideUp(this int dividend, int divisor)
    {
        return (dividend + (divisor - 1)) / divisor;
    }

Không có kiểm tra nào ở đây (tràn, DivideByZerov.v.), vui lòng thêm nếu bạn muốn. Nhân tiện, đối với những người lo lắng về phí gọi phương thức, các hàm đơn giản như thế này có thể được trình biên dịch nội tuyến đưa vào, vì vậy tôi không nghĩ đó là nơi cần quan tâm. Chúc mừng.

PS bạn cũng có thể thấy hữu ích khi nhận thức được điều này (phần còn lại):

    int remainder; 
    int result = Math.DivRem(dividend, divisor, out remainder);

1
Điều này là không chính xác. Ví dụ: DivideUp(4, -2)trả về 0 (nên là -2). Nó chỉ đúng với các số nguyên không âm mà không rõ ràng từ câu trả lời hoặc từ giao diện của hàm.
Thash

6
Thash, tại sao bạn không làm điều gì đó hữu ích như thêm kiểm tra thêm sau đó nếu số đó âm tính, thay vì bỏ phiếu trả lời của tôi và đưa ra tuyên bố không chính xác: "Điều này không chính xác", trong khi thực tế nó chỉ là một cạnh trường hợp Tôi đã nói rõ rằng bạn nên thực hiện các kiểm tra khác trước: "Không có kiểm tra nào ở đây (tràn, DivideByZero, v.v.), vui lòng thêm nếu bạn muốn ."
Nicholas Petersen

1
Câu hỏi đề cập đến "Tôi đang suy nghĩ cụ thể về cách hiển thị các điều khiển phân trang ", do đó, các số âm sẽ nằm ngoài giới hạn. Một lần nữa, chỉ cần làm một cái gì đó hữu ích và đề nghị kiểm tra thêm nếu bạn muốn, đó là một người nỗ lực nhóm.
Nicholas Petersen

Tôi không có ý thô lỗ và tôi xin lỗi nếu bạn làm theo cách đó. Câu hỏi là "Làm thế nào để làm tròn kết quả của phép chia số nguyên". Các tác giả đã đề cập phân trang nhưng những người khác có thể có nhu cầu khác nhau. Tôi nghĩ sẽ tốt hơn nếu chức năng của bạn bằng cách nào đó phản ánh rằng nó không hoạt động đối với các số nguyên âm vì nó không rõ ràng từ giao diện (ví dụ: một loại tên hoặc loại đối số khác nhau). Để làm việc với các số nguyên âm, ví dụ, bạn có thể lấy một giá trị tuyệt đối của cổ tức và ước số và nhân kết quả bằng dấu của nó.
Thash

2

Một cách khác là sử dụng hàm mod () (hoặc '%'). Nếu có phần dư khác không thì tăng kết quả số nguyên của phép chia.


1

Tôi làm như sau, xử lý bất kỳ tràn:

var totalPages = totalResults.IsDivisble(recordsperpage) ? totalResults/(recordsperpage) : totalResults/(recordsperpage) + 1;

Và sử dụng tiện ích mở rộng này nếu có 0 kết quả:

public static bool IsDivisble(this int x, int n)
{
           return (x%n) == 0;
}

Ngoài ra, đối với số trang hiện tại (không được hỏi nhưng có thể hữu ích):

var currentPage = (int) Math.Ceiling(recordsperpage/(double) recordsperpage) + 1;

0

Thay thế để loại bỏ phân nhánh trong thử nghiệm cho không:

int pageCount = (records + recordsPerPage - 1) / recordsPerPage * (records != 0);

Không chắc chắn nếu điều này sẽ hoạt động trong C #, nên làm trong C / C ++.


-1

Một phương pháp chung, mà kết quả mà bạn có thể lặp lại có thể được quan tâm:

public static Object[][] chunk(Object[] src, int chunkSize) {

    int overflow = src.length%chunkSize;
    int numChunks = (src.length/chunkSize) + (overflow>0?1:0);
    Object[][] dest = new Object[numChunks][];      
    for (int i=0; i<numChunks; i++) {
        dest[i] = new Object[ (i<numChunks-1 || overflow==0) ? chunkSize : overflow ];
        System.arraycopy(src, i*chunkSize, dest[i], 0, dest[i].length); 
    }
    return dest;
}

Quả ổi có một phương pháp tương tự ( Lists.partition(List, int)) và trớ trêu thay, size()phương pháp của kết quả List(kể từ r09) bị lỗi tràn được đề cập trong câu trả lời của Brandon DuRette .
vây

-2

Tôi có một nhu cầu tương tự khi tôi cần chuyển đổi Phút thành giờ & phút. Những gì tôi đã sử dụng là:

int hrs = 0; int mins = 0;

float tm = totalmins;

if ( tm > 60 ) ( hrs = (int) (tm / 60);

mins = (int) (tm - (hrs * 60));

System.out.println("Total time in Hours & Minutes = " + hrs + ":" + mins);

-2

Sau đây nên làm tròn tốt hơn các giải pháp trên, nhưng với chi phí hiệu năng (do tính toán dấu phẩy động 0,5 * rctDenominator):

uint64_t integerDivide( const uint64_t& rctNumerator, const uint64_t& rctDenominator )
{
  // Ensure .5 upwards is rounded up (otherwise integer division just truncates - ie gives no remainder)
  return (rctDenominator == 0) ? 0 : (rctNumerator + (int)(0.5*rctDenominator)) / rctDenominator;
}

-4

Bạn sẽ muốn thực hiện phép chia dấu phẩy động, sau đó sử dụng hàm trần để làm tròn giá trị cho số nguyên tiếp theo.

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.