Dav*_*ave 67 c math int rounding integer-division
我很想知道如何将数字舍入到最接近的第十个整数.例如,如果我有:
int a = 59 / 4;
Run Code Online (Sandbox Code Playgroud)
以浮点计算的14.75; 如何将数字存储为"a"中的15?
Jon*_*ler 120
整数舍入的标准习语是:
int a = (59 + (4 - 1)) / 4;
Run Code Online (Sandbox Code Playgroud)
您将除数减一加到被除数.
0xC*_*ACE 46
int a = 59.0f / 4.0f + 0.5f;
Run Code Online (Sandbox Code Playgroud)
这仅在分配给int时有效,因为它会丢弃'.'之后的任何内容.
编辑: 此解决方案仅适用于最简单的情况.一个更强大的解决方案是:
unsigned int round_closest(unsigned int dividend, unsigned int divisor)
{
return (dividend + (divisor / 2)) / divisor;
}
Run Code Online (Sandbox Code Playgroud)
eri*_*cbn 44
适用于任何红利和除数符号的代码:
int divRoundClosest(const int n, const int d)
{
return ((n < 0) ^ (d < 0)) ? ((n - d/2)/d) : ((n + d/2)/d);
}
Run Code Online (Sandbox Code Playgroud)
如果您更喜欢宏:
#define DIV_ROUND_CLOSEST(n, d) ((((n) < 0) ^ ((d) < 0)) ? (((n) - (d)/2)/(d)) : (((n) + (d)/2)/(d)))
Run Code Online (Sandbox Code Playgroud)
Linux内核宏DIV_ROUND_CLOSEST不适用于负除数!
Way*_*neJ 22
你应该使用这样的东西:
int a = (59 - 1)/ 4 + 1;
Run Code Online (Sandbox Code Playgroud)
我假设你真的想做一些更通用的事情:
int divide(x, y)
{
int a = (x -1)/y +1;
return a;
}
Run Code Online (Sandbox Code Playgroud)
x +(y-1)有可能溢出,给出错误的结果; 然而,如果x = min_int,x - 1只会下溢...
Way*_*neJ 11
(已编辑)使用浮点舍入整数是解决此问题的最简单方法; 但是,根据问题集可能是可能的.例如,在嵌入式系统中,浮点解决方案可能成本太高.
使用整数数学做这件事结果有点难,有点不直观.第一个发布的解决方案对于我使用它的问题没有问题,但是在整数范围内表征结果后,结果总体上非常糟糕.通过几本关于钻头和嵌入式数学的书籍回顾几乎没有什么结果.几个笔记.首先,我只测试了正整数,我的工作不涉及负分子或分母.第二,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)不正确.没想到它实际上处理大 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)
所以通常情况下,特定分子变坏的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);
Run Code Online (Sandbox Code Playgroud)
或者将它们转换为float或其他浮点类型:
int a = round((float)59 / 4);
Run Code Online (Sandbox Code Playgroud)
无论哪种方式,您都需要使用标头中的round()函数进行最后的舍入math.h,因此请务必#include <math.h>使用兼容C99的编译器.
#define CEIL(a, b) (((a) / (b)) + (((a) % (b)) > 0 ? 1 : 0))
Run Code Online (Sandbox Code Playgroud)
另一个有用的宏(必须有):
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define ABS(a) (((a) < 0) ? -(a) : (a))
Run Code Online (Sandbox Code Playgroud)
小智 5
从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)); \
} \
)
Run Code Online (Sandbox Code Playgroud)
自 2023 年 10 月否决以来,该答案已完全重写。
最受欢迎的答案显示了正确的原理,但仅适用于向上舍入,而不是向下舍入或舍入到最接近的值,并且仅适用于正数。
这些是一些很大的限制,所以我将在此处添加一个更完整的答案,以向上舍入(远离零)、向下(向零)或最接近的舍入,并且它将处理正数和负数(在分子中) 、分母或两者)。它也适用于 C 和 C++,为了完整起见,我将添加 C++ 中的模板版本。
有关我的完整代码,请参阅c/rounding_integer_division我的eRCaGuy_hello_world存储库中的文件夹。特别参见该rounding_integer_division.cpp文件。使用 运行单元测试./run_tests.sh。
// -----------------------------------------------------------------------------
// 1. Macros
// -----------------------------------------------------------------------------
#define DIVIDE_ROUND_AWAY_FROM_ZERO(numer, denom) DIVIDE_ROUNDUP((numer), (denom))
#define DIVIDE_ROUNDUP(numer, denom) ( \
((numer) < 0) != ((denom) < 0) ? \
(numer) / (denom) : \
((numer) + ((denom) < 0 ? (denom) + 1 : (denom) - 1)) / (denom) \
)
#define DIVIDE_ROUND_TOWARDS_ZERO(numer, denom) DIVIDE_ROUNDDOWN((numer), (denom))
#define DIVIDE_ROUNDDOWN(numer, denom) ( \
((numer) < 0) != ((denom) < 0) ? \
((numer) - ((denom) < 0 ? (denom) + 1 : (denom) - 1)) / (denom) : \
(numer) / (denom)
#define DIVIDE_ROUNDNEAREST(numer, denom) ( \
((numer) < 0) != ((denom) < 0) ? \
((numer) - ((denom)/2)) / (denom) : \
((numer) + ((denom)/2)) / (denom) \
)
// -----------------------------------------------------------------------------
// 2. Statement Expressions (safer than macros)
// -----------------------------------------------------------------------------
#define DIVIDE_ROUND_AWAY_FROM_ZERO2(numer, denom) DIVIDE_ROUNDUP2((numer), (denom))
#define DIVIDE_ROUNDUP2(numer, denom) \
({ \
__typeof__(numer) numer_ = (numer); \
__typeof__(denom) denom_ = (denom); \
((numer_) < 0) != ((denom_) < 0) ? \
(numer_) / (denom_) : \
((numer_) + ((denom_) < 0 ? (denom_) + 1 : (denom_)-1)) / (denom_); \
})
#define DIVIDE_ROUND_TOWARDS_ZERO2(numer, denom) DIVIDE_ROUNDDOWN2((numer), (denom))
#define DIVIDE_ROUNDDOWN2(numer, denom) \
({ \
__typeof__(numer) numer_ = (numer); \
__typeof__(denom) denom_ = (denom); \
((numer_) < 0) != ((denom_) < 0) ? \
((numer_) - ((denom_) < 0 ? (denom_) + 1 : (denom_)-1)) / (denom_) : \
(numer_) / (denom_); \
})
#define DIVIDE_ROUNDNEAREST2(numer, denom) \
({ \
__typeof__ (numer) numer_ = (numer); \
__typeof__ (denom) denom_ = (denom); \
((numer_) < 0) != ((denom_) < 0) ? \
((numer_) - ((denom_)/2)) / (denom_) : \
((numer_) + ((denom_)/2)) / (denom_); \
})
// -----------------------------------------------------------------------------
// 3. C++ Templated Functions (AKA: Function Templates)
// -----------------------------------------------------------------------------
#ifdef __cplusplus
#include <limits>
template <typename T>
T divide_roundup(T numer, T denom)
{
static_assert(std::numeric_limits<T>::is_integer, "Only integer types are allowed");
T result = ((numer) < 0) != ((denom) < 0) ?
(numer) / (denom) :
((numer) + ((denom) < 0 ? (denom) + 1 : (denom) - 1)) / (denom);
return result;
}
template <typename T>
inline T divide_round_away_from_zero(T numer, T denom)
{
return divide_roundup(numer, denom);
}
template <typename T>
T divide_rounddown(T numer, T denom)
{
static_assert(std::numeric_limits<T>::is_integer, "Only integer types are allowed");
T result = ((numer) < 0) != ((denom) < 0) ?
((numer) - ((denom) < 0 ? (denom) + 1 : (denom) - 1)) / (denom) :
(numer) / (denom);
return result;
}
template <typename T>
inline T divide_round_towards_zero(T numer, T denom)
{
return divide_rounddown(numer, denom);
}
template <typename T>
T divide_roundnearest(T numer, T denom)
{
static_assert(std::numeric_limits<T>::is_integer, "Only integer types are allowed");
T result = ((numer) < 0) != ((denom) < 0) ?
((numer) - ((denom)/2)) / (denom) :
((numer) + ((denom)/2)) / (denom);
return result;
}
#endif
Run Code Online (Sandbox Code Playgroud)
// -----------------------------------------------------------------------------
// 1. Macros
// -----------------------------------------------------------------------------
// Great for C or C++, but some C++ developers hate them since they may have the multiple evaluation
// problem where you pass in an expression as an input parameter and it gets evaluated multiple
// times.
/// @brief A function-like macro to perform integer division of numer/denom, rounding the
/// result UP (AWAY FROM ZERO) to the next whole integer.
/// @note This works on *integers only* since it assumes integer truncation will take place
/// automatically during the division! It will NOT work properly on floating point
/// types! Valid types are int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t,
/// int64_t, uint64_t, etc.
/// @details The concept is this:
/// (1) when the division result will be positive, *add*
/// (abs(denom) - 1) to the numerator *prior to* the division, as this is the
/// equivalent of adding a *tiny bit less than 1* to the result, which will always
/// result in a rounding up once integer truncation takes place. Examples:
/// 1/4 = 0.25, but we add (abs(denom) - 1) to the numerator --> (1 + (4 - 1))/4 =
/// (1 + 3)/4 = 4/4 = 1.
/// (2) when the division result will be negative, simply truncating the result by
/// performing division as normal results in a rounding-up effect.
/// @param[in] numer The numerator in the division: any positive or negative integer
/// @param[in] denom The denominator in the division: any positive or negative integer
/// @return The result of the (numer/denom) division rounded UP to the next *whole integer*!
#define DIVIDE_ROUND_AWAY_FROM_ZERO(numer, denom) DIVIDE_ROUNDUP((numer), (denom))
#define DIVIDE_ROUNDUP(numer, denom) ( \
/* NB: `!=` acts as a logical XOR operator */ \
/* See: /sf/answers/111767701/ */ \
((numer) < 0) != ((denom) < 0) ? \
/* numer OR denom, but NOT both, is negative, so do this: */ \
(numer) / (denom) : \
/* numer AND denom are either *both positive* OR *both negative*, so do this, */ \
/* acting slightly differently if denom is negative: */ \
((numer) + ((denom) < 0 ? (denom) + 1 : (denom) - 1)) / (denom) \
)
/// @brief A function-like macro to perform integer division of numer/denom, rounding the
/// result DOWN (TOWARDS ZERO) to the next whole integer.
/// @note This works on *integers only* since it assumes integer truncation will take place
/// automatically during the division! It will NOT work properly on floating point
/// types! Valid types are int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t,
/// int64_t, uint64_t, etc.
/// @details The concept is this:
/// (1) when the division result will be positive, simply truncating the result by
/// performing division as normal results in a rounding-down effect.
/// (2) When the division result will be negative, *subtract*
/// (abs(denom) - 1) from the numerator *prior to* the division, as this is the
/// equivalent of subtracting a *tiny bit less than 1* from the result, which will
/// always result in a rounding down once integer truncation takes place. Examples:
/// -1/4 = -0.25, but we subtract (abs(denom) - 1) from the numerator -->
/// (-1 - (4 - 1))/4 = (-1 - 3)/4 = -4/4 = -1.
/// @param[in] numer The numerator in the division: any positive or negative integer
/// @param[in] denom The denominator in the division: any positive or negative integer
/// @return The result of the (numer/denom) division rounded DOWN to the next *whole integer*!
#define DIVIDE_ROUND_TOWARDS_ZERO(numer, denom) DIVIDE_ROUNDDOWN((numer), (denom))
#define DIVIDE_ROUNDDOWN(numer, denom) ( \
/* NB: `!=` acts as a logical XOR operator */ \
/* See: /sf/answers/111767701/ */ \
((numer) < 0) != ((denom) < 0) ? \
/* numer OR denom, but NOT both, is negative, so do this, */ \
/* acting slightly differently if denom is negative: */ \
((numer) - ((denom) < 0 ? (denom) + 1 : (denom) - 1)) / (denom) : \
/* numer AND denom are either *both positive* OR *both negative*, so do this: */ \
(numer) / (denom)
/// @brief A function-like macro to perform integer division of numer/denom, rounding the
/// result TO THE NEAREST whole integer.
/// @note This works on *integers only* since it assumes integer truncation will take place
/// automatically during the division! It will NOT work properly on floating point
/// types! Valid types are int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t,
/// int64_t, uint64_t, etc.
/// @details The concept is this:
/// (1) when the division result will be positive, *add* (denom/2) to
/// the numerator *prior to* the division, as this is the equivalent of adding
/// 0.5 to the result, which will always result in rounding to the nearest whole
/// integer once integer truncation takes place. Examples:
/// 3/4 = 0.75, but we add (denom/2) to the numerator --> (3 + 4/2)/4 =
/// (3 + 2)/4 = 5/4 = 1.25, which truncates to 1.
/// (2) when the division result will be negative, *subtract* (denom/2) from
/// the numerator *prior to* the division, as this is required to grow it by 0.5
/// in the direction *away from zero* (more negative in this case), which is required
/// for rounding to the nearest whole integer. The same principle as in the positive
/// case applies. Example: -3/4 = -0.75, but we subtract (denom/2) from the numerator
/// --> (-3 - 4/2)/4 = (-3 - 2)/4 = -5/4 = -1.25, which truncates to -1.
/// @param[in] numer The numerator in the division: any positive or negative integer
/// @param[in] denom The denominator in the division: any positive or negative integer
/// @return The result of the (numer/denom) division rounded TO THE NEAREST *whole integer*!
#define DIVIDE_ROUNDNEAREST(numer, denom) ( \
/* NB: `!=` acts as a logical XOR operator */ \
/* See: /sf/answers/111767701/ */ \
((numer) < 0) != ((denom) < 0) ? \
/* numer OR denom, but NOT both, is negative, so do this: */ \
((numer) - ((denom)/2)) / (denom) : \
/* numer AND denom are either *both positive* OR *both negative*, so do this: */ \
((numer) + ((denom)/2)) / (denom) \
)
// -----------------------------------------------------------------------------
// 2. Statement Expressions
// -----------------------------------------------------------------------------
// These solve the multiple evaluation problem of macros perfectly, but are not part of the C or
// C++ standard. Instead, they are gcc and clang compiler extensions to C and C++. These are safer
// to use than macros, but can still have a name pollution risk because variables created inside
// statement expressions are not in their own scope--rather, they are part of the outer scope.
// Nevertheless, prefer them to macros.
/// @brief *gcc statement expression* form of the above equivalent macro
#define DIVIDE_ROUND_AWAY_FROM_ZERO2(numer, denom) DIVIDE_ROUNDUP2((numer), (denom))
#define DIVIDE_ROUNDUP2(numer, denom) \
({ \
__typeof__(numer) numer_ = (numer); \
__typeof__(denom) denom_ = (denom); \
((numer_) < 0) != ((denom_) < 0) ? \
(numer_) / (denom_) : \
((numer_) + ((denom_) < 0 ? (denom_) + 1 : (denom_)-1)) / (denom_); \
})
/// @brief *gcc statement expression* form of the above equivalent macro
#define DIVIDE_ROUND_TOWARDS_ZERO2(numer, denom) DIVIDE_ROUNDDOWN2((numer), (denom))
#define DIVIDE_ROUNDDOWN2(numer, denom) \
({ \
__typeof__(numer) numer_ = (numer); \
__typeof__(denom) denom_ = (denom); \
((numer_) < 0) != ((denom_) < 0) ? \
((numer_) - ((denom_) < 0 ? (denom_) + 1 : (denom_)-1)) / (denom_) : \
(numer_) / (denom_); \
})
/// @brief *gcc statement expression* form of the above equivalent macro
#define DIVIDE_ROUNDNEAREST2(numer, denom) \
({ \
__typeof__ (numer) numer_ = (numer); \
__typeof__ (denom) denom_ = (denom); \
((numer_) < 0) != ((denom_) < 0) ? \
((numer_) - ((denom_)/2)) / (denom_) : \
((numer_) + ((denom_)/2)) / (denom_); \
})
// -----------------------------------------------------------------------------
// 3. C++ Templated Functions (AKA: Function Templates)
// -----------------------------------------------------------------------------
// Templates work in C++ only. They solve both problems above, and suffer neither from the multiple
// evaluation problem of macros, nor from the name pollution/variable scope problem of statement
// expressions. Since they work only in C++, I'm going to add type checking here too with a
// `static_assert()` using `std::numeric_limits`, but this feature could be *easily* added to both
// macros and statement expressions as well so long as you're using C++. Some C++ developers feel so
// strongly against macros (and are probably not aware of statement expressions) that they won't let
// you merge the above macro versions into their codebase. If this is the case, use templates.
#ifdef __cplusplus
#include <limits>
/// @brief C++ function template form of the above equivalent macro
template <typename T>
T divide_roundup(T numer, T denom)
{
// 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");
T result = ((numer) < 0) != ((denom) < 0) ?
(numer) / (denom) :
((numer) + ((denom) < 0 ? (denom) + 1 : (denom) - 1)) / (denom);
return result;
}
template <typename T>
inline T divide_round_away_from_zero(T numer, T denom)
{
return divide_roundup(numer, denom);
}
/// @brief C++ function template form of the above equivalent macro
template <typename T>
T divide_rounddown(T numer, T denom)
{
// 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");
T result = ((numer) < 0) != ((denom) < 0) ?
((numer) - ((denom) < 0 ? (denom) + 1 : (denom) - 1)) / (denom) :
(numer) / (denom);
return result;
}
template <typename T>
inline T divide_round_towards_zero(T numer, T denom)
{
return divide_rounddown
-
简短的答案是有缺陷的:“ROUND_DIVIDE(-3, 4)”的计算结果为“0”,这不是最接近的整数。冗长的解释根本没有解决这个问题。`(int)round(-3.0 / 4.0)` 的计算结果为 `-1`。 (2认同)
| 归档时间: |
|
| 查看次数: |
126273 次 |
| 最近记录: |