舍入整数除法(而不是截断)

时间:2010-03-11 05:20:37

标签: c math int rounding integer-division

我很想知道如何将数字舍入到最接近的整数。例如,如果我有:

int a = 59 / 4;

如果以浮点计算,则为14.75;如何将结果存储为“a”中的15?

22 个答案:

答案 0 :(得分:115)

整数舍入的标准习惯用语是:

int a = (59 + (4 - 1)) / 4;

将除数减一加到被除数。

答案 1 :(得分:43)

int a = 59.0f / 4.0f + 0.5f;

这仅适用于分配给int,因为它会丢弃'。'

之后的任何内容

修改 此解决方案仅适用于最简单的情况。一个更强大的解决方案是:

unsigned int round_closest(unsigned int dividend, unsigned int divisor)
{
    return (dividend + (divisor / 2)) / divisor;
}

答案 2 :(得分:42)

适用于任何红利和除数符号的代码:

int divRoundClosest(const int n, const int d)
{
  return ((n < 0) ^ (d < 0)) ? ((n - d/2)/d) : ((n + d/2)/d);
}

如果您更喜欢宏:

#define DIV_ROUND_CLOSEST(n, d) ((((n) < 0) ^ ((d) < 0)) ? (((n) - (d)/2)/(d)) : (((n) + (d)/2)/(d)))

linux内核宏DIV_ROUND_CLOSEST不适用于负除数!

答案 3 :(得分:23)

你应该使用这样的东西:

int a = (59 - 1)/ 4 + 1;

我认为你真的想做一些更通用的事情:

int divide(x, y)
{
   int a = (x -1)/y +1;

   return a;
}

x +(y-1)有可能溢出,给出错误的结果;然而,如果x = min_int ...

,x - 1只会下溢

答案 4 :(得分:10)

(编辑)的 具有浮点的舍入整数是解决此问题的最简单方法;但是,根据问题集可能是可能的。例如,在嵌入式系统中,浮点解决方案可能成本太高。

使用整数数学做这件事结果有点难,有点不直观。第一个发布的解决方案对于我使用它的问题没有问题,但是在整数范围内表征结果后,结果总体上非常糟糕。通过几本关于钻头和嵌入式数学的书籍回顾几乎没有什么结果。 几个笔记。首先,我只测试了正整数,我的工作不涉及负分子或分母。第二,32位整数的详尽测试是计算禁止的,所以我从8位整数开始,然后确保我得到类似的16位整数结果。

我从之前提出的2个解决方案开始:

#define DIVIDE_WITH_ROUND(N, D) (((N) == 0) ? 0:(((N * 10)/D) + 5)/10)

#define DIVIDE_WITH_ROUND(N, D) (N == 0) ? 0:(N - D/2)/D + 1;

我的想法是,第一个版本会溢出大数字,第二个版本会出现小数字。我没有考虑两件事。 1.)第二个问题实际上是递归的,因为要得到正确的答案你必须正确地围绕D / 2。 2.)在第一种情况下,你经常溢出然后下溢,两者相互抵消。 以下是两个(不正确的)算法的错误图:Divide with Round1 8 bit x=numerator y=denominator

该图显示第一算法仅对小分母(0 大分子。

这是第二个算法的图: 8 bit signed numbers 2nd algorithm.

正如预期的那样,对于小分子来说它会失败,但是对于比第一个版本更大的分子也是如此。

显然,这是正确版本的更好起点:

#define DIVIDE_WITH_ROUND(N, D) (((N) == 0) ? 0:(((N * 10)/D) + 5)/10)

如果你的分母是> 10然后这将正常工作。

D == 1需要特殊情况,只需返回N. D == 2,= N / 2 +(N&amp; 1)需要特殊情况//如果奇数则向上舍入。

一旦N变得足够大,

D&gt; = 3也会出现问题。事实证明,较大的分母只有较大分子的问题。对于8位有符号数,问题点是

if (D == 3) && (N > 75))
else if ((D == 4) && (N > 100))
else if ((D == 5) && (N > 125))
else if ((D == 6) && (N > 150))
else if ((D == 7) && (N > 175))
else if ((D == 8) && (N > 200))
else if ((D == 9) && (N > 225))
else if ((D == 10) && (N > 250))

(返回D / N)

所以一般来说,特定分子变坏的pointe就在附近 N > (MAX_INT - 5) * D/10

这不完全但很接近。当使用16位或更大的数字时,错误<如果您对这些情况进行C分割(截断),则为1%。

对于16位有符号数,测试将是

if ((D == 3) && (N >= 9829))
else if ((D == 4) && (N >= 13106))
else if ((D == 5) && (N >= 16382))
else if ((D == 6) && (N >= 19658))
else if ((D == 7) && (N >= 22935))
else if ((D == 8) && (N >= 26211))
else if ((D == 9) && (N >= 29487))
else if ((D == 10) && (N >= 32763))

当然对于无符号整数,MAX_INT将被MAX_UINT替换。我确信有一个确切的公式可以确定适用于特定D和位数的最大N,但我没有时间处理这个问题......

(我此刻似乎缺少此图表,我稍后会编辑并添加。) 这是8位版本的图表,其中包含上述特殊情况:![8位签名,包含0 < N <= 10 3的特殊情况

注意,对于8位,对于图中的所有错误,误差为10%或更小,16位是&lt; 0.1%。

答案 5 :(得分:7)

如上所述,您正在执行整数运算,它会自动截断任何小数结果。要执行浮点运算,请将常量更改为浮点值:

int a = round(59.0 / 4);

或者将它们转换为float或其他浮点类型:

int a = round((float)59 / 4);

无论哪种方式,您都需要使用round()标头中的math.h函数进行最后的舍入,因此请务必使用#include <math.h>并使用兼容C99的编译器。

答案 6 :(得分:4)

int a, b;
int c = a / b;
if(a % b) { c++; }

检查是否有余数允许您手动对整数除法的商进行求解。

答案 7 :(得分:4)

来自Linux内核(GPLv2):

/*
 * Divide positive or negative dividend by positive divisor and round
 * to closest integer. Result is undefined for negative divisors and
 * for negative dividends if the divisor variable type is unsigned.
 */
#define DIV_ROUND_CLOSEST(x, divisor)(          \
{                           \
    typeof(x) __x = x;              \
    typeof(divisor) __d = divisor;          \
    (((typeof(x))-1) > 0 ||             \
     ((typeof(divisor))-1) > 0 || (__x) > 0) ?  \
        (((__x) + ((__d) / 2)) / (__d)) :   \
        (((__x) - ((__d) / 2)) / (__d));    \
}                           \
)

答案 8 :(得分:3)

#define CEIL(a, b) (((a) / (b)) + (((a) % (b)) > 0 ? 1 : 0))

另一个有用的MACROS(必须):

#define MIN(a, b)  (((a) < (b)) ? (a) : (b))
#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
#define ABS(a)     (((a) < 0) ? -(a) : (a))

答案 9 :(得分:2)

从@ericbn借款我优先定义如

#define DIV_ROUND_INT(n,d) ((((n) < 0) ^ ((d) < 0)) ? (((n) - (d)/2)/(d)) : (((n) + (d)/2)/(d)))
or if you work only with unsigned ints
#define DIV_ROUND_UINT(n,d) ((((n) + (d)/2)/(d)))

答案 10 :(得分:2)

这是我的解决方案。我喜欢它,因为我发现它更具可读性,因为它没有分支(既不是ifs也不是三元组)。

int32_t divide(int32_t a, int32_t b) {
  int32_t resultIsNegative = ((a ^ b) & 0x80000000) >> 31;
  int32_t sign = resultIsNegative*-2+1;
  return (a + (b / 2 * sign)) / b;
}

说明预期行为的完整测试程序:

#include <stdint.h>
#include <assert.h>

int32_t divide(int32_t a, int32_t b) {
  int32_t resultIsNegative = ((a ^ b) & 0x80000000) >> 31;
  int32_t sign = resultIsNegative*-2+1;
  return (a + (b / 2 * sign)) / b;
}

int main() {
  assert(divide(0, 3) == 0);

  assert(divide(1, 3) == 0);
  assert(divide(5, 3) == 2);

  assert(divide(-1, 3) == 0);
  assert(divide(-5, 3) == -2);

  assert(divide(1, -3) == 0);
  assert(divide(5, -3) == -2);

  assert(divide(-1, -3) == 0);
  assert(divide(-5, -3) == 2);
}

答案 11 :(得分:1)

int divide(x,y){
 int quotient = x/y;
 int remainder = x%y;
 if(remainder==0)
  return quotient;
 int tempY = divide(y,2);
 if(remainder>=tempY)
  quotient++;
 return quotient;
}

例如59/4 Quotient = 14,tempY = 2,余数= 3,余数&gt; = tempY因此商= 15;

答案 12 :(得分:1)

double a=59.0/4;
int b=59/4;
if(a-b>=0.5){
    b++;
}
printf("%d",b);
  1. 让59.0 / 4的精确浮点值为x(此处为14.750000)
  2. 让小于x的最小整数为y(此处为14)
  3. 如果x-y <0.5则y是解决方案
  4. 其他y + 1是解决方案

答案 13 :(得分:0)

对于某些算法,当“最接近”为平局时,您需要一致的偏差。

// round-to-nearest with mid-value bias towards positive infinity
int div_nearest( int n, int d )
   {
   if (d<0) n*=-1, d*=-1;
   return (abs(n)+((d-(n<0?1:0))>>1))/d * ((n<0)?-1:+1);
   }

无论分子或分母的符号如何,这都有效。


如果您想匹配round(N/(double)D)(浮点除法和舍入)的结果,以下是一些产生相同结果的变体:

int div_nearest( int n, int d )
   {
   int r=(n<0?-1:+1)*(abs(d)>>1); // eliminates a division
// int r=((n<0)^(d<0)?-1:+1)*(d/2); // basically the same as @ericbn
// int r=(n*d<0?-1:+1)*(d/2); // small variation from @ericbn
   return (n+r)/d;
   }

注意:(abs(d)>>1)(d/2)的相对速度可能与平台有关。

答案 14 :(得分:0)

尝试使用数学ceil函数进行四舍五入。 Math Ceil

答案 15 :(得分:0)

如果你正在划分正整数,你可以将它向上移动,进行除法,然后检查实数b0右边的位。换句话说,100/8是12.5,但会返回12.如果你这样做(100 <1)/ 8,你可以检查b0,然后在将结果向下移动后向上舍入。

答案 16 :(得分:0)

对于没有浮点数或条件分支的正数和负数操作数,以下正确将商数舍入到最接近的整数(请参见下面的程序集输出)。假定N位2的补码整数。

#define ASR(x) ((x) < 0 ? -1 : 0)  // Compiles into a (N-1)-bit arithmetic shift right
#define ROUNDING(x,y) ( (y)/2 - (ASR((x)^(y)) & (y)))

int RoundedQuotient(int x, int y)
   {
   return (x + ROUNDING(x,y)) / y ;
   }

ROUNDING的值与除数(x)的符号相同,而除数(y)的 幅值 的一半。因此,在整数除法截断所得商之前,将ROUNDING添加到被除数会增加其幅度。这是针对32位ARM Cortex-M4处理器进行-O3优化的gcc编译器的输出:

RoundedQuotient:                // Input parameters: r0 = x, r1 = y
    eor     r2, r1, r0          // r2 = x^y
    and     r2, r1, r2, asr #31 // r2 = ASR(x^y) & y
    add     r3, r1, r1, lsr #31 // r3 = (y < 0) ? y + 1 : y
    rsb     r3, r2, r3, asr #1  // r3 = y/2 - (ASR(x^y) & y)
    add     r0, r0, r3          // r0 = x + (y/2 - (ASR(x^y) & y)
    sdiv    r0, r0, r1          // r0 = (x + ROUNDING(x,y)) / y
    bx      lr                  // Returns r0 = rounded quotient

答案 17 :(得分:0)

一些除以4的替代方法

return x/4 + (x/2 % 2);
return x/4 + (x % 4 >= 2)

或者通常除以2的任意次幂

return x/y + x/(y/2) % 2;    // or
return (x >> i) + ((x >> i - 1) & 1);  // with y = 2^i

如果小数部分⩾0.5,即第一位数字⩾base / 2,则四舍五入。用二进制表示,相当于将第一个小数位添加到结果中

此方法在带有标志寄存器的体系结构中具有优势,因为进位标志将包含被移出的最后一位。例如,在x86上,它可以是optimized into

shr eax, i
adc eax, 0

它也很容易扩展以支持带符号整数。请注意,负数的表达式为

(x - 1)/y + ((x - 1)/(y/2) & 1)

我们可以使它适用于正值和负值

int t = x + (x >> 31);
return (t >> i) + ((t >> i - 1) & 1);

答案 18 :(得分:0)

TLDR:这是一个宏;使用它!

// To do (numer/denom), rounded to the nearest whole integer, use:
#define ROUND_DIVIDE(numer, denom) (((numer) + (denom) / 2) / (denom))

用法示例:

int num = ROUND_DIVIDE(13,7); // 13/7 = 1.857 --> rounds to 2, so num is 2

完整答案:

其中一些答案看起来很疯狂! Codeface钉了它! (请参阅@ 0xC0DEFACE的answer here)。我真的很喜欢无形式的宏或gcc语句表达式形式,而不是函数形式,所以,我写了这个答案,并详细说明了我在做什么(即:为什么这样做在数学上可行)并将其分为2种形式:

1。宏形式,带有详细的注释来解释整个事情:

/// @brief      ROUND_DIVIDE(numerator/denominator): round to the nearest whole integer when doing 
///             *integer* division only
/// @details    This works on *integers only* since it assumes integer truncation will take place automatically
///             during the division! 
/// @notes      The concept is this: add 1/2 to any number to get it to round to the nearest whole integer
///             after integer trunction.
///             Examples:  2.74 + 0.5 = 3.24 --> 3 when truncated
///                        2.99 + 0.5 = 3.49 --> 3 when truncated
///                        2.50 + 0.5 = 3.00 --> 3 when truncated
///                        2.49 + 0.5 = 2.99 --> 2 when truncated
///                        2.00 + 0.5 = 2.50 --> 2 when truncated
///                        1.75 + 0.5 = 2.25 --> 2 when truncated
///             To add 1/2 in integer terms, you must do it *before* the division. This is achieved by 
///             adding 1/2*denominator, which is (denominator/2), to the numerator before the division.
///             ie: `rounded_division = (numer + denom/2)/denom`.
///             ==Proof==: 1/2 is the same as (denom/2)/denom. Therefore, (numer/denom) + 1/2 becomes 
///             (numer/denom) + (denom/2)/denom. They have a common denominator, so combine terms and you get:
///             (numer + denom/2)/denom, which is the answer above.
/// @param[in]  numerator   any integer type numerator; ex: uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t, etc
/// @param[in]  denominator any integer type denominator; ex: uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t, etc
/// @return     The result of the (numerator/denominator) division rounded to the nearest *whole integer*!
#define ROUND_DIVIDE(numerator, denominator) (((numerator) + (denominator) / 2) / (denominator))

2。 GCC Statement Expression格式:

请参见a little more on gcc statement expressions here

/// @brief      *gcc statement expression* form of the above macro
#define ROUND_DIVIDE2(numerator, denominator)               \
({                                                          \
    __typeof__ (numerator) numerator_ = (numerator);        \
    __typeof__ (denominator) denominator_ = (denominator);  \
    numerator_ + (denominator_ / 2) / denominator_;         \
})

相关答案:

  1. Fixed Point Arithmetic in C Programming-在这个答案中,我将研究如何进行整数舍入到最接近的整数,然后是第十位(小数点右边的1个小数位),第100位(2个十进制数字),千位(3个十进制数字)等。在我的代码注释中搜索名为BASE 2 CONCEPT:的部分的答案,以了解更多详细信息!
  2. 我在gcc语句表达式上的一个相关答案:MIN and MAX in C
  3. 具有固定类型的函数形式:Rounding integer division (instead of truncating)
  4. What is the behavior of integer division?
  5. 要舍入而不是四舍五入,请遵循类似的模式:Rounding integer division (instead of truncating)

答案 19 :(得分:0)

如先前贡献者所述,基本的舍入除法算法是在除法之前将分子的分母加一半。当输入是无符号的时,这很简单,而当涉及带符号的值时,情况就并非如此。这是一些可以通过GCC为ARM(thumb-2)生成最佳代码的解决方案。

已签名/未签名

inline int DivIntByUintRnd(int n, uint d)       
{ 
    int sgn = n >> (sizeof(n)*8-1); // 0 or -1
    return (n + (int)(((d / 2) ^ sgn) - sgn)) / (int)d; 
}

第一行代码将整个分子复制分子符号位,创建零(正)或-1(负)。在第二行中,此值(如果为负)用于使用2的补码负号取反:取整和增量。先前的答案使用条件语句或相乘来实现。

已签名/已签名

inline int DivIntRnd(int n, int d)      
{ 
    int rnd = d / 2;
    return (n + ((n ^ d) < 0 ? -rnd : rnd)) / d; 
}

我发现我得到了带有条件表达式的最短代码,但前提是我通过计算舍入值d / 2帮助了编译器。使用2的补码求反运算很接近:

inline int DivIntRnd(int n, int d)      
{ 
    int sgn = (n ^ d) >> (sizeof(n)*8-1);   // 0 or -1
    return (n + ((d ^ sgn) - sgn) / 2) / d; 
}

2的幂除法

当整数除法向零截断时,截断向负无穷大。这样就使舍入移位变得更加简单,因为无论分子的符号如何,您总是要添加舍入值。

inline int ShiftIntRnd(int n, int s)        { return ((n >> (s - 1)) + 1) >> 1; }
inline uint ShiftUintRnd(uint n, int s)     { return ((n >> (s - 1)) + 1) >> 1; }

表达式是相同的(根据类型生成不同的代码),因此宏或重载函数都可以使用。

传统方法(四舍五入运算的方式)是将除数的一半加1 <<(s-1)。取而代之的是,我们少移位一个,再加一个,然后进行最后的移位。这样可以节省创建一个非平凡的值(即使是常量)和机器寄存器以将其放入。

答案 20 :(得分:-1)

我遇到了同样的困难。 下面的代码应该适用于正整数。

我还没有编译它,但是我在google电子表格上测试了算法(我知道,wtf)并且它正在运行。

Component[] componente = painelMain.getComponents();
    list = new ArrayList();
    for (int i = 0; i < componente.length; i++) {
        if (componente[i] instanceof JTextField) {
            JTextField textfield = (JTextField) componente[i];
            if (!"".equals(textfield.getText())) {
                list.add(textfield.getText());
                System.out.println(list);
            }
        } else if (componente[i] instanceof JComboBox) {
            JComboBox combo = (JComboBox) componente[i];
            if (!"".equals(combo.getSelectedItem())) {
                list.add(combo.getSelectedItem());
            }
        }
    }
}

答案 21 :(得分:-1)

更安全的C代码(除非你有其他处理方法/ 0):

return (_divisor > 0) ? ((_dividend + (_divisor - 1)) / _divisor) : _dividend;

当然,这不能解决由于输入数据无效而导致返回值不正确而导致的问题。