这感觉像是一个基本的问题,但是到目前为止我还没有找到一个明确的答案。
我想实现一个有效的函数round_to_nearest(int x, int multiple)
,它将有符号整数x
舍入到multiple
的最接近的倍数,如果可能的话,避免使用浮点运算。
示例输出:
round_to_nearest(14, 5);
15
round_to_nearest(16, 5);
15
round_to_nearest(23, 5);
25
round_to_nearest(22, 5);
20
round_to_nearest(-23, 5);
-25
round_to_nearest(-22, 5);
-20
在整数运算中,如果n为正数,则加m/2,否则减去m/2,然后除以m(截断整数除法),再乘以m:
int round_to_nearest( int n, int m )
{
return (( n + ((n < 0) ? -m : m) / 2) / m ) * m ;
}
int main()
{
int test[] = {16, 23, 22, -23, -22} ;
int m = 5 ;
for( int i = 0; i < sizeof(test) / sizeof(*test); i++ )
{
printf(" round_to_nearest( %d, %d ) = %dn", test[i], m,
round_to_nearest( test[i], m ) ) ;
}
return 0;
}
测试输出:
round_to_nearest( 16, 5 ) = 15
round_to_nearest( 23, 5 ) = 25
round_to_nearest( 22, 5 ) = 20
round_to_nearest( -23, 5 ) = -25
round_to_nearest( -22, 5 ) = -20
需要注意的是m必须是>0 -在这种情况下是有意义的,我会接受它作为正确操作的先决条件;作为运行时错误检查它可能是不必要的,但是您可以包括一个断言来防止程序员语义错误:
assert( m > 0 ) ;
标准库断言在定义NDEBUG
时被删除-通常在调试支持被禁用时。
对于正数:
- 将
multiple
的一半添加到x
- 然后执行整数除法,这会丢掉小数部分
- 然后乘以
multiple
得到最终答案
对于负数,第一步是减法,而不是加法。
int round_to_nearest(int x, int multiple)
{
if (x >= 0)
return ((x + multiple / 2) / multiple) * multiple;
else
return ((x - multiple / 2) / multiple) * multiple;
}
为了在零的方向上四舍五入到下一个倍数(即正数为下,负数为上),您所要做的就是除以该倍数,然后将结果与该倍数相乘。向零的舍入将通过除法中的截断来完成。
int round_toward_zero( int num, int multiple )
{
int quotient;
quotient = num / multiple;
return quotient * multiple;
}
然而,既然你说你想四舍五入到最接近的倍数,而不是在零的方向上的下一个倍数,我们必须做同样的事情,但是我们必须在我们想要在另一个方向上四舍五入的情况下添加一个小的修正:
- 对于正数,如果除法的余数至少是倍数的一半,那么在与倍数相乘之前,必须将
1
加到商上,使其从零四舍五入。 - 对于负数,如果除法的余数不大于倍数的一半,在与倍数相乘之前,必须将
-1
加到商中,使其从零舍入。
因此,在下面的代码中,变量correction
的值可以是-1
、0
或+1
。对于正数,它将是0
或+1
,对于负数,它将是-1
或0
。
#include <stdio.h>
int round_to_nearest( int num, int multiple )
{
int quotient, remainder, correction;
quotient = num / multiple;
remainder = num % multiple;
correction = remainder / ( (multiple + 1 ) / 2 );
return (quotient + correction) * multiple;
}
int main( void )
{
printf( "%dn", round_to_nearest(14, 5) );
printf( "%dn", round_to_nearest(16, 5) );
printf( "%dn", round_to_nearest(23, 5) );
printf( "%dn", round_to_nearest(22, 5) );
printf( "%dn", round_to_nearest(-23, 5) );
printf( "%dn", round_to_nearest(-22, 5) );
}
输出:
15
15
25
20
-25
-20
整数除法向零方向截断;比四舍五入到最接近的结果小0.5(平均量级)。
如果将0.5 * divisor
的大小与分子的大小相加,则结果将大0.5。
也就是说,对于无符号整数:
result = (numerator + divisor/2) / divisor;
. .或者(除数为奇数时舍入误差较小,溢出风险较高-例如,如果分子为INT_MAX
):
result = (numerator*2 + divisor) / (divisor * 2);
对于有符号整数" magnitde& quot;不是"value";当分子和除数的符号不同时,就会变得很混乱。修改:
if( (numerator < 0) && (divisor < 0) ||
(numerator >= 0) && (divisor |= 0) ) {
/* Numerator and divisor have same sign */
result = (numerator*2 + divisor) / (divisor * 2);
} else {
/* Numerator and divisor have different sign */
result = (numerator*2 - divisor) / (divisor * 2);
}
要四舍五入到最接近的倍数,只需在"四舍五入"后面乘以除数。代码变成:
if( (numerator < 0) && (multiple < 0) ||
(numerator >= 0) && (multiple |= 0) ) {
/* Numerator and multiple have same sign */
result = (numerator*2 + multiple) / (multiple * 2);
} else {
/* Numerator and multiple have different sign */
result = (numerator*2 - multiple) / (multiple * 2);
}
result *= multiple;