C# 如何确保整数除法总是四舍五入?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/921180/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 03:14:40  来源:igfitidea点击:

How can I ensure that a division of integers is always rounded up?

c#math

提问by Karsten

I want to ensure that a division of integers is always rounded up if necessary. Is there a better way than this? There is a lot of casting going on. :-)

我想确保在必要时总是四舍五入整数的除法。还有比这更好的方法吗?有很多铸造正在进行。:-)

(int)Math.Ceiling((double)myInt1 / myInt2)

采纳答案by Eric Lippert

UPDATE: This question was the subject of my blog in January 2013. Thanks for the great question!

更新:这个问题是我 2013 年 1 月博客的主题。谢谢你的好问题!



Getting integer arithmetic correct is hard. As has been demonstrated amply thus far, the moment you try to do a "clever" trick, odds are good that you've made a mistake. And when a flaw is found, changing the code to fix the flaw without considering whether the fix breaks something elseis not a good problem-solving technique. So far we've had I think five different incorrect integer arithmetic solutions to this completely not-particularly-difficult problem posted.

使整数算术正确很难。正如迄今为止充分证明的那样,当你尝试做一个“聪明”的把戏时,你犯错的可能性很大。当发现缺陷时,不考虑修复是否破坏其他问题更改代码以修复缺陷并不是一个好的解决问题的技术。到目前为止,我们已经发布了五种不同的错误整数算术解决方案来解决这个完全不是特别困难的问题。

The right way to approach integer arithmetic problems -- that is, the way that increases the likelihood of getting the answer right the first time - is to approach the problem carefully, solve it one step at a time, and use good engineering principles in doing so.

处理整数算术问题的正确方法——即增加第一次得到正确答案的可能性的方法——是仔细处理问题,一步一步地解决它,并使用良好的工程原理来做所以。

Start by reading the specification for what you're trying to replace.The specification for integer division clearly states:

首先阅读您要替换的内容的规范。整数除法的规范明确指出:

  1. The division rounds the result towards zero

  2. The result is zero or positive when the two operands have the same sign and zero or negative when the two operands have opposite signs

  3. If the left operand is the smallest representable int and the right operand is –1, an overflow occurs. [...] it is implementation-defined as to whether [an ArithmeticException] is thrown or the overflow goes unreported with the resulting value being that of the left operand.

  4. If the value of the right operand is zero, a System.DivideByZeroException is thrown.

  1. 除法将结果向零舍入

  2. 当两个操作数的符号相同时,结果为零或正数,当两个操作数的符号相反时,结果为零或负数

  3. 如果左操作数是可表示的最小 int 且右操作数是 –1,则会发生溢出。[...] 它是实现定义的,关于是否抛出 [an ArithmeticException] 或溢出未报告,结果值是左操作数的值。

  4. 如果右操作数的值为零,则抛出 System.DivideByZeroException。

What we want is an integer division function which computes the quotient but rounds the result always upwards, not always towards zero.

我们想要的是一个整数除法函数,它计算商但总是向上舍入结果,而不总是向零舍入

So write a specification for that function.Our function int DivRoundUp(int dividend, int divisor)must have behaviour defined for every possible input. That undefined behaviour is deeply worrying, so let's eliminate it. We'll say that our operation has this specification:

所以为那个函数写一个规范。我们的函数int DivRoundUp(int dividend, int divisor)必须为每个可能的输入定义行为。这种未定义的行为令人深感担忧,所以让我们消除它。我们会说我们的操作有这个规范:

  1. operation throws if divisor is zero

  2. operation throws if dividend is int.minval and divisor is -1

  3. if there is no remainder -- division is 'even' -- then the return value is the integral quotient

  4. Otherwise it returns the smallestinteger that is greaterthan the quotient, that is, it always rounds up.

  1. 如果除数为零则操作抛出

  2. 如果股息为 int.minval 且除数为 -1,则操作抛出

  3. 如果没有余数——除法是“偶数”——那么返回值是整数商

  4. 否则它返回大于商的最小整数,即它总是向上取整。

Now we have a specification, so we know we can come up with a testable design. Suppose we add an additional design criterion that the problem be solved solely with integer arithmetic, rather than computing the quotient as a double, since the "double" solution has been explicitly rejected in the problem statement.

现在我们有了一个规范,所以我们知道我们可以提出一个可测试的设计。假设我们添加了一个额外的设计标准,即仅使用整数算术来解决问题,而不是将商计算为双精度,因为在问题陈述中明确拒绝了“双精度”解决方案。

So what must we compute? Clearly, to meet our spec while remaining solely in integer arithmetic, we need to know three facts. First, what was the integer quotient? Second, was the division free of remainder? And third, if not, was the integer quotient computed by rounding up or down?

那么我们必须计算什么呢?显然,为了满足我们的规范,同时只停留在整数算术上,我们需要知道三个事实。首先,整数商是多少?第二,除法是否没有余数?第三,如果不是,整数商是通过向上还是向下四舍五入计算的?

Now that we have a specification and a design, we can start writing code.

现在我们有了规范和设计,我们可以开始编写代码了。

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

Is this clever? No. Beautiful? No. Short? No. Correct according to the specification? I believe so, but I have not fully tested it.It looks pretty good though.

这很聪明吗?不,漂亮吗?不。短?否。是否按照规范正确?我相信是这样,但我还没有完全测试它。不过看起来还不错。

We're professionals here; use good engineering practices. Research your tools, specify the desired behaviour, consider error cases first, and write the code to emphasize its obvious correctness.And when you find a bug, consider whether your algorithm is deeply flawed to begin with before you just randomly start swapping the directions of comparisons around and break stuff that already works.

我们是这里的专业人士;使用良好的工程实践。研究您的工具,指定所需的行为,首先考虑错误情况,然后编写代码以强调其明显的正确性。当您发现错误时,请先考虑您的算法是否存在严重缺陷,然后再随机开始交换比较方向并破坏已经有效的东西。

回答by Daniel Brückner

You could use something like the following.

您可以使用以下内容。

a / b + ((Math.Sign(a) * Math.Sign(b) > 0) && (a % b != 0)) ? 1 : 0)

回答by JaredPar

You could write a helper.

你可以写一个助手。

static int DivideRoundUp(int p1, int p2) {
  return (int)Math.Ceiling((double)p1 / p2);
}

回答by joshcomley

Perfect chance to use an extension method:

使用扩展方法的绝佳机会:

public static class Int32Methods
{
    public static int DivideByAndRoundUp(this int number, int divideBy)
    {                        
        return (int)Math.Ceiling((float)number / (float)divideBy);
    }
}

This makes your code uber readable too:

这也使您的代码可读性更强:

int result = myInt.DivideByAndRoundUp(4);

回答by jerryjvl

The final int-based answer

基于 int 的最终答案

For signed integers:

对于有符号整数:

int div = a / b;
if (((a ^ b) >= 0) && (a % b != 0))
    div++;

For unsigned integers:

对于无符号整数:

int div = a / b;
if (a % b != 0)
    div++;

The reasoning for this answer

这个答案的理由

Integer division '/' is defined to round towards zero (7.7.2 of the spec), but we want to round up. This means that negative answers are already rounded correctly, but positive answers need to be adjusted.

整数除法 ' /' 定义为向零舍入(规范的 7.7.2),但我们想向上舍入。这意味着否定答案已经正确四舍五入,但需要调整肯定答案。

Non-zero positive answers are easy to detect, but answer zero is a little trickier, since that can be either the rounding up of a negative value or the rounding down of a positive one.

非零正答案很容易检测,但答案零有点棘手,因为这可以是负值的四舍五入或正值的四舍五入。

The safest bet is to detect when the answer should be positive by checking that the signs of both integers are identical. Integer xor operator '^' on the two values will result in a 0 sign-bit when this is the case, meaning a non-negative result, so the check (a ^ b) >= 0determines that the result should have been positive before rounding. Also note that for unsigned integers, every answer is obviously positive, so this check can be omitted.

最安全的方法是通过检查两个整数的符号是​​否相同来检测答案何时应该为正。^在这种情况下,两个值上的整数异或运算符 ' ' 将导致符号位为 0,这意味着非负结果,因此检查(a ^ b) >= 0确定结果在舍入之前应该为正。另请注意,对于无符号整数,每个答案显然都是正数,因此可以省略此检查。

The only check remaining is then whether any rounding has occurred, for which a % b != 0will do the job.

剩下的唯一检查是是否发生了任何舍入,这a % b != 0将完成这项工作。

Lessons learned

得到教训

Arithmetic (integer or otherwise) isn't nearly as simple as it seems. Thinking carefully required at all times.

算术(整数或其他)并不像看起来那么简单。任何时候都需要仔细思考。

Also, although my final answer is perhaps not as 'simple' or 'obvious' or perhaps even 'fast' as the floating point answers, it has one very strong redeeming quality for me; I have now reasoned through the answer, so I am actually certain it is correct (until someone smarter tells me otherwise -furtive glance in Eric's direction-).

此外,虽然我的最终答案可能不像浮点答案那样“简单”或“显而易见”,甚至“快”,但它对我来说具有很强的救赎品质;我现在已经对答案进行了推理,所以我实际上确定它是正确的(直到有人更聪明地告诉我否则 -偷偷看向 Eric 的方向-)。

To get the same feeling of certainty about the floating point answer, I'd have to do more (and possibly more complicated) thinking about whether there is any conditions under which the floating-point precision might get in the way, and whether Math.Ceilingperhaps does something undesirable on 'just the right' inputs.

为了对浮点答案获得同样的确定感,我必须做更多(并且可能更复杂)的考虑是否存在浮点精度可能会妨碍的任何条件,以及是否Math.Ceiling可能在“恰到好处”的输入上有一些不受欢迎的东西。

The path travelled

走过的路

Replace (note I replaced the second myInt1with myInt2, assuming that was what you meant):

更换(注意我更换了第二myInt1myInt2,假设这是你的意思):

(int)Math.Ceiling((double)myInt1 / myInt2)

with:

和:

(myInt1 - 1 + myInt2) / myInt2

The only caveat being that if myInt1 - 1 + myInt2overflows the integer type you are using, you might not get what you expect.

唯一需要注意的是,如果myInt1 - 1 + myInt2您使用的整数类型溢出,您可能不会得到您期望的结果。

Reason this is wrong: -1000000 and 3999 should give -250, this gives -249

这是错误的原因:-1000000 和 3999 应该给出 -250,这给出 -249

EDIT:
Considering this has the same error as the other integer solution for negative myInt1values, it might be easier to do something like:

编辑:
考虑到这与负值的其他整数解具有相同的错误,执行以下操作myInt1可能更容易:

int rem;
int div = Math.DivRem(myInt1, myInt2, out rem);
if (rem > 0)
  div++;

That should give the correct result in divusing only integer operations.

这应该会在div仅使用整数运算时给出正确的结果。

Reason this is wrong: -1 and -5 should give 1, this gives 0

这是错误的原因:-1 和 -5 应该给出 1,这给出 0

EDIT (once more, with feeling):
The division operator rounds towards zero; for negative results this is exactly right, so only non-negative results need adjustment. Also considering that DivRemjust does a /and a %anyway, let's skip the call (and start with the easy comparison to avoid modulo calculation when it is not needed):

编辑(再次,有感觉):
除法运算符向零舍入;对于负面结果,这是完全正确的,因此只有非负面结果需要调整。还考虑到无论如何DivRem只执行 a/和 a %,让我们跳过调用(并从简单的比较开始,以避免在不需要时进行模计算):

int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
    div++;

Reason this is wrong: -1 and 5 should give 0, this gives 1

这是错误的原因:-1 和 5 应该给 0,这给 1

(In my own defence of the last attempt I should never have attempted a reasoned answer while my mind was telling me I was 2 hours late for sleep)

(在我自己为上次尝试辩护时,我不应该尝试合理的答案,而我的大脑却告诉我我迟到了 2 小时睡觉)

回答by Ian Nelson

All the answers here so far seem rather over-complicated.

到目前为止,这里的所有答案似乎都过于复杂。

In C# and Java, for positive dividend and divisor, you simply need to do:

在 C# 和 Java 中,对于正被除数和除数,您只需要执行以下操作:

( dividend + divisor - 1 ) / divisor 

Source: Number Conversion, Roland Backhouse, 2001

资料来源:数字转换,Roland Backhouse,2001

回答by raboni

The problem with all the solutions here is either that they need a cast or they have a numerical problem. Casting to float or double is always an option, but we can do better.

这里所有解决方案的问题要么是需要演员表,要么是数字问题。转换为 float 或 double 总是一种选择,但我们可以做得更好。

When you use the code of the answer from @jerryjvl

当您使用@jerryjvl 的答案代码时

int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
    div++;

there is a rounding error. 1 / 5 would round up, because 1 % 5 != 0. But this is wrong, because rounding will only occur if you replace the 1 with a 3, so the result is 0.6. We need to find a way to round up when the calculation give us a value greater than or equal to 0.5. The result of the modulo operator in the upper example has a range from 0 to myInt2-1. The rounding will only occur if the remainder is greater than 50% of the divisor. So the adjusted code looks like this:

存在舍入误差。1 / 5 会向上取整,因为 1 % 5 != 0。但这是错误的,因为只有将 1 替换为 3 时才会发生四舍五入,因此结果为 0.6。当计算给出大于或等于 0.5 的值时,我们需要找到一种四舍五入的方法。上例中模运算符的结果范围从 0 到 myInt2-1。仅当余数大于除数的 50% 时才会进行舍入。所以调整后的代码如下所示:

int div = myInt1 / myInt2;
if (myInt1 % myInt2 >= myInt2 / 2)
    div++;

Of course we have a rounding problem at myInt2 / 2 too, but this result will give you a better rounding solution than the other ones on this site.

当然,我们在 myInt2 / 2 也有一个舍入问题,但是这个结果会给你一个比这个站点上其他的更好的舍入解决方案。

回答by Tom Mensink

Some of the above answers use floats, this is inefficient and really not necessary. For unsigned ints this is an efficient answer for int1/int2:

上面的一些答案使用浮点数,这是低效的,真的没有必要。对于无符号整数,这是 int1/int2 的有效答案:

(int1 == 0) ? 0 : (int1 - 1) / int2 + 1;

For signed ints this will not be correct

对于签名整数,这将不正确