簡體   English   中英

四舍五入 integer 除法(而不是截斷)

[英]Rounding integer division (instead of truncating)

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

int a = 59 / 4;

如果以浮點數計算,則為 14.75; 如何將結果存儲為“a”中的 15?

整數舍入的標准習慣用法是:

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

您將除數減一加到被除數上。

適用於除數和除數的任何符號的代碼:

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 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 - 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 位整數得到類似的結果。

我從之前提出的 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.)在第一種情況下,您經常上溢然后下溢,兩者相互抵消。 這是兩個(不正確的)算法的錯誤圖:除以 Round1 8 位 x=分子 y=分母

該圖顯示第一個算法僅對小分母 (0 < d < 10) 不正確。 出乎意料的是,它實際上比第二版更好地處理大分子

這是第二種算法的圖:8 位有符號數第二算法。

正如預期的那樣,它對於小分子會失敗,但對於比第一版​​更多的大分子也會失敗。

顯然,這是正確版本的更好起點:

#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

這並不准確,但很接近。 當使用 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的特殊情況0 < N <= 10 3

請注意,對於圖表中的所有錯誤,對於 8 位,錯誤為 10% 或更少,16 位小於 0.1%。

如所寫,您正在執行整數運算,它會自動截斷任何小數結果。 要執行浮點運算,請將常量更改為浮點值:

int a = round(59.0 / 4);

或者將它們轉換為float或其他浮點類型:

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

無論哪種方式,您都需要使用math.h頭文件中的round()函數進行最后的舍入,因此請務必#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));    \
}                           \
)

2021 年 5 月 2 日更新:

下面我原始答案中的代碼僅適用於整數。 要處理負整數的正確舍入,請在此處查看我的項目存儲庫中的技術: https ://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/tree/master/c/rounding_integer_division。 主文件: rounding_integer_division.cpp

它包含:

  1. 這些 C 或 C++ 宏:
     DIVIDE_ROUNDUP(numer, denom) DIVIDE_ROUNDDOWN(numer, denom) DIVIDE_ROUNDNEAREST(numer, denom)
  2. 這些 gcc/clang C 或 C++語句表達式(比宏更安全):
     DIVIDE_ROUNDUP2(numer, denom) DIVIDE_ROUNDDOWN2(numer, denom) DIVIDE_ROUNDNEAREST2(numer, denom)
  3. 這些 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)
  4. 一堆單元測試來測試以上所有內容。

原始答案:

摘要:這是一個您可以使用的宏

// (Works for **positive** integers only! See above for fully-robust
// options instead.)
// 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 種形式:

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 語句表達式形式:

在此處查看更多關於 gcc 語句表達式的信息

/// @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;
}

在此處運行並測試其中的一些代碼:

  1. OnlineGDB:除法期間的整數舍入

相關答案:

  1. C 編程中的定點算術- 在這個答案中,我討論了如何將整數舍入到最接近的整數,然后是第十位(小數點右側的 1 個小數位)、百位(2 個十進制數字)、千位( 3 dec 數字)等。在我的代碼注釋中搜索名為BASE 2 CONCEPT:了解更多詳細信息!
  2. 我關於 gcc 語句表達式的相關答案: C 中的 MIN 和 MAX
  3. 具有固定類型的 this 的函數形式: 舍入整數除法(而不是截斷)
  4. 整數除法的行為是什么?
  5. 對於舍入而不是最接近的整數,請遵循以下類似模式: 舍入整數除法(而不是截斷)

參考:

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

待辦事項:測試這個是否有負面輸入並更新這個答案,如果它有效:

#define ROUND_DIVIDE(numer, denom) ((numer < 0) != (denom < 0) ? ((numer) - (denom) / 2) / (denom) : ((numer) + (denom) / 2) / (denom))
#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))
int a, b;
int c = a / b;
if(a % b) { c++; }

檢查是否有余數允許您手動舍入整數除法的商。

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

這是我的解決方案。 我喜歡它,因為我發現它更具可讀性並且因為它沒有分支(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);
}

對於沒有浮點或條件分支的正負操作數,以下正確地將商四舍五入到最接近的整數(請參見下面的匯編輸出)。 假設 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
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;

如果我沒有錯過任何東西,那么 cipilo的答案是唯一適用於所有參數的解決方案,甚至接近INT_MAX 它避免了其他解決方案可能出現的溢出錯誤。 由於缺乏聲譽,我無法發表評論或投票。

他的實現只處理正除數。 我的解決方案也考慮了這些溢出並處理了負除數。

C(按要求):

unsigned round_div_halfawayu(unsigned n, unsigned d)
{
    return n / d + (n % d > (d - 1) / 2);
}

int round_div_halfaway(int n, int d)
{
    assert(abs(INT_MIN) - 1 == abs(INT_MIN + 1));
    if ((n < 0) == (d < 0))
        return n / d + (abs(n % d) > (abs(d) - 1) / 2);
    else
        return n / d - (abs(n % d) > (abs(d) - 1) / 2);
}

C++(我正在使用):

template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
T round_div_halfaway(T n, T d)
{
    if constexpr (std::is_unsigned<T>::value)
        return n / d + (n % d > (d - 1) / 2);
    else if constexpr (std::is_signed<T>::value)
    {
        assert(std::abs(std::numeric_limits<T>::min()) - 1 == 
               std::abs(std::numeric_limits<T>::min() + 1));
        if ((n < 0) == (d < 0))
            return n / d + (std::abs(n % d) > (std::abs(d) - 1) / 2);
        else
            return n / d - (std::abs(n % d) > (std::abs(d) - 1) / 2);
    }
}

它基本上是計算截斷的結果,並在必要時增加絕對值。 斷言檢查abs(INT_MIN)的溢出行為,這是標准未定義的。

該函數實現了常見的商業四舍五入(距離零的一半)。

對於某些算法,當“最近”是平局時,您需要一致的偏差。

// 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)的相對速度可能取決於平台。

如果要除正整數,可以將其向上移動,進行除法,然后檢查實數 b0 右側的位。 換句話說,100/8 是 12.5,但會返回 12。如果您執行 (100<<1)/8,則可以檢查 b0,然后在將結果向下移動后向上取整。

double a=59.0/4;
int b=59/4;
if(a-b>=0.5){
    b++;
}
printf("%d",b);
  1. 讓 59.0/4 的精確浮點值為 x(這里是 14.750000)
  2. 讓小於 x 的最小整數為 y(這里是 14)
  3. 如果 xy<0.5 則 y 是解決方案
  4. 否則 y+1 是解決方案

除以 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)。 相反,我們減一格,加一格,然后進行最后一班。 這可以節省創建一個重要的值(即使是常量)和機器寄存器來放入它。

對於無符號整數類型和正除法器,請使用:

template<typename T> T urounddiv(T n, T d) {
    return (n / d) + ((n % d) > (d / 2)); 
}

一步步

59 / 4 + ((59 % 4) > (4 / 2))
14 + (3 > 2)
14 + true
14 + 1
15

一般情況

這些 C++ 函數模板適用於任何整數類型 T 而不會溢出,適用於任何正除法器,並且不使用浮點。
C 中的整數除法向零截斷,rounddiv、rounddiv(3, 2) == 1 和 rounddiv(-3, 2) == -1 也是如此。 rounddiv_away 函數模板從零開始舍入半點,rounddiv_away(3, 2) == 2 和 rounddiv_away(-3, 2) == -2。 僅當 d 為偶數時,結果才不同。

// Round to zero division, no overflow
template<typename T> T rounddiv(T n, T d) {
    T r = n / d;
    T m = n % d;
    T h = d / 2;
    return r + (!(n < 0) & (m > h)) - ((n < 0) & ((m + h) < 0));
}
// Round away from zero division, no overflow
template<typename T> T rounddiv_away(T n, T d) {
    T r = n / d;
    T m = n % d;
    T h = (d / 2) + (d & 1);
    return r + (!(n < 0) & (m >= h)) - ((n < 0) & ((m + h) <= 0));
}

這個怎么運作

從正整數開始。 將除法的其余部分(模運算的結果)與除法的一半進行比較,而不是將除法的一半加到原始數字上,這會導致范圍溢出。 如果提醒大於除法器的一半,則通過遞增將結果四舍五入。 在大多數情況下,提醒值是整數除法的副產品,它不是額外的操作。

// Round to zero division of unsigned types, no overflow
template<typename T>
static T rounddiv(T x, T d) {
    static_assert(std::is_unsigned<T>(), "Unsigned integer types only");
    T r = x / d;
    T m = x % d;
    T h = d / 2;
    if (m > h)
        r = r + 1;
    return r;
}

對於有符號類型,如果 x 為正,則適用相同的公式。 但是當 x 為負時,r 和 m 都將為負,r 被截斷為零。 這意味着當提醒(-m)的絕對值大於量子的一半時,結果需要遞減。

// Round to zero division, no overflow
template<typename T>
static T rounddiv(T x, T d) {
    static_assert(std::is_integral<T>(), "Integer types only");
    T r = x / d
    T m = x % d;
    T h = d / 2;
    if ((x >= 0) && (m > h))
        r = r + 1;
    else if ((x < 0) && (-m > h))
        r = r - 1
    return r;
}

其余的只是幫助編譯器生成更好代碼的優化形式。 它通過利用條件的結果是一個布爾值這一事實來避免條件執行,當轉換為整數類型時,布爾值變為 0(假)或 1(真)。 (m + h) < 0-m > h的另一種形式,當 T 是無符號類型時,某些編譯器不喜歡這種形式。 對於無符號類型,大多數條件都是恆定的,將被優化掉。
rounddiv_away 函數的工作方式類似,但調整條件以包含精確的中間值。
如果除法器d為負數,請使用: -rounddiv(n, -d) (僅當 d 是類型的最小可能值時才溢出)。 通過組合上面兩個模板的部分,也可以向上舍入(到正無窮大)或向下舍入(到負無窮大)。

如果您使用浮點數,如果兩個整數同樣接近,您將四舍五入到最接近的偶數。 例如 round(5./2.) = 2,而上面提供的 integer 方法給出 5/2 = 3。舍入到最近或偶數方法對於最小化累積舍入誤差很有用。 參見https://en.wikipedia.org/wiki/Rounding#Rounding_half_to_even

要在不使用浮點數的情況下執行此操作,如果余數等於除數的一半並且除法結果為奇數,則必須從零開始舍入。

此代碼適用於正整數:

unsigned int rounded_division(unsigned int n, unsigned int d) {
    unsigned int q = n / d;  // quotient
    unsigned int r = n % d;  // remainder
    if (r > d>>1  // fraction > 0.5
    || (r == d>>1 && (q&1) && !(d&1))) { // fraction == 0.5 and odd
        q++;
    }
    return q;
}

嘗試使用進行四舍五入的數學 ceil 函數。 數學賽爾

我遇到了同樣的困難。 下面的代碼應該適用於正整數。

我還沒有編譯它,但我在谷歌電子表格上測試了算法(我知道,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;
}

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

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

當然,這並不能處理由於輸入數據無效而導致返回值不正確而導致的問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM