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

Posted

技术标签:

【中文标题】舍入整数除法(而不是截断)【英文标题】:Rounding integer division (instead of truncating) 【发布时间】:2011-01-26 05:37:19 【问题描述】:

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

int a = 59 / 4;

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

【问题讨论】:

除了我的 C 宏和 gcc 语句表达式版本之外,我还添加了一个 C++ 函数模板 版本,通过类型检查确保只使用整数类型,万一有人需要这个用于宏被皱眉的核心 C++ 程序:ಠ╭╮ಠ ***.com/questions/2422712/…。 (以及 ASCII 皱眉的来源:( ͡° ͜ʖ ͡°)) 【参考方案1】:

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

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

你将除数减一加到被除数上。

【讨论】:

如果要进行数学回合(14.75 到 15、14.25 到 14)怎么办? 呃...那么您必须考虑...添加 (n - 1) / 2,或多或少。对于 n == 4,您希望 x % n ∈ 0, 1 向下舍入,并且 x % n ∈ 2, 3 向上舍入。因此,您需要添加 2,即 n / 2。对于 n == 5,您希望 x % n ∈ 0, 1, 2 向下取整, x % n ∈ 3, 4 向上取整,所以你需要再次添加 2...因此:int i = (x + (n / 2)) / n;? 此方法适用于正面int。但如果除数或被除数为负数,则会产生不正确的答案。 @caf 的提示也不起作用。 (原始)标题和问题要求两个不同的东西。标题说四舍五入(这是您回答的内容),但正文说四舍五入到最近(这是接受的答案尝试的内容)。 请注意 c = (INT_MAX + (4 - 1)) / 4; 由于整数溢出而给出 c = -536870911...【参考方案2】:

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

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,这是迄今为止最好的解决方案。 为什么这真的有效?这背后的数学概念是什么? @TobiasMarschall 这相当于floor(n / d + 0.5),其中 n 和 d 是浮点数。 编辑中的代码不起作用。 divRoundClosest(1, 5) 等于 1 嗨@迈克尔。也许您应该将此处添加的 EDIT 移到单独的答案中,因此可以单独讨论有关该特定代码的 cmets。【参考方案3】:
int a = 59.0f / 4.0f + 0.5f;

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

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

unsigned int round_closest(unsigned int dividend, unsigned int divisor)

    return (dividend + (divisor / 2)) / divisor;

【讨论】:

请注意,这是浮动指针解决方案。出于多种原因,我建议您使用整数运算。 因为在没有 FPU 的系统上,这会产生非常非常糟糕的代码。 这就是问题所在。 OP 的问题完全可以在不使用任何浮点的情况下解决,因此不依赖于 FPU 支持的存在或良好。此外,在大多数架构上更快(如果需要计算很多这些),包括那些具有其他出色 FPU 支持的架构。另请注意,如果浮点数无法准确表示给定的整数值,您的解决方案可能会出现问题。 -1,当 sizeof(int) >= sizeof(float) 时,这对许多值给出了错误的答案。例如,一个 32 位浮点数使用一些位来表示指数,因此它不能准确地表示每个 32 位整数。所以 12584491 / 3 = 4194830.333...,应该向下舍入到 4194830,但是,在我的机器上不能精确地表示 12584491 的浮点数,上面的公式四舍五入到 4194831,这是错误的。使用 double 更安全。 真正的问题是为什么要将这样的值表示为整数类型。 double 可容纳高达 2^53 的完美整数,并允许您轻松指定如何舍入错误。【参考方案4】:

你应该改用这样的东西:

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 时才会下溢...

【讨论】:

61.0 / 30.0 = 2.03333(3)。所以向上取整应该是 2,但是 (61-1)/30+1=3 @nad2000 为什么将 2.0333.. 四舍五入 up 为 2? 这在 x = 0 时不起作用。如果 x = 0 时 x/y 向上舍入的预期结果为 0。但此解决方案产生的结果为 1。另一个解决方案提出了正确的回答。 其实这个答案一点也不正确。它适用于一些数字,但在很多方面都失败了。稍后在线程中查看我更好(我希望)的答案。 如果这个答案是 qrong 你为什么不直接删除它?【参考方案5】:

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

事实证明,使用整数数学来做这件事有点困难,而且有点不直观。第一个发布的解决方案对于我使用它的问题来说效果很好,但是在对整数范围内的结果进行表征之后,它通常变得非常糟糕。翻阅几本关于位旋转和嵌入式数学的书,结果很少。 一些笔记。首先,我只测试了正整数,我的工作不涉及负分子或分母。其次,对 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 大分子。

这是第二个算法的图:

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

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

#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 &gt; (MAX_INT - 5) * D/10

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

对于 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 &lt; N &lt;= 10 的特殊情况@3

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

【讨论】:

你是对的。第一个宏是不正确的(我发现这很困难。)结果比我预期的要难。我更新了承认错误的帖子,并包含了一些进一步的讨论。【参考方案6】:

来自 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 的一部分还是编译器特定的扩展? @chux:It's a GCC extension。它不是标准 C 的一部分。 在宏中检查有符号和无符号参数的好方法,因此无符号参数可以完全省略分支和额外指令。【参考方案7】:

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

int a = round(59.0 / 4);

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

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

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

【讨论】:

典型的float (IEEE) 将此解决方案的有用范围限制为 abs(a/b) 或者lround【参考方案8】:

2021 年 5 月 2 日更新:

下面我原始答案中的代码仅适用于 整数。要处理负整数的正确舍入,请参阅我的项目 repo 中的技术:https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/tree/master/c/rounding_integer_division。主文件:rounding_integer_division.cpp.

它包含:

    这些 C 或 C++ 宏:
    DIVIDE_ROUNDUP(numer, denom)
    DIVIDE_ROUNDDOWN(numer, denom)
    DIVIDE_ROUNDNEAREST(numer, denom)
    
    这些 gcc/clang C 或 C++ 语句表达式(比宏更安全):
    DIVIDE_ROUNDUP2(numer, denom)
    DIVIDE_ROUNDDOWN2(numer, denom)
    DIVIDE_ROUNDNEAREST2(numer, denom)
    
    这些 C++ 模板:
    template <typename T>
    T divide_roundup(T numer, T denom)
    
    template <typename T>
    T divide_rounddown(T numer, T denom)
    
    template <typename T>
    T divide_roundnearest(T numer, T denom)
    
    一堆单元测试来测试以上所有内容。

原始答案:

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_;         \
)

3。 C++函数模板形式:

(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;

在这里运行和测试其中的一些代码:

    OnlineGDB: integer rounding during division

相关答案:

    Fixed Point Arithmetic in C Programming - 在这个答案中,我讨论了如何将 整数舍入 到最接近的整数,然后是第十位(小数点右侧的 1 个小数位),百位(2 个小数位) ,千位(3 个十进制数字)等。在我的代码 cmets 中搜索名为 BASE 2 CONCEPT: 的部分的答案以获取更多详细信息! 我对 gcc 语句表达式的相关回答:MIN and MAX in C this 固定类型的函数形式:Rounding integer division (instead of truncating) What is the behavior of integer division? 要向上舍入而不是最接近的整数,请遵循类似的模式:Rounding integer division (instead of truncating)

参考资料:

    https://www.tutorialspoint.com/cplusplus/cpp_templates.htm

tod​​o:测试这个是否有负面输入,如果可行,请更新这个答案:

#define ROUND_DIVIDE(numer, denom) ((numer < 0) != (denom < 0) ? ((numer) - (denom) / 2) / (denom) : ((numer) + (denom) / 2) / (denom))

【讨论】:

绝对是 TLDR 复制代码块。删除所有 cmets。 这 3 种方法中的每一种都像 1 到 4 行。 然而,真正发生的事情及其工作原理的细节值得作为教学平台的详细解释。 简短的回答是有缺陷的:ROUND_DIVIDE(-3 , 4) 计算结果为0,这不是最接近的整数。冗长的解释根本没有解决这个问题。 (int)round(-3.0 / 4.0) 将评估为 -1 如果我说错了,我会在我去修复它时弄清楚。超级晚了。我也会在我的测试用例中添加负数。 我没有忘记这件事。我的直觉是在某些情况下将加法换成减法是正确的,需要基于每个输入的负值进行逻辑异或。我正在首先在我的本地计算机上完全重写这个答案,修复它以处理负数,并添加宏来四舍五入。完成后,我将拥有DIVIDE_ROUNDUP()DIVIDE_ROUNDDOWN()DIVIDE_ROUNDNEAREST() 之类的东西,所有这些都将处理正 和负 整数输入。希望那时我会赢得你的支持。我当然会自己使用这些。【参考方案9】:
#define CEIL(a, b) (((a) / (b)) + (((a) % (b)) > 0 ? 1 : 0))

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

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

【讨论】:

你的括号让我头晕。 当然,这看起来像是一个糟糕的 LISP 案例,但是省略每个参数周围的括号然后评估 ABS(4 & -1) 会更糟。 他想要的是ROUND,而不是CEIL【参考方案10】:
int a, b;
int c = a / b;
if(a % b)  c++; 

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

【讨论】:

当除数不是 2 时,这不是经常向上取整吗? 这总是像 ceil 函数一样四舍五入,而不是正确的四舍五入【参考方案11】:

这是我的解决方案。我喜欢它,因为我发现它更具可读性,而且它没有分支(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);

【讨论】:

((a ^ b) &amp; 0x80000000) &gt;&gt; 31; 中的 &amp; 是多余的,因为无论如何移位后低位都会被丢弃【参考方案12】:

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

【讨论】:

【参考方案13】:
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)【参考方案14】:

对于没有浮点或条件分支的正负操作数,以下正确地将商四舍五入到最接近的整数(参见下面的汇编输出)。假设 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

【讨论】:

【参考方案15】:

如果要除正整数,可以将其上移,进行除法,然后检查实数 b0 右侧的位。换句话说,100/8 是 12.5,但会返回 12。如果您这样做 (100

【讨论】:

【参考方案16】:

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

// 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)&gt;&gt;1)(d/2) 的相对速度可能取决于平台。

【讨论】:

正如@caf 在对另一个答案的评论中指出的那样,溢出是这种舍入方法的风险(因为它在除法之前修改了分子),所以如果你使用这个函数是不合适的正在突破int 的范围限制。 顺便说一句,如果你碰巧被2的幂除(这也意味着一个正除数),你可以利用signed-shift-right具有除法效果的事实向负无穷大舍入(与向零舍入的除法运算符不同)以避免使用任何条件逻辑。所以公式变成了return (n+(1&lt;&lt;shift&gt;&gt;1))&gt;&gt;shift;,它简化为(n+C)&gt;&gt;shift的形式(如果shift恰好是一个常数,则C=(1&lt;&lt;shift&gt;&gt;1) “中值偏差”问题仅与整数之间恰好 0.5 的情况有关,因此肯定是深奥的。例如,对于某种图形表示,您可能希望看到围绕零的连续性而不是对称性。【参考方案17】:
double a=59.0/4;
int b=59/4;
if(a-b>=0.5)
    b++;

printf("%d",b);
    将 59.0/4 的精确浮点值设为 x(此处为 14.750000) 让小于x的最小整数为y(这里是14) 如果 x-y 否则 y+1 是解决方案

【讨论】:

这是最糟糕的解决方案。做2个慢分区不是人们想做的。而b可以通过截断a而不是再除法得到【参考方案18】:

除以 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);

【讨论】:

【参考方案19】:

如先前的贡献者所介绍的,基本的舍入除法算法是在除法之前将分子的一半加到分母上。这在输入无符号时很简单,但在涉及有符号值时则不然。以下是一些通过 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

【讨论】:

int sgn = n &gt;&gt; (sizeof(n)*8-1); // 0 or -1:不,C 标准没有定义该行为。你应该使用int sgn = ~(n &lt; 0); 我关心的是 ARM 微控制器的速度和尺寸。表达式~(n &lt; 0) 再生成一条指令。此外,原始表达式适用于任何使用 8 位字节和二进制补码的架构,我认为这描述了所有现代机器。【参考方案20】:

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

【讨论】:

【参考方案21】:

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

我还没有编译它,但我在谷歌电子表格上测试了算法(我知道,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;。它的目的是什么?【参考方案22】:

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

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

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

【讨论】:

以上是关于舍入整数除法(而不是截断)的主要内容,如果未能解决你的问题,请参考以下文章

为啥Firebird在除法时会截断小数位?

用 random.randint 进行整数除法

为啥整数除法在许多脚本语言中舍入?

在 Python 中,整数除法中向零舍入的好方法是啥?

JS中整数除法的精确性

如何在 Java 中舍入整数除法并获得 int 结果? [复制]