我怎样才能确保一个整数的分割总是四舍五入的?
我想确保整数的分割总是在必要时进行。 有没有比这更好的方法? 有很多铸造正在进行。 🙂
(int)Math.Ceiling((double)myInt1 / myInt2)
更新:这个问题是我的博客在2013年1月的主题 。 感谢您的好问题!
获得整数算术正确是困难的。 到目前为止已经充分certificate,当你尝试做一个“聪明”的伎俩的时候,你犯了一个错误的几率是很好的。 当发现缺陷时,修改代码来修复缺陷, 而不考虑修复是否会破坏别的东西,这不是一个好的解决问题的技巧。 到目前为止,我们已经有了五个不同的不正确的整数算术解决scheme来解决这个完全不是特别困难的问题。
解决整数算术问题的正确方法 – 也就是增加第一次得到答案的可能性的方法 – 就是小心处理问题,一步一步地解决问题,并在工作中使用良好的工程原理所以。
首先阅读您要replace的规范。 整数除法规范明确规定:
-
该部门将结果向零调整
-
当两个操作数具有相同的符号时,结果为零或正值,当两个操作数具有相反的符号时,结果为负值或负值
-
如果左操作数是最小可表示的int,右操作数是-1,则发生溢出。 […]是实现定义是否抛出[ArithmeticException],或者溢出未被报告,结果值是左操作数的值。
-
如果右操作数的值为零,则抛出System.DivideByZeroException。
我们想要的是一个计算商的整数除法函数,但总是向上舍入结果,而不是总是趋向零 。
所以写一个该函数的规范。 我们的函数int DivRoundUp(int dividend, int divisor)
必须为每个可能的input定义行为。 这个未定义的行为令人深感忧虑,所以让我们来消除它。 我们会说我们的操作有这个规范:
-
如果除数为零,则操作抛出
-
如果dividend是int.minval和divisor是-1,则操作会抛出
-
如果没有余数 – 除法是“偶数” – 则返回值是积分商
-
否则,它返回大于商的最小整数,即总是四舍五入。
现在我们有一个规范,所以我们知道我们可以拿出一个可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(注意,我用myInt2
replace了第二个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。