我想确保在必要时总是对整数除法进行四舍五入。有没有比这更好的方法了?正在进行很多强制转换。:-)
(int)Math.Ceiling((double)myInt1 / myInt2)
我想确保在必要时总是对整数除法进行四舍五入。有没有比这更好的方法了?正在进行很多强制转换。:-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Answers:
更新:这个问题是我2013年1月博客的主题。感谢您提出的好问题!
要使整数算术正确,是很难的。到目前为止,已经充分证明了,当您尝试做一个“聪明”的窍门时,很可能您犯了一个错误。而且,当发现缺陷时,更改代码以修正缺陷而不考虑修正是否破坏了其他问题并不是一种好的解决问题的技术。到目前为止,我已经发布了五个完全不特别困难的问题的五个不同的不正确的整数算术解决方案。
解决整数算术问题的正确方法(即增加首次获得正确答案的可能性的方法)是认真解决问题,一次解决一个问题,并在执行过程中使用良好的工程原理所以。
首先阅读要替换的规范。整数除法规范明确指出:
除法将结果舍入为零
当两个操作数具有相同的符号时结果为零或正,而当两个操作数具有相反的符号时结果为零或负。
如果左操作数是可表示的最小int,而右操作数是–1,则发生溢出。[...]由实现定义,是抛出[ArithmeticException]还是未报告溢出,其结果值为左操作数。
如果右操作数的值为零,则抛出System.DivideByZeroException。
我们想要的是一个整数除法函数,该函数可计算商,但将结果始终向上舍入,而不总是向零舍入。
因此,编写该功能的规范。我们的函数int DivRoundUp(int dividend, int divisor)
必须为每个可能的输入定义行为。这种不确定的行为令人深感忧虑,所以让我们消除它。我们将说我们的操作具有以下规范:
如果除数为零,则操作抛出
如果被除数为int.minval并且除数为-1,则操作抛出
如果没有余数-除法为“偶数”-则返回值为整数商
否则,它将返回大于商的最小整数,即,它总是四舍五入。
现在我们有了一个规范,因此我们知道我们可以提出一个可测试的设计。假设我们添加了一个附加的设计准则,该问题仅用整数算术即可解决,而不是将商计算为双精度数,因为在问题陈述中明确拒绝了“双精度”解。
那么我们必须计算什么呢?显然,要满足我们的规范,同时仅保留整数算术,我们需要知道三个事实。首先,整数商是多少?第二,除法是否没有余数?第三,如果不是,则是通过四舍五入来计算整数商吗?
现在我们有了规范和设计,我们可以开始编写代码了。
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;
}
这很聪明吗?不,美丽吗?不,简短吗?否。根据规格正确吗?我相信,但是我还没有完全测试它。看起来不错。
我们是这里的专业人士;使用良好的工程实践。研究您的工具,指定所需的行为,首先考虑错误情况,然后编写代码以强调其明显的正确性。当您发现错误时,请先考虑您的算法是否存在严重缺陷,然后再随机开始交换比较方向并破坏已经起作用的内容。
到目前为止,这里的所有答案似乎都过于复杂。
在C#和Java中,为获得正除数和除数,您只需要执行以下操作:
( dividend + divisor - 1 ) / divisor
((13-1)%3)+1)
结果为1。进行正确的除法1+(dividend - 1)/divisor
运算,得出的结果与正数除数和除数的答案相同。而且,没有溢出问题,无论它们可能是人为的。
对于有符号整数:
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
将完成工作。
算术(整数或其他形式)并不像看起来那样简单。始终需要认真思考。
同样,尽管我的最终答案可能不像浮点数答案那样“简单”,“显而易见”,甚至不如“快速”,但它对我来说具有非常强的赎回质量。我现在已经通过答案进行了推理,因此实际上可以确定它是正确的(除非有更聪明的人告诉我- 偷偷摸摸地看向Eric的方向 -)。
要获取有关浮点答案肯定一样的感觉,我不得不做更多的(也可能是更复杂的)思考一下,是否有在其下浮点精度可能的方式获得任何条件,以及是否Math.Ceiling
可能呢在“恰到好处”的输入上有些不受欢迎的东西。
更换(注意我更换了第二myInt1
用myInt2
,假设这是你的意思):
(int)Math.Ceiling((double)myInt1 / myInt2)
与:
(myInt1 - 1 + myInt2) / myInt2
唯一的警告是,如果myInt1 - 1 + myInt2
溢出您正在使用的整数类型,您可能无法获得预期的结果。
这是错误的原因:-1000000和3999应该给-250,这给-249
编辑:
考虑到这与负值的其他整数解决方案具有相同的错误myInt1
,可能更容易执行以下操作:
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
(以我自己为上一次尝试的辩护,在我的头脑告诉我我睡了两个小时之后,我不应该尝试一个合理的答案)
使用扩展方法的绝佳机会:
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);
您可以使用类似以下的内容。
a / b + ((Math.Sign(a) * Math.Sign(b) > 0) && (a % b != 0)) ? 1 : 0)
上面的一些答案使用浮点数,这效率低下,实际上是没有必要的。对于无符号整数,这是对int1 / int2的有效回答:
(int1 == 0) ? 0 : (int1 - 1) / int2 + 1;
对于带符号的整数,这将是不正确的
这里所有解决方案的问题在于它们是否需要强制转换或存在数值问题。强制浮起或翻倍始终是一种选择,但我们可以做得更好。
当您使用@jerryjvl的答案代码时
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
有一个舍入错误。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++;
当然,在myInt2 / 2上也存在舍入问题,但是与该站点上的其他结果相比,此结果将为您提供更好的舍入解决方案。