将一个整数除以另一个整数后的结果的整数舍入算法,其值可能为负

时间:2017-07-10 19:37:37

标签: c algorithm rounding

我正在使用在各种报告中使用的函数RptRound()的C源代码,它使用两个值,一个金额和一个第二个值(如计数),将它们分开然后舍入结果。划分的金额通常是货币金额,如美元乘以100美分的金额。

我的问题是,如果lAmountsDivisor的值均为负值,当前使用的函数是否会提供正确的舍入,从而导致其除法的正结果。

考虑到四舍五入的问题,它似乎并没有像我原先想象的那样切割和干燥。然而,这里的意图似乎是进行Round-Half-Up类型的舍入。或者更确切地说是圆形 - 半离开。但是,如果除数sDivisor的值为正,则此函数仅执行此操作。

目前的功能如下:

DCURRENCY RptRound( DCURRENCY lAmount, SHORT sDivisor )
{
    if (sDivisor) {                      /* Not Divide 0 Case */
        D13DIGITS   Quot, Quot1;

        if ( lAmount < 0 ) {
            Quot1 = -5;
        } else {
            Quot1 = 5;
        }
        Quot = lAmount * 10;
        Quot /= (LONG)sDivisor;
        Quot += Quot1;
        Quot /= 10;
        return Quot;
    }
    return (0);
}

看起来只要sDivisor总是正数,那么这个函数将给出正确的舍入结果,其中x.5的正值向上舍入为(x + 1)以及为负值-x.5向下舍入(更负)到(x - 1)。

然而,在我看来,如果lAmountsDivisor均为负数而导致阳性结果,那么舍入将是错误的,因为偏差值Quot1会当它应该是积极的时候是消极的。结果是x.5将舍入为x而不是x + 1(如果两者都为负)。或者更糟糕的是,如果两者都是负数,则x.2将四舍五入为x - 1。

所以我想用以下内容替换上面的内容。

DCURRENCY RptRound( DCURRENCY lAmount, SHORT sDivisor )
{
    D13DIGITS   Quot = 0;

    if (sDivisor) {                      /* Not Divide 0 Case */
        Quot = lAmount;
        Quot *= 10;                      // multiply by 10 with larger number of digits
        Quot /= sDivisor;
        // add the bias and divide now in case both lAmount and sDivisor are negative.
        // this will get us to the closest whole number value of the result.
        if ( Quot < 0 ) {
            Quot += -5;      // add half of -10 to make more negative
        } else {
            Quot += 5;       // add half of +10 to make more positive
        }
        Quot /= 10;          // divide by 10 to get to nearest whole number of result.
    }

    return Quot;
}

如果此版本sDivisor为负且lAmount为正,则结果将从零开始舍入(结果为负数且舍入为负数),如果sDivisor为{否定且lAmount为负数(结果为正数且舍入为正数)。如果sDivisor为正数且lAmount为正数,则结果将从零开始舍入(结果为正数且舍入为正数),如果sDivisor为正数且lAmount为负数,则结果从零开始舍入(结果为负数,舍入为负数)。

然而,经过一些阅读后,我对此变化的确定程度大大降低,因此我正在寻找其他反馈。

注意:,因为DCURRENCY当前是long,此函数会在转换返回值时生成编译器警告。一旦DCURRENCY变为与long long相同的D13DIGITS,这种情况就会消失。

warning C4244: 'return' : conversion from 'D13DIGITS' to 'DCURRENCY', possible loss of data

2 个答案:

答案 0 :(得分:3)

这里的负除数看起来有点奇怪,所以考虑一个有符号除数和无符号除数。 signed函数简单地否定了两个操作数。

不是按10缩放并加+/- 5除以10,而是加一半除数。

DCURRENCY RptRoundU(DCURRENCY lAmount, unsigned Divisor) {
  if (Divisor > 0) {
    if  lAmount < 0) {
      lAmount -= Divisor/2;
    } else {
      lAmount += Divisor/2;
    }  
    lAmount /= (LONG) sDivisor;
    return lAmount;
  }
  return 0;
}

DCURRENCY RptRoundS(DCURRENCY lAmount, signed Divisor) {
  return Divisor < 0 ? RptRoundU(-lAmount, 0u-Divisor) : RptRoundU(lAmount, Divisor);
}

答案 1 :(得分:1)

这个想法只是在除数之前加上或减去除数值的一半,因为它将从零开始舍入。

为了使@ chux的答案稍微缩短,只有当其中一个值为负数时才能减去该值,即

DCURRENCY RptRound( DCURRENCY lAmount, SHORT sDivisor )
{
    if (!sDivisor)
        return 0; // or some invalid value

    if ((sDivisor < 0) ^ (lAmount < 0)) {
        lAmount -= (sDivisor / 2);
    } else {
        lAmount += (sDivisor / 2);
    }

    return lAmount / sDivisor;
}

此外,我的偏好是返回一个特殊的无效值除以零,类似于(long)0x80000000