我很想知道如何将数字四舍五入到最接近的整数。例如,如果我有:
int a = 59 / 4;
如果以浮点数计算,则为14.75;如何将结果存储为“ a”中的15?
我很想知道如何将数字四舍五入到最接近的整数。例如,如果我有:
int a = 59 / 4;
如果以浮点数计算,则为14.75;如何将结果存储为“ a”中的15?
Answers:
int a = 59.0f / 4.0f + 0.5f;
这仅在分配给int时有效,因为它会丢弃'。'之后的任何内容。
编辑: 此解决方案将仅在最简单的情况下工作。一个更强大的解决方案是:
unsigned int round_closest(unsigned int dividend, unsigned int divisor)
{
return (dividend + (divisor / 2)) / divisor;
}
整数舍入的标准习惯用法是:
int a = (59 + (4 - 1)) / 4;
您将除数减一加到除数中。
int i = (x + (n / 2)) / n;
?
int
。但是,如果除数或除数为负,则会产生错误的答案。@caf的提示也不起作用。
c = (INT_MAX + (4 - 1)) / 4;
给出的c = -536870911
...
适用于任何除数和除数符号的代码:
int divRoundClosest(const int n, const int d)
{
return ((n < 0) ^ (d < 0)) ? ((n - d/2)/d) : ((n + d/2)/d);
}
在回应“为什么这实际上起作用?”的评论时,我们可以将其分开。首先,请注意这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
如果您更喜欢宏:
#define DIV_ROUND_CLOSEST(n, d) ((((n) < 0) ^ ((d) < 0)) ? (((n) - (d)/2)/(d)) : (((n) + (d)/2)/(d)))
linux内核宏DIV_ROUND_CLOSEST对负除数不起作用!
编辑:这将工作而不会溢出:
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;
}
int
接近min / max int的值之外,这是迄今为止最好的解决方案。
floor(n / d + 0.5)
,其中n和d是浮点数。
您应该改用如下方式:
int a = (59 - 1)/ 4 + 1;
我认为您确实是在尝试做一些更一般的事情:
int divide(x, y)
{
int a = (x -1)/y +1;
return a;
}
x +(y-1)可能会溢出,给出错误的结果;而x-1仅在x = min_int时才下溢。
(编辑)使用浮点数舍入整数是解决此问题的最简单方法;但是,根据问题集可能是可行的。例如,在嵌入式系统中,浮点解决方案可能会过于昂贵。
使用整数数学执行此操作比较困难,有点不直观。第一个发布的解决方案可以很好地解决我曾经使用过的问题,但是在对整数范围内的结果进行特征化后,结果通常很糟糕。翻阅几本有关位旋转和嵌入式数学的书,收效甚微。几个注意事项。首先,我只测试了正整数,我的工作不涉及负分子或分母。其次,对32位整数进行详尽的测试在计算上是令人望而却步的,因此我从8位整数开始,然后确保获得16位整数的相似结果。
我从之前提出的两种解决方案开始:
#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)不正确。没想到它实际上处理大numerators比第二版更好。
这是第二种算法的图解:
正如预期的那样,它对于小型分子失败,但对于比第一版本更大的分子失败。
显然,这是正确版本的更好起点:
#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)
因此,一般而言,特定分子变质的尖端就在某处
N > (MAX_INT - 5) * D/10
这并不确切,但很接近。如果使用C位除法(截断),则使用16位或更大的数字时,错误<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);
或将它们强制转换为afloat
或其他浮点类型:
int a = round((float)59 / 4);
无论哪种方式,都需要对标头中的round()
函数进行最后的四舍五入math.h
,因此请确保#include <math.h>
并使用与C99兼容的编译器。
float
(IEEE)将此解决方案的有用范围限制为abs(a / b)<16,777,216。
lround
。
从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)); \
} \
)
typeof()
C或编译器特定扩展的一部分吗?
int a, b;
int c = a / b;
if(a % b) { c++; }
通过检查是否存在余数,可以手动舍入整数除法的商。
ceil
函数一样四舍五入,而不是适当的舍入
这是我的解决方案。我喜欢它是因为我发现它更具可读性,并且因为它没有分支(ifs和ternaries)。
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);
}
&
中((a ^ b) & 0x80000000) >> 31;
是多余的,因为低位将移位后扔掉无论如何
// 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的答案)。我真的很喜欢无形式的宏或gcc语句表达式形式,而不是函数形式,所以,我写了这个答案,并详细说明了我在做什么(即:为什么这样做在数学上可行)并将其分为2种形式:
/// @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))
/// @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_; \
})
(新增2020年3月/ 4日)
#include <limits>
// Template form for C++ (with type checking to ensure only integer types are passed in!)
template <typename T>
T round_division(T numerator, T denominator)
{
// Ensure only integer types are passed in, as this round division technique does NOT work on
// floating point types since it assumes integer truncation will take place automatically
// during the division!
// - The following static assert allows all integer types, including their various `const`,
// `volatile`, and `const volatile` variations, but prohibits any floating point type
// such as `float`, `double`, and `long double`.
// - Reference page: https://en.cppreference.com/w/cpp/types/numeric_limits/is_integer
static_assert(std::numeric_limits<T>::is_integer, "Only integer types are allowed");
return (numerator + denominator/2)/denominator;
}
BASE 2 CONCEPT:
获取更多详细信息!待办事项:测试是否为负输入并更新此答案(如果可行):
#define ROUND_DIVIDE(numer, denom) ((numer < 0) != (denom < 0) ? ((numer) - (denom) / 2) / (denom) : ((numer) + (denom) / 2) / (denom))
ROUND_DIVIDE(-3 , 4)
计算结果为0
,这不是最接近的整数。冗长的解释根本没有解决这个问题。(int)round(-3.0 / 4.0)
将评估为-1
。
DIVIDE_ROUNDUP()
,它们都将处理正整数和负整数输入。希望我能赢得您的支持。我当然会自己使用这些。DIVIDE_ROUNDDOWN()
DIVIDE_ROUNDNEAREST()
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商= 14,tempY = 2,余数= 3,余数> = tempY,因此商= 15;
divide(-59, 4)
。
尝试使用数学四舍五入函数进行舍入。Math Ceil!
对于某些算法,当“最近”为平局时,您需要一个一致的偏差。
// 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)
vs .的相对速度(d/2)
可能取决于平台。
int
。
return (n+(1<<shift>>1))>>shift;
,这简化了对形式(n+C)>>shift
(其中,C=(1<<shift>>1)
如果shift
刚好是一个常数。
对于没有浮点数或条件分支的正数和负数操作数,以下内容正确地将商数舍入到最接近的整数(请参见下面的程序集输出)。假设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
除以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上,可以将其优化为
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);
如先前贡献者所述,基本的舍入除法算法是在除法之前将分母的一半加到分子上。当输入是无符号的时,这很简单,而当涉及带符号的值时,情况就并非如此。以下是一些解决方案,这些解决方案可以通过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)。取而代之的是,我们少移位一个,再加一个,然后进行最后的移位。这样可以节省创建一个非平凡的值(即使是常量),也可以省去将其放入机器的寄存器。
int sgn = n >> (sizeof(n)*8-1); // 0 or -1
:否,该行为未由C标准定义。您应该使用int sgn = ~(n < 0);
~(n < 0)
生成一条指令。此外,原始表达式适用于任何使用8位字节和二进制补码的体系结构,我认为它可以描述所有现代机器。
我遇到了同样的困难。下面的代码应适用于正整数。
我尚未编译它,但我在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;
}
if(denominator == 1) return numerator;
。目的是什么?