Tôi muốn đảm bảo rằng một bộ phận số nguyên luôn được làm tròn nếu cần thiết. Có cách nào tốt hơn thế này không? Có rất nhiều diễn viên đang diễn ra. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Tôi muốn đảm bảo rằng một bộ phận số nguyên luôn được làm tròn nếu cần thiết. Có cách nào tốt hơn thế này không? Có rất nhiều diễn viên đang diễn ra. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Câu trả lời:
CẬP NHẬT: Câu hỏi này là chủ đề của blog của tôi vào tháng 1 năm 2013 . Cảm ơn vì câu hỏi tuyệt vời của bạn!
Lấy số nguyên đúng là khó. Như đã được chứng minh amply cho đến nay, thời điểm bạn cố gắng thực hiện một mẹo "thông minh", tỷ lệ cược là tốt mà bạn đã phạm sai lầm. Và khi một lỗ hổng được tìm thấy, thay đổi mã để sửa lỗi mà không xem xét liệu sửa lỗi có làm hỏng thứ gì khác không phải là một kỹ thuật giải quyết vấn đề tốt. Cho đến nay chúng tôi đã nghĩ rằng năm giải pháp số học số nguyên không chính xác khác nhau cho vấn đề hoàn toàn không đặc biệt khó khăn này được đăng.
Cách đúng đắn để tiếp cận các vấn đề số học số nguyên - đó là cách làm tăng khả năng nhận được câu trả lời ngay lần đầu tiên - là tiếp cận vấn đề một cách cẩn thận, giải quyết từng bước một và sử dụng các nguyên tắc kỹ thuật tốt để thực hiện vì thế.
Bắt đầu bằng cách đọc thông số kỹ thuật cho những gì bạn đang cố gắng thay thế. Các đặc điểm kỹ thuật cho phân chia số nguyên nêu rõ:
Bộ phận làm tròn kết quả về không
Kết quả bằng 0 hoặc dương khi hai toán hạng có cùng dấu và 0 hoặc âm khi hai toán hạng có dấu trái ngược nhau
Nếu toán hạng bên trái là int đại diện nhỏ nhất và toán hạng bên phải là1, thì xảy ra tràn. [...] Nó được định nghĩa theo triển khai là liệu [ArithaturesException] có bị ném hay tràn không được báo cáo với giá trị kết quả là giá trị của toán hạng bên trái.
Nếu giá trị của toán hạng bên phải bằng 0, System.DivideByZeroException sẽ bị ném.
Những gì chúng ta muốn là một hàm chia số nguyên tính toán thương số nhưng làm tròn kết quả luôn luôn hướng lên trên , không phải luôn luôn hướng về không .
Vì vậy, viết một đặc điểm kỹ thuật cho chức năng đó. Chức năng của chúng tôi int DivRoundUp(int dividend, int divisor)
phải có hành vi được xác định cho mọi đầu vào có thể. Hành vi không xác định đó là rất đáng lo ngại, vì vậy hãy loại bỏ nó. Chúng tôi sẽ nói rằng hoạt động của chúng tôi có đặc điểm kỹ thuật này:
hoạt động ném nếu ước số bằng không
hoạt động ném nếu cổ tức là int.minval và ước số là -1
nếu không có phần còn lại - phép chia là 'chẵn' - thì giá trị trả về là thương số nguyên
Mặt khác, nó trả về số nguyên nhỏ nhất lớn hơn thương số, nghĩa là nó luôn làm tròn lên.
Bây giờ chúng tôi có một đặc điểm kỹ thuật, vì vậy chúng tôi biết rằng chúng tôi có thể đưa ra một thiết kế thử nghiệm . Giả sử chúng ta thêm một tiêu chí thiết kế bổ sung rằng vấn đề chỉ được giải quyết bằng số học số nguyên, thay vì tính thương số là gấp đôi, vì giải pháp "nhân đôi" đã bị từ chối rõ ràng trong tuyên bố vấn đề.
Vậy chúng ta phải tính toán cái gì? Rõ ràng, để đáp ứng thông số kỹ thuật của chúng tôi trong khi chỉ còn lại trong số học số nguyên, chúng tôi cần biết ba sự thật. Đầu tiên, số nguyên là gì? Thứ hai, là sự phân chia miễn phí? Và thứ ba, nếu không, là số nguyên được tính bằng cách làm tròn lên hoặc xuống?
Bây giờ chúng ta có một đặc tả và thiết kế, chúng ta có thể bắt đầu viết mã.
public static int DivRoundUp(int dividend, int divisor)
{
if (divisor == 0 ) throw ...
if (divisor == -1 && dividend == Int32.MinValue) throw ...
int roundedTowardsZeroQuotient = dividend / divisor;
bool dividedEvenly = (dividend % divisor) == 0;
if (dividedEvenly)
return roundedTowardsZeroQuotient;
// At this point we know that divisor was not zero
// (because we would have thrown) and we know that
// dividend was not zero (because there would have been no remainder)
// Therefore both are non-zero. Either they are of the same sign,
// or opposite signs. If they're of opposite sign then we rounded
// UP towards zero so we're done. If they're of the same sign then
// we rounded DOWN towards zero, so we need to add one.
bool wasRoundedDown = ((divisor > 0) == (dividend > 0));
if (wasRoundedDown)
return roundedTowardsZeroQuotient + 1;
else
return roundedTowardsZeroQuotient;
}
Điều này có thông minh không? Không đẹp? Số ngắn? Không đúng theo đặc điểm kỹ thuật? Tôi tin là như vậy, nhưng tôi chưa kiểm tra đầy đủ. Có vẻ khá tốt mặc dù.
Chúng tôi là những chuyên gia ở đây; sử dụng thực hành kỹ thuật tốt. Nghiên cứu các công cụ của bạn, xác định hành vi mong muốn, xem xét các trường hợp lỗi trước và viết mã để nhấn mạnh tính chính xác rõ ràng của nó. Và khi bạn tìm thấy một lỗi, hãy xem xét liệu thuật toán của bạn có thiếu sót sâu sắc để bắt đầu hay không trước khi bạn ngẫu nhiên bắt đầu hoán đổi các hướng so sánh xung quanh và phá vỡ những thứ đã hoạt động.
Tất cả các câu trả lời ở đây cho đến nay có vẻ khá phức tạp.
Trong C # và Java, đối với cổ tức và ước số dương, bạn chỉ cần thực hiện:
( dividend + divisor - 1 ) / divisor
((13-1)%3)+1)
cho 1 kết quả. Lấy đúng loại phân chia, 1+(dividend - 1)/divisor
cho kết quả giống như câu trả lời cho cổ tức và ước số dương. Ngoài ra, không có vấn đề tràn, tuy nhiên chúng có thể là nhân tạo.
Đối với số nguyên đã ký:
int div = a / b;
if (((a ^ b) >= 0) && (a % b != 0))
div++;
Đối với số nguyên không dấu:
int div = a / b;
if (a % b != 0)
div++;
Phân chia số nguyên ' /
' được xác định để làm tròn về 0 (7.7.2 của thông số kỹ thuật), nhưng chúng tôi muốn làm tròn số. Điều này có nghĩa là các câu trả lời phủ định đã được làm tròn một cách chính xác, nhưng các câu trả lời tích cực cần được điều chỉnh.
Câu trả lời tích cực khác không rất dễ phát hiện, nhưng câu trả lời bằng 0 khó hơn một chút, vì đó có thể là làm tròn giá trị âm hoặc làm tròn số dương.
Đặt cược an toàn nhất là phát hiện khi nào câu trả lời phải dương tính bằng cách kiểm tra xem các dấu hiệu của cả hai số nguyên có giống nhau không. Toán tử xor ' ^
' trên hai giá trị sẽ dẫn đến bit 0 khi trường hợp này có nghĩa là kết quả không âm, do đó kiểm tra (a ^ b) >= 0
xác định rằng kết quả phải có giá trị dương trước khi làm tròn. Cũng lưu ý rằng đối với các số nguyên không dấu, mọi câu trả lời rõ ràng là tích cực, vì vậy kiểm tra này có thể được bỏ qua.
Việc kiểm tra duy nhất còn lại là liệu có bất kỳ làm tròn nào đã xảy ra hay không, a % b != 0
sẽ thực hiện công việc.
Số học (số nguyên hoặc cách khác) gần như không đơn giản. Suy nghĩ cẩn thận yêu cầu mọi lúc.
Ngoài ra, mặc dù câu trả lời cuối cùng của tôi có lẽ không phải là "đơn giản" hay "rõ ràng" hay thậm chí là "nhanh" như câu trả lời dấu phẩy động, nó có một chất lượng hoàn lại rất mạnh đối với tôi; Bây giờ tôi đã suy luận thông qua câu trả lời, vì vậy tôi thực sự chắc chắn rằng nó là chính xác (cho đến khi ai đó thông minh hơn nói với tôi - ánh mắt giận dữ theo hướng của Eric -).
Để có cùng cảm giác chắc chắn về câu trả lời dấu phẩy động, tôi phải suy nghĩ nhiều hơn (và có thể phức tạp hơn) về việc liệu có bất kỳ điều kiện nào mà độ chính xác của dấu phẩy động có thể cản trở hay Math.Ceiling
không , và có lẽ một cái gì đó không mong muốn trên đầu vào 'chỉ bên phải'.
Thay thế (lưu ý tôi đã thay thế thứ hai myInt1
bằng myInt2
, giả sử đó là những gì bạn muốn nói):
(int)Math.Ceiling((double)myInt1 / myInt2)
với:
(myInt1 - 1 + myInt2) / myInt2
Nhắc nhở duy nhất là nếu myInt1 - 1 + myInt2
vượt quá loại số nguyên bạn đang sử dụng, bạn có thể không nhận được những gì bạn mong đợi.
Lý do điều này là sai : -1000000 và 3999 nên cho -250, điều này mang lại -249
EDIT:
Xem xét điều này có cùng lỗi với giải pháp số nguyên khác cho các myInt1
giá trị âm , có thể dễ dàng hơn để làm một cái gì đó như:
int rem;
int div = Math.DivRem(myInt1, myInt2, out rem);
if (rem > 0)
div++;
Điều đó sẽ cho kết quả chính xác khi div
chỉ sử dụng các phép toán số nguyên.
Lý do điều này là sai : -1 và -5 nên cho 1, điều này cho 0
EDIT (một lần nữa, với cảm giác):
Toán tử chia làm tròn về 0; Đối với kết quả âm tính, điều này là hoàn toàn chính xác, vì vậy chỉ những kết quả không âm mới cần điều chỉnh. Ngoài ra, xem xét rằng DivRem
chỉ cần thực hiện một /
và một %
, hãy bỏ qua cuộc gọi (và bắt đầu với việc so sánh dễ dàng để tránh tính toán modulo khi không cần thiết):
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
Lý do điều này là sai : -1 và 5 nên cho 0, điều này cho 1
(Để bảo vệ nỗ lực cuối cùng của tôi, tôi không bao giờ nên cố gắng trả lời có lý do trong khi tâm trí tôi nói với tôi rằng tôi đã trễ 2 tiếng để ngủ)
Cơ hội hoàn hảo để sử dụng phương pháp mở rộng:
public static class Int32Methods
{
public static int DivideByAndRoundUp(this int number, int divideBy)
{
return (int)Math.Ceiling((float)number / (float)divideBy);
}
}
Điều này làm cho mã của bạn uber có thể đọc được:
int result = myInt.DivideByAndRoundUp(4);
Bạn có thể viết một người trợ giúp.
static int DivideRoundUp(int p1, int p2) {
return (int)Math.Ceiling((double)p1 / p2);
}
Bạn có thể sử dụng một cái gì đó như sau.
a / b + ((Math.Sign(a) * Math.Sign(b) > 0) && (a % b != 0)) ? 1 : 0)
Một số câu trả lời trên sử dụng phao, điều này không hiệu quả và thực sự không cần thiết. Đối với ints không dấu, đây là một câu trả lời hiệu quả cho int1 / int2:
(int1 == 0) ? 0 : (int1 - 1) / int2 + 1;
Đối với ints đã ký, điều này sẽ không chính xác
Vấn đề với tất cả các giải pháp ở đây là họ cần diễn viên hoặc họ có vấn đề về số. Đúc để nổi hoặc gấp đôi luôn là một lựa chọn, nhưng chúng ta có thể làm tốt hơn.
Khi bạn sử dụng mã của câu trả lời từ @jerryjvl
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
có lỗi làm tròn số. 1/5 sẽ làm tròn lên, vì 1% 5! = 0. Nhưng điều này là sai, vì làm tròn sẽ chỉ xảy ra nếu bạn thay 1 bằng 3, vì vậy kết quả là 0,6. Chúng ta cần tìm cách làm tròn lên khi phép tính cho chúng ta một giá trị lớn hơn hoặc bằng 0,5. Kết quả của toán tử modulo trong ví dụ trên có phạm vi từ 0 đến myInt2-1. Việc làm tròn sẽ chỉ xảy ra nếu phần còn lại lớn hơn 50% số chia. Vì vậy, mã điều chỉnh trông như thế này:
int div = myInt1 / myInt2;
if (myInt1 % myInt2 >= myInt2 / 2)
div++;
Tất nhiên chúng tôi cũng có một vấn đề làm tròn tại myInt2 / 2, nhưng kết quả này sẽ cung cấp cho bạn một giải pháp làm tròn tốt hơn so với các giải pháp khác trên trang web này.