我想确保整数的除法在必要时总是四舍五入。还有比这更好的办法吗?有很多演员都在选角。: -)

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

你可以使用下面这样的东西。

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

您可以编写一个helper。

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

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

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

最后一个基于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。“天花板”也许在“刚刚好”的输入上做了一些不受欢迎的事情。

走过的路

Replace(注意,我用myInt2替换了第二个myInt1,假设这就是你的意思):

(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

(在我为上次尝试辩护的时候,当我的大脑告诉我我已经晚睡了2个小时的时候,我就不应该尝试一个理性的答案)


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


获得正确的整数算术是困难的。正如迄今为止已经充分证明的那样,当你试图做一个“聪明”的把戏时,很可能你犯了一个错误。当发现缺陷时,修改代码来修复缺陷而不考虑修复是否会破坏其他东西,这不是一种很好的解决问题的技术。到目前为止,我想我们已经给出了5个不同的错误整数算术解来解决这个完全不是特别难的问题。

处理整数算术问题的正确方法——也就是说,增加第一次得到正确答案的可能性的方法——是仔细地处理问题,一步一步地解决它,并在此过程中使用良好的工程原理。

首先阅读您要替换的产品的规格说明。整数除法规范明确规定:

The division rounds the result towards zero 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 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. If the value of the right operand is zero, a System.DivideByZeroException is thrown.

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

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

如果除数为零,操作抛出 如果被除数为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 

资料来源:《数字转换》,罗兰·巴恪思,2001年


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

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

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

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

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

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


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

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

对于有符号int型,这是不正确的


对于有符号整数或无符号整数。

Q = x / y + !(((x < 0) != (y < 0)) || !(x % y));

有符号的股利和无符号的因子。

Q = x / y + !((x < 0) || !(x % y));

对于无符号股利和有符号因子。

Q = x / y + !((y < 0) || !(x % y));

对于无符号整数。

Q = x / y + !!(x % y);

零除数失败(与本机操作一样)。

不能溢出。

优雅而正确。

理解这种行为的关键是认识到截断、下限和上限划分的区别。c# / c++是被截断的。当商为负(即运算符符号不同)时,截断是一个上限(更少的负)。否则截断是一个底(不太正)。

因此,如果有余数,如果结果为正,则加1。模也是一样的,只是加了除数。地板是一样的,但是在相反的条件下减去。


四舍五入,我想你的意思是总是从零开始。如果没有任何类型转换,请使用Math.DivRem()函数

/// <summary>
/// Divide a/b but always round up
/// </summary>
/// <param name="a">The numerator.</param>
/// <param name="b">The denominator.</param>
int DivRndUp(int a, int b)
{
    // remove sign
    int s = Math.Sign(a) * Math.Sign(b);
    a = Math.Abs(a);
    b = Math.Abs(b);
    var c = Math.DivRem(a, b, out int r);
    // if remainder >0 round up
    if (r > 0)
    {
        c++;
    }
    return s * c;
}

如果roundup的意思是不管符号是什么,总是向上,那么

/// <summary>
/// Divide a/b but always round up
/// </summary>
/// <param name="a">The numerator.</param>
/// <param name="b">The denominator.</param>
int DivRndUp(int a, int b)
{
    // remove sign
    int s = Math.Sign(a) * Math.Sign(b);
    a = Math.Abs(a);
    b = Math.Abs(b);
    var c = Math.DivRem(a, b, out int r);
    // if remainder >0 round up
    if (r > 0)
    {
        c+=s;
    }
    return s * c;
}