【发布时间】:2017-07-10 19:37:37
【问题描述】:
我正在使用具有函数RptRound() 的 C 源代码在各种报告中使用,该函数采用两个值,一个数量和第二个值(例如计数),将它们相除,然后对结果进行四舍五入。被划分的金额值通常是货币金额,例如美元乘以 100 得到美分金额。
我的问题是,如果 lAmount 和 sDivisor 的值都是负数,导致它们的除法结果为正数,那么当前使用的函数是否会提供正确的舍入。
查看四舍五入的主题,似乎它并没有我最初想象的那么简单。然而,这里的意图似乎是进行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)。
但是在我看来,如果 lAmount 和 sDivisor 都为负数导致结果为正数,则舍入将不正确,因为偏差值 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/10或LONG_MIN/10附近的任何值,Quot = lAmount * 10;是溢出 (UB)。其他方法不需要 *10。 -
lAmount, sDivisor的一些样本值和预期结果会增加您的否定案例的清晰度。sDivisor < 0的使用有不同的解释。 IMO,为什么不使用无符号除数来避免这个问题? -
@chux 不确定你的意思是其他方法不需要 * 10。你能详细说明一下吗?另外,我不确定您所说的“使用
sDivisor < 0有其他解释”是什么意思。关于上溢和下溢的要点。 -
@chux 在某些情况下,某些类型的数据项的计数可能为负数,因此使用无符号除数似乎不合适。
-
您可能是正确的。然而,除了 10 和 5,还可以使用 2 和 1 来获得更广泛的范围或下面提到的一半/除数技巧。