获得整数算术是正确的。正如迄今为止已经充分证明的那样,当你尝试做一个“聪明”的伎俩时,你犯错误的可能性很大。当发现一个缺陷时,更改代码以修复缺陷而不考虑修复是否破坏其他东西并不是一个很好的解决问题的技术。到目前为止,我们已经考虑了五种不同的错误整数算术解决方案来解决这个完全不是特别困难的问题。
解决整数算术问题的正确方法 - 即增加第一次得到正确答案的可能性的方法 - 是仔细处理问题,一次一步地解决问题,并使用良好的工程原理所以。
首先阅读您要替换的内容的规范。整数除法的规范明确规定:该部门将结果舍入为零
当两个操作数具有相同符号时,结果为零或正,当两个操作数具有相反符号时,结果为零或负
如果左操作数是最小的可表示的int而右操作数是-1,则发生溢出。[...]它是实现定义的,是否抛出[ArithmeticException]或溢出未报告,结果值是左操作数的值。
如果右操作数的值为零,则抛出System.DivideByZeroException。
我们想要的是一个整数除法函数,它计算商但是总是向上舍入结果,而不是总是向零。
所以写一个该函数的规范。我们的函数int DivRoundUp(int dividend, int divisor)必须为每个可能的输入定义行为。这种未定义的行为令人深感担忧,所以让我们消除它。我们会说我们的操作有这个规范:如果除数为零,则抛出操作
如果dividend为int.minval且divisor为-1,则抛出操作
如果没有余数 - 除法是'偶数' - 那么返回值就是整数商
否则,它返回大于商的最小整数,也就是说,它总是向上舍入。
现在我们有一个规格,所以我们知道我们可以提出一个可测试的设计。假设我们添加了一个额外的设计标准,即只用整数算法解决问题,而不是将商计算为double,因为在问题陈述中明确拒绝了“双重”解决方案。
那么我们必须计算什么?显然,为了满足我们的规范,同时只保留整数算术,我们需要知道三个事实。首先,什么是整数商?第二,没有剩余部门吗?第三,如果没有,是通过向上或向下舍入计算的整数商?
既然我们有规范和设计,我们就可以开始编写代码了。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;}
这个聪明吗?不,很美?不,短?没有。根据规格正确吗?我相信,但我还没有完全测试过它。虽然它看起来很不错。
我们这里是专业人士; 使用良好的工程实践。研究您的工具,指定所需的行为,首先考虑错误情况,并编写代码以强调其明显的正确性。当你发现一个bug时,在你随机开始交换比较方向并破坏已经有效的东西之前,先考虑一下你的算法是否存在严重缺陷。