簡體   English   中英

沒有邏輯運算符的舍入整數除法

[英]Rounding integer division without logical operators

我想要一個功能

int rounded_division(const int a, const int b) { 
    return round(1.0 * a/b); 
}

所以我們有,例如,

rounded_division(3, 2) // = 2
rounded_division(2, 2) // = 1
rounded_division(1, 2) // = 1
rounded_division(0, 2) // = 0
rounded_division(-1, 2) // = -1
rounded_division(-2, 2) // = -1
rounded_division(-3, -2) // = 2

或者在代碼中,其中ab是32位有符號整數:

int rounded_division(const int a, const int b) {
    return ((a < 0) ^ (b < 0)) ? ((a - b / 2) / b) : ((a + b / 2) / b);
}

這里談到棘手的問題:如何有效地實現這家伙(不使用較大的64個值),並沒有一個邏輯運算符 ,如?: && ,...? 有可能嗎?

我之所以想避免使用邏輯運算符,因為我必須實現此函數的處理器,沒有條件指令( 更多關於ARM上缺少條件指令的原因 )。

a/b + a%b/(b/2 + b%2)運行良好 - 在十億+測試案例中沒有失敗。 它符合所有OP的目標:無溢出,無long long ,無分支,在定義a/b時可在整個int范圍內工作。

沒有32位依賴。 如果使用C99或更高版本,則沒有實現行為限制。

int rounded_division(int a, int b) {
  int q = a / b;
  int r = a % b;
  return q + r/(b/2 + b%2);
}

這適用於2的補碼,1的補碼和符號幅度,因為所有操作都是數學運算。

這個怎么樣:

int rounded_division(const int a, const int b) {
    return (a + b/2 + b * ((a^b) >> 31))/b;
}

(a ^ b) >> 31如果ab具有不同的符號則應評估為-1否則a 0 ,假設int具有32位且最左邊是符號位。

編輯

正如@chux在他的評論中指出的那樣,由於整數除法,這種方法是錯誤的。 這個新版本的評估與OP的示例相同,但包含更多操作。

int rounded_division(const int a, const int b) {
    return (a + b * (1 + 2 * ((a^b) >> 31)) / 2)/b;
}

但是,此版本仍未考慮溢出問題。

關於什么

  ...
  return ((a + (a*b)/abs(a*b) * b / 2) / b);
}

沒有溢出:

  ...
  return ((a + ((a/abs(a))*(b/abs(b))) * b / 2) / b);    
}

這是您可能使用的粗略方法。 如果操作a * b <0,則使用掩碼應用某些內容。

請注意,我沒有對此進行適當的測試。

int function(int a, int b){
    int tmp = float(a)/b + 0.5;
    int mask = (a*b) >> 31; // shift sign bit to set rest of the bits

    return tmp - (1 & mask);//minus one if a*b was < 0
}

以下rounded_division_test1()符合OP的無分支要求 - 如果將sign(int a)nabs(int a)cmp_le(int a, int b)為非分支。 請參閱此處 ,了解如何在不使用比較運算符的情況下執行sign() 這些輔助函數可以在沒有顯式調用的情況下滾動到rounded_division_test1()

該代碼演示了正確的功能,可用於測試各種答案。 定義a/b ,此答案不會溢出。

#include <limits.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

int nabs(int a) {
  return (a < 0) * a - (a >= 0) * a;
}

int sign(int a) {
  return (a > 0) - (a < 0);
}

int cmp_le(int a, int b) {
  return (a <= b);
}

int rounded_division_test1(int a, int b) {
  int q = a / b;
  int r = a % b;
  int flag = cmp_le(nabs(r), (nabs(b) / 2 + nabs(b % 2)));
  return q + flag * sign(b) * sign(r);
}

// Alternative that uses long long
int rounded_division_test1LL(int a, int b) {
  int c = (a^b)>>31;
  return (a + (c*2 + 1)*1LL*b/2)/b;
}

// Reference code
int rounded_division(int a, int b) {
  return round(1.0*a/b);
}

int test(int a, int b) {
  int q0 = rounded_division(a, b);
  //int q1 = function(a,b);
  int q1 = rounded_division_test1(a, b);
  if (q0 != q1) {
    printf("%d %d --> %d %d\n", a, b, q0, q1);
    fflush(stdout);
  }
  return q0 != q1;
}

void tests(void) {
  int err = 0;
  int const a[] = { INT_MIN, INT_MIN + 1, INT_MIN + 1, -3, -2, -1, 0, 1, 2, 3,
      INT_MAX - 1, INT_MAX };
  for (unsigned i = 0; i < sizeof a / sizeof a[0]; i++) {
    for (unsigned j = 0; j < sizeof a / sizeof a[0]; j++) {
      if (a[j] == 0) continue;
      if (a[i] == INT_MIN && a[j] == -1) continue;
      err += test(a[i], a[j]);
    }
  }
  printf("Err %d\n", err);
}

int main(void) {
  tests();
  return 0;
}

讓我給出我的貢獻:

關於什么:

int rounded_division(const int a, const int b) {
    return a/b + (2*(a%b))/b;
}

沒有分支,沒有邏輯運算符,只有數學運算符。 但如果b大於INT_MAX / 2或小於INT_MIN / 2,則可能失敗。

但是如果允許64位計算32位輪次。 它不會失敗

int rounded_division(const int a, const int b) {
    return a/b + (2LL*(a%b))/b;
}

我提出的代碼用於ARM M0(沒有浮點,慢速划分)。 它只使用一個除法指令而沒有條件,但如果分子+(分母/ 2)> INT_MAX則會溢出。

ARM M0上的循環計數= 7個循環+除法(M0沒有除法指令,因此它取決於工具鏈)。

int32_t Int32_SignOf(int32_t val)
{
    return (+1 | (val >> 31)); // if v < 0 then -1, else +1
}

uint32_t Int32_Abs(int32_t val)
{
    int32_t tmp = val ^ (val >> 31);
    return (tmp - (val >> 31));

    // the following code looks like it should be faster, using subexpression elimination
    // except on arm a bitshift is free when performed with another operation,
    // so it would actually end up being slower
    // tmp = val >> 31;
    // dst = val ^ (tmp);
    // dst -= tmp;
    // return dst;
}

int32_t Int32_DivRound(int32_t numerator, int32_t denominator)
{
    // use the absolute (unsigned) demominator in the fudge value
    // as the divide by 2 then becomes a bitshift
    int32_t  sign_num  = Int32_SignOf(numerator);
    uint32_t abs_denom = Int32_Abs(denominator);
    return (numerator + sign_num * ((int32_t)(abs_denom / 2u))) / denominator;
}

因為函數似乎是對稱的如何關於sign(a/b)*floor(abs(a/b)+0.5)

暫無
暫無

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

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