【问题标题】:Algorithm for integer rounding of result after division of one integer by another whose values may be negative一个整数除以另一个整数(其值可能为负)后对结果进行整数舍入的算法
【发布时间】:2017-07-10 19:37:37
【问题描述】:

我正在使用具有函数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 变为与D13DIGITS 相同的long long,这将消失。

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

【问题讨论】:

  • 对于LONG_MAX/10LONG_MIN/10 附近的任何值,Quot = lAmount * 10; 是溢出 (UB)。其他方法不需要 *10。
  • lAmount, sDivisor 的一些样本值和预期结果会增加您的否定案例的清晰度。 sDivisor &lt; 0 的使用有不同的解释。 IMO,为什么不使用无符号除数来避免这个问题?
  • @chux 不确定你的意思是其他方法不需要 * 10。你能详细说明一下吗?另外,我不确定您所说的“使用sDivisor &lt; 0 有其他解释”是什么意思。关于上溢和下溢的要点。
  • @chux 在某些情况下,某些类型的数据项的计数可能为负数,因此使用无符号除数似乎不合适。
  • 您可能是正确的。然而,除了 10 和 5,还可以使用 2 和 1 来获得更广泛的范围或下面提到的一半/除数技巧。

标签: c algorithm rounding


【解决方案1】:

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

不是按 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);
}

【讨论】:

  • 舍入部分没问题,但RptRoundU函数中if (Divisor != 0)的“else”部分可能需要考虑一下?
  • @Groo 更改为与Divisor==0 案例中 OP 的原始代码匹配。
  • OP 正在考虑这个问题。
  • @RichardChambers Pedantic 代码也可以处理可能发生溢出的情况,例如lAmount += Divisor/2;-lAmount,但这似乎是次要问题。
  • @chux 在计算期间上溢/下溢是D13DIGITS 变量类型为long long 的原因。这段代码最初是为一个不支持long long 的小盒子设计的,所以定义了一个 64 位整数类型,其中定义了一组用于 64 位算术的函数,使用该 64 位整数类型,然后转换回long。跨度>
【解决方案2】:

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

为了使@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

【讨论】:

  • if 中有趣地使用 XOR 运算符。如果尝试除以零,我不愿意更改返回值,因为我不确定报告是否期望它始终是有效的计算值,或者如果 sDivisor 为零,则仅为零。好的,所以我现在得到了半除数值的原因。
  • @RichardChambers:如果这是您正在重构的现有遗留代码,那么留下0 可能是有意义的,只是为了避免破坏现有功能。
  • 如何确定从零四舍五入是否总是合适的?我不确定这段代码是故意这样设计的,还是它与所提供的数据一起工作只是一个意外,或者如果该领域的某个人确实遇到了不准确的情况,没有人抱怨。跨度>
猜你喜欢
  • 2015-01-26
  • 2010-09-06
  • 2022-11-14
  • 2016-01-06
  • 2015-04-22
  • 1970-01-01
  • 1970-01-01
  • 2012-10-19
相关资源
最近更新 更多