【问题标题】:C: Round signed integer to nearest multipleC:将有符号整数舍入到最接近的倍数
【发布时间】:2021-08-03 17:45:39
【问题描述】:

这感觉像是一个基本问题,但到目前为止我找不到明确的答案。

我想实现一个高效的函数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

【问题讨论】:

  • x 除以multiple(整数除法)。然后从那里往两个方向(上和下)走,看看哪个“更近”。当心极端情况。
  • round_to_nearest(9, 6);这种模棱两可的情况应该怎么办?
  • round_to_nearest(-9, 6)?和round_to_nearest(-9, -6)一样吗?你希望它是对称的还是标准的?
  • 应该可以只用一个% 操作,加上加法和减法,而不是同时需要除法和乘法。
  • 问题已经回答了,但是为了澄清原始问题,我并不关心一个倍数和下一个倍数中间的值的模棱两可的情况,我假设multiple的正值

标签: c rounding


【解决方案1】:

在整数运算中,如果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 ) = %d\n", 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 时移除标准库断言 - 通常在禁用调试支持时。

【讨论】:

  • ( n + ((n &lt; 0) ? -m : m) / 2) 在某些情况下会溢出。
  • @EricPostpischil 当然。你可以用intermediate long long来处理这个问题,但在大多数情况下(至少在32位平台上)可能是一个极端情况,但会以牺牲性能为代价。
【解决方案2】:

对于正数:

  • 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;
}

【讨论】:

  • 这些表达式在某些情况下会溢出。
【解决方案3】:

为了在零方向上舍入到下一个倍数(即正数向下,负数向上),您所要做的就是除以该倍数,然后将结果乘以该倍数。向零舍入将通过除法中的截断来完成。

int round_toward_zero( int num, int multiple )
{
    int quotient;

    quotient  = num / multiple;

    return quotient * multiple;
}

但是,由于您说要四舍五入到最接近的倍数,而不是在零方向上的下一个倍数,我们必须做同样的事情,但我们必须在我们想要四舍五入的情况下添加一个小的修正另一个方向:

  • 对于正数,如果除法的余数至少是倍数的一半,那么我们必须在与倍数相乘之前将1 加到商上,使其从零四舍五入。
  • 对于负数,如果除数的余数不超过倍数的一半,我们必须在与倍数相乘之前将-1 添加到商,以便它从零四舍五入。

因此,在以下代码中,变量correction 的值可以是-10+1。对于正数,它将是0+1,对于负数,它将是-10

#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( "%d\n", round_to_nearest(14, 5) );
    printf( "%d\n", round_to_nearest(16, 5) );
    printf( "%d\n", round_to_nearest(23, 5) );
    printf( "%d\n", round_to_nearest(22, 5) );
    printf( "%d\n", round_to_nearest(-23, 5) );
    printf( "%d\n", round_to_nearest(-22, 5) );
}

输出:

15
15
25
20
-25
-20

【讨论】:

  • 我认为这个答案避免了不必要的溢出,并且是迄今为止唯一的答案,除了multiple = -1 和num = INT_MIN 的情况或@ 的情况987654337@ = INT_MAX。 (我认为这可能对multiple
【解决方案4】:

整数除法向零截断;这比四舍五入的结果小 0.5(平均而言)。

如果你把0.5 * divisor的大小加到分子的大小上,那么结果会大0.5。

换句话说,对于无符号整数:

    result = (numerator + divisor/2) / divisor;

..或者(除数为奇数时舍入误差较小,溢出风险较高 - 例如,如果分子是INT_MAX):

    result = (numerator*2 + divisor) / (divisor * 2);

对于有符号整数,“幅度”不是“值”;当分子和除数的符号不同时,它就会变得一团糟。要解决这个问题:

    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;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-07
    • 2011-03-25
    • 2011-03-16
    相关资源
    最近更新 更多