我很想知道如何将数字舍入到最接近的整数。例如,如果我有:
int a = 59 / 4;
如果以浮点计算,则为14.75;如何将结果存储为“a”中的15?
int a = 59.0f / 4.0f + 0.5f;
这仅在分配给int时有效,因为它会丢弃'。'之后的任何内容。
编辑:此解决方案仅适用于最简单的情况。一个更强大的解决方案是:
unsigned int round_closest(unsigned int dividend, unsigned int divisor)
{
return (dividend + (divisor / 2)) / divisor;
}
这是我的解决方案。我喜欢它,因为我觉得它更具可读性,因为它没有分支(既不是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);
}
借用@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)))
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,余数> = tempY因此商= 15;
double a=59.0/4;
int b=59/4;
if(a-b>=0.5){
b++;
}
printf("%d",b);
尝试使用数学ceil函数进行四舍五入。 Math Ceil!
如果你正在划分正整数,你可以将它向上移动,进行除法,然后检查实数b0右边的位。换句话说,100/8是12.5,但会返回12.如果你这样做(100 << 1)/ 8,你可以检查b0,然后在将结果向下移动后向上舍入。
对于某些算法,当“最接近”为平局时,您需要一致的偏差。
// 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)
的相对速度可能与平台有关。
对于正负操作数且没有浮点或条件分支,下面正确地将商舍入到最接近的整数(请参阅下面的汇编输出)。假设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处理器的-cc优化的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
划分为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);
我遇到了同样的困难。下面的代码应该适用于正整数。
我还没有编译它,但我在google电子表格上测试了算法(我知道,wtf),它正在运行。
unsigned int integer_div_round_nearest(unsigned int numerator, unsigned int denominator)
{
unsigned int rem;
unsigned int floor;
unsigned int denom_div_2;
// check error cases
if(denominator == 0)
return 0;
if(denominator == 1)
return numerator;
// Compute integer division and remainder
floor = numerator/denominator;
rem = numerator%denominator;
// Get the rounded value of the denominator divided by two
denom_div_2 = denominator/2;
if(denominator%2)
denom_div_2++;
// If the remainder is bigger than half of the denominator, adjust value
if(rem >= denom_div_2)
return floor+1;
else
return floor;
}
整数舍入的标准习语是:
int a = (59 + (4 - 1)) / 4;
您将除数减一加到被除数。
更安全的C代码(除非你有其他处理方法/ 0):
return (_divisor > 0) ? ((_dividend + (_divisor - 1)) / _divisor) : _dividend;
当然,这不能解决由于输入数据无效而导致返回值不正确而导致的问题。
适用于任何红利和除数符号的代码:
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不适用于负除数!
你应该使用这样的东西:
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只会下溢...
(已编辑)使用浮点舍入整数是解决此问题的最简单方法;但是,根据问题集可能是可能的。例如,在嵌入式系统中,浮点解决方案可能成本太高。
使用整数数学做这件事结果有点难,有点不直观。第一个发布的解决方案对于我使用它的问题没有问题,但是在整数范围内表征结果后,结果总体上非常糟糕。通过几本关于钻头和嵌入式数学的书籍回顾几乎没有什么结果。几个笔记。首先,我只测试了正整数,我的工作不涉及负分子或分母。第二,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.)在第一种情况下,你经常溢出然后下溢,两者相互抵消。这是两个(不正确)算法的错误图:
该图显示第一种算法仅对小分母(0 <d <10)不正确。出乎意料的是,它实际上比第二版更好地处理大分子。
这是第二个算法的图:
正如预期的那样,它对于小分子来说是失败的,但是对于比第一版更大的分子也是如此。
显然,这是正确版本的更好起点:
#define DIVIDE_WITH_ROUND(N, D) (((N) == 0) ? 0:(((N * 10)/D) + 5)/10)
如果你的分母大于10,那么这将正常工作。
D == 1需要一个特殊情况,只需返回N. D == 2,= N / 2 +(N&1)//需要特殊情况//如果奇数则向上舍入。
一旦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))
(返回这些的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位<0.1%。
如上所述,您正在执行整数运算,它会自动截断任何小数结果。要执行浮点运算,请将常量更改为浮点值:
int a = round(59.0 / 4);
或者将它们转换为float
或其他浮点类型:
int a = round((float)59 / 4);
无论哪种方式,你需要使用round()
头中的math.h
函数进行最后的舍入,所以一定要使用#include <math.h>
并使用兼容C99的编译器。
从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)); \
} \
)
int a, b;
int c = a / b;
if(a % b) { c++; }
检查是否有余数允许您手动对整数除法的商进行四舍五入。
#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))