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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2422712/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 04:48:55  来源:igfitidea点击:

Rounding integer division (instead of truncating)

cmathintroundinginteger-division

提问by Dave

I was curious to know how I can round a number to the nearest whole number. For instance, if I had:

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

int a = 59 / 4;

which would be 14.75 if calculated in floating point; how can I store the result as 15 in "a"?

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

采纳答案by 0xC0DEFACE

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

This only works when assigning to an int as it discards anything after the '.'

这仅在分配给 int 时有效,因为它会丢弃 '.' 之后的任何内容。

Edit:This solution will only work in the simplest of cases. A more robust solution would be:

编辑:此解决方案仅适用于最简单的情况。更强大的解决方案是:

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

回答by Jonathan Leffler

The standard idiom for integer rounding up is:

整数四舍五入的标准习惯用法是:

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

You add the divisor minus one to the dividend.

您将除数减去一添加到被除数中。

回答by ericbn

A code that works for any sign in dividend and divisor:

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

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

In response to a comment "Why is this actually working?", we can break this apart. First, observe that n/dwould be the quotient, but it is truncated towards zero, not rounded. You get a rounded result if you add half of the denominator to the numerator before dividing, but only if numerator and denominator have the same sign. If the signs differ, you must subtract half of the denominator before dividing. Putting all that together:

为了回应“为什么这实际上有效?”的评论,我们可以将其分解。首先,观察这n/d将是商,但它被截断为零,而不是四舍五入。如果在除法之前将分母的一半加到分子上,您会得到一个四舍五入的结果,但前提是分子和分母具有相同的符号。如果符号不同,则必须在除法之前减去分母的一半。把所有这些放在一起:

(n < 0) is false (zero) if n is non-negative
(d < 0) is false (zero) if d is non-negative
((n < 0) ^ (d < 0)) is true if n and d have opposite signs
(n + d/2)/d is the rounded quotient when n and d have the same sign
(n - d/2)/d is the rounded quotient when n and d have opposite signs

If you prefer a macro:

如果您更喜欢宏:

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

The linux kernel macro DIV_ROUND_CLOSEST doesn't work for negative divisors!

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

EDIT: This will work without overflow:

编辑:这将工作而不会溢出:

int divRoundClosest( int A, int B )
{
if(A<0)
    if(B<0)
        return (A + (-B+1)/2) / B + 1;
    else
        return (A + ( B+1)/2) / B - 1;
else
    if(B<0)
        return (A - (-B+1)/2) / B - 1;
    else
        return (A - ( B+1)/2) / B + 1;
}

回答by WayneJ

You should instead use something like this:

你应该使用这样的东西:

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

I assume that you are really trying to do something more general:

我假设您真的在尝试做一些更一般的事情:

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

   return a;
}

x + (y-1) has the potential to overflow giving the incorrect result; whereas, x - 1 will only underflow if x = min_int...

x + (y-1) 有可能溢出给出不正确的结果;而 x - 1 只有在 x = min_int 时才会下溢...

回答by WayneJ

(Edited) Rounding integers with floating point is the easiest solution to this problem; however, depending on the problem set is may be possible. For example, in embedded systems the floating point solution may be too costly.

(已编辑)使用浮点数舍入整数是解决此问题的最简单方法;然而,根据问题集是可能的。例如,在嵌入式系统中,浮点解决方案可能过于昂贵。

Doing this using integer math turns out to be kind of hard and a little unintuitive. The first posted solution worked okay for the the problem I had used it for but after characterizing the results over the range of integers it turned out to be very bad in general. Looking through several books on bit twiddling and embedded math return few results. A couple of notes. First, I only tested for positive integers, my work does not involve negative numerators or denominators. Second, and exhaustive test of 32 bit integers is computational prohibitive so I started with 8 bit integers and then mades sure that I got similar results with 16 bit integers.

事实证明,使用整数数学来做这件事有点困难,而且有点不直观。第一个发布的解决方案对于我用它解决的问题来说还可以,但是在对整数范围内的结果进行表征后,结果证明总的来说非常糟糕。翻阅几本关于 bit twiddling 和嵌入式数学的书,结果很少。一些注意事项。首先,我只测试了正整数,我的工作不涉及负分子或分母。其次,对 32 位整数的详尽测试是计算上的,所以我从 8 位整数开始,然后确保我用 16 位整数得到类似的结果。

I started with the 2 solutions that I had previously proposed:

我从我之前提出的 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 * 10)/D) + 5)/10)

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

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

My thought was that the first version would overflow with big numbers and the second underflow with small numbers. I did not take 2 things into consideration. 1.) the 2nd problem is actually recursive since to get the correct answer you have to properly round D/2. 2.) In the first case you often overflow and then underflow, the two canceling each other out. Here is an error plot of the two (incorrect) algorithms:Divide with Round1 8 bit x=numerator y=denominator

我的想法是第一个版本会以大数字溢出,而第二个版本会以小数字溢出。我没有考虑两件事。1.) 第二个问题实际上是递归的,因为要得到正确的答案,您必须正确地舍入 D/2。2.) 在第一种情况下,您经常溢出然后下溢,两者相互抵消。这是两种(不正确的)算法的误差图:除以 Round1 8 位 x=分子 y=分母

This plot shows that the first algorithm is only incorrect for small denominators (0 < d < 10). Unexpectedly it actually handles largenumerators better than the 2nd version.

该图显示第一个算法仅对小分母 (0 < d < 10) 不正确。没想到它实际上处理numerators比第二版更好。

Here is a plot of the 2nd algorithm: 8 bit signed numbers 2nd algorithm.

这是第二个算法的图: 8 位有符号数第二算法。

As expected it fails for small numerators but also fails for more large numerators than the 1st version.

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

Clearly this is the better starting point for a correct version:

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

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

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

If your denominators is > 10 then this will work correctly.

如果您的分母 > 10,那么这将正常工作。

A special case is needed for D == 1, simply return N. A special case is needed for D== 2, = N/2 + (N & 1) // Round up if odd.

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

D >= 3 also has problems once N gets big enough. It turns out that larger denominators only have problems with larger numerators. For 8 bit signed number the problem points are

一旦 N 足够大,D >= 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))

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))

(return D/N for these)

(为这些返回 D/N)

So in general the the pointe where a particular numerator gets bad is somewhere around
N > (MAX_INT - 5) * D/10

所以一般来说,特定分子变坏的指针在某处
N > (MAX_INT - 5) * D/10

This is not exact but close. When working with 16 bit or bigger numbers the error < 1% if you just do a C divide (truncation) for these cases.

这并不准确,但很接近。当使用 16 位或更大的数字时,如果您只是对这些情况进行 C 除法(截断),则误差 < 1%。

For 16 bit signed numbers the tests would be

对于 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))

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))

Of course for unsigned integers MAX_INT would be replaced with MAX_UINT. I am sure there is an exact formula for determining the largest N that will work for a particular D and number of bits but I don't have any more time to work on this problem...

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

(I seem to be missing this graph at the moment, I will edit and add later.) This is a graph of the 8 bit version with the special cases noted above:![8 bit signed with special cases for 0 < N <= 103

(我现在好像缺少这个图,我稍后会编辑和添加。)这是一个 8 位版本的图,上面提到的特殊情况:![8 位签名,特殊情况为0 < N <= 103

Note that for 8 bit the error is 10% or less for all errors in the graph, 16 bit is < 0.1%.

请注意,对于 8 位,图中所有错误的误差为 10% 或更少,16 位为 < 0.1%。

回答by Chris Lutz

As written, you're performing integer arithmetic, which automatically just truncates any decimal results. To perform floating point arithmetic, either change the constants to be floating point values:

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

int a = round(59.0 / 4);

Or cast them to a floator other floating point type:

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

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

Either way, you need to do the final rounding with the round()function in the math.hheader, so be sure to #include <math.h>and use a C99 compatible compiler.

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

回答by PauliusZ

From Linux kernel (GPLv2):

来自 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));    \
}                           \
)

回答by Magnetron

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

Another useful MACROS (MUST HAVE):

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

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

回答by user3707766

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

Checking if there is a remainder allows you to manually roundup the quotient of integer division.

检查是否有余数允许您手动四舍五入整数除法的商。

回答by zevero

Borrowing from @ericbn I prefere defines like

借用@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)))