我怎样才能确保一个整数的分割总是四舍五入的?

我想确保整数的分割总是在必要时进行。 有没有比这更好的方法? 有很多铸造正在进行。 🙂

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

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


获得整数算术正确是困难的。 到目前为止已经充分certificate,当你尝试做一个“聪明”的伎俩的时候,你犯了一个错误的几率是很好的。 当发现缺陷时,修改代码来修复缺陷, 而不考虑修复是否会破坏别的东西,这不是一个好的解决问题的技巧。 到目前为止,我们已经有了五个不同的不正确的整数算术解决scheme来解决这个完全不是特别困难的问题。

解决整数算术问题的正确方法 – 也就是增加第一次得到答案的可能性的方法 – 就是小心处理问题,一步一步地解决问题,并在工作中使用良好的工程原理所以。

首先阅读您要replace的规范。 整数除法规范明确规定:

  1. 该部门将结果向零调整

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

  3. 如果左操作数是最小可表示的int,右操作数是-1,则发生溢出。 […]是实现定义是否抛出[ArithmeticException],或者溢出未被报告,结果值是左操作数的值。

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

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

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

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

  2. 如果dividend是int.minval和divisor是-1,则操作会抛出

  3. 如果没有余数 – 除法是“偶数” – 则返回值是积分商

  4. 否则,它返回大于商的最小整数,即总是四舍五入。

现在我们有一个规范,所以我们知道我们可以拿出一个可testing的devise 。 假设我们增加了一个额外的devise标准,即只用整数algorithm解决问题,而不是将商计算为双数,因为在问题陈述中明确拒绝了“双重”解决scheme。

那么我们要计算什么? 显然,为了满足我们的规范,而只保留整数算术,我们需要知道三个事实。 首先,整数商是什么? 其次,这个部门是否没有剩余? 第三,如果不是的话,是整数商数是通过四舍五入来计算的吗?

现在我们有一个规范和一个devise,我们可以开始编写代码。

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

这很聪明吗? 不是美丽的 不是, 不是。根据规范正确吗? 我相信,但我还没有完全testing。 它看起来不错,虽然。

我们是专业人士 使用良好的工程实践。 研究你的工具,指定所需的行为,首先考虑错误案例,并编写代码以强调其明显的正确性。 当你发现一个bug的时候,考虑一下你的algorithm是否有很大的缺陷,然后你只是随机地开始交换比较的方向,并且破坏已经有效的东西。

最终的基于int的答案

对于有符号整数:

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

对于无符号整数:

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

这个答案的推理

整数除法“ / ”被定义为朝向零(规范的7.7.2),但是我们想要四舍五入。 这意味着负面的答案已经被正确地舍入,但是正面的答案需要被调整。

非零的肯定答案很容易被发现,但回答零是有点棘手,因为这可能是一个负值的四舍五入或四舍五入一个正面的。

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

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

得到教训

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

而且,虽然我的最终答案可能不像浮点数的“简单”,“显而易见”或者甚至是“快速”,但是对我来说,它有一个非常强大的救赎品质。 我现在通过这个答案来推理,所以我确定它是正确的(直到有人更聪明地告诉我,否则偷偷摸摸埃里克的方向 )。

为了得到与浮点数答案相同的确定感,我不得不做更多(也可能更复杂的)关于是否存在浮点精度可能会影响到的条件以及Math.Ceiling可能会在“正确”的input上做一些不合要求的事情。

走过的路

replace(注意,我用myInt2replace了第二个myInt1 ,假设这是你的意思):

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

有:

 (myInt1 - 1 + myInt2) / myInt2 

唯一需要注意的是,如果myInt1 - 1 + myInt2溢出了正在使用的整数types,您可能得不到您所期望的。

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

编辑:
考虑到这与负myInt1值的其他整数解决scheme具有相同的错误,可能会更容易做到这一点:

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

这应该只给出正确的结果div使用整数操作。

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

编辑(再次感慨):
该分部操作员向零转向; 对于负面的结果,这是完全正确的,所以只有非负面的结果需要调整。 另外考虑到DivRem只是做一个/和一个% ,让我们跳过这个调用(并且从简单的比较开始,避免在不需要时进行模计算):

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

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

(在我自己的最后一次尝试的辩护,我不应该尝试一个合理的答案,而我的头脑告诉我,我睡了2个小时)

这里所有的答案似乎都过于复杂了。

在C#和Java中,为了获得正面的分红和除数,你只需要做:

 ( dividend + divisor - 1 ) / divisor 

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

完美的机会使用扩展方法:

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

这使得你的代码也是可读的:

 int result = myInt.DivideByAndRoundUp(4); 

你可以写一个帮手。

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

你可以使用下面的东西。

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

这里所有的解决scheme的问题是他们需要一个演员,或者他们有一个数字问题。 铸造浮动或双重总是一个select,但我们可以做得更好。

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

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

有一个舍入错误。 1/5会凑整,因为1%5!= 0。但这是错误的,因为四舍五入只会发生,如果你用1代替1,所以结果是0.6。 当计算给我们一个大于或等于0.5的值时,我们需要find一个收集方法。 上例中模运算符的结果范围从0到myInt2-1。 只有当余数大于除数的50%时才会发生舍入。 所以调整后的代码如下所示:

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

当然,我们在myInt2 / 2也有一个四舍五入的问题,但是这个结果会给你一个更好的舍入解决scheme。