programing

반올림 정수 분할(잘리는 대신)

prostudy 2022. 5. 11. 22:00
반응형

반올림 정수 분할(잘리는 대신)

나는 어떻게 숫자를 가장 가까운 정수로 반올림할 수 있는지 궁금했다.예를 들어 다음과 같은 경우:

int a = 59 / 4;

부동소수로 계산하면 14.75가 된다. 결과를 15로 "a"에 저장하려면 어떻게 해야 하는가?

정수 반올림을 위한 표준 숙어는 다음과 같다.

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

배당금에서 1을 뺀 값을 더하면 된다.

배당금 및 배당금 부호에 적용되는 코드:

int divRoundClosest(const int n, const int d)
{
  return ((n < 0) ^ (d < 0)) ? ((n - d/2)/d) : ((n + d/2)/d);
}

"왜 이게 실제로 효과가 있지?"라는 댓글에 대해 우리는 이것을 분열시킬 수 있다.첫째, 을 관찰하라.n/d몫은 되겠지만, 반올림하지 않고 0을 향해 잘린다.분자의 절반을 분자에 더하고 분자와 분모가 같은 부호를 갖는 경우에만 반올림한 결과가 나온다.부호가 다르면 분모를 절반 빼서 나누어야 한다.이 모든 것을 종합해 보면:

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

리눅스 커널 매크로 DIV_RONG_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;
}

(편집) 부동 소수점 반올림 정수는 이 문제에 대한 가장 쉬운 해결책이지만, 문제 세트에 따라 가능한 경우도 있다.예를 들어 임베디드 시스템에서 부동소수점 솔루션은 비용이 너무 많이 들 수 있다.

정수수학을 이용해 이렇게 하는 것은 좀 어렵고 약간 비양심적인 것으로 밝혀졌다.처음 게시된 솔루션은 내가 사용했던 문제에 대해 잘 작동했지만, 정수 범위에 대한 결과를 특성화한 결과 전반적으로 매우 좋지 않은 것으로 드러났다.약간의 빈둥거림과 내장된 수학에 관한 책을 몇 권 훑어보면 거의 결과가 나오지 않는다.음표 몇 장.첫째, 나는 양의 정수만을 검사했는데, 내 작업은 음의 분자나 분모를 포함하지 않는다.둘째로, 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를 제대로 돌아야 하기 때문에 실제로 반복적이다.) 첫 번째 경우 넘치다가 흐른 경우가 많으면 두 사람은 서로를 취소한다.다음은 두 가지(잘못된) 알고리즘의 오류 그림이다.라운드1 8비트 x=숫자 y=분수기로 나누기

이 그래프는 첫 번째 알고리즘이 작은 분모(0 < d < 10)에 대해서만 부정확하다는 것을 보여준다.의외로 2판보다 큰 숫자를 더 잘 다룬다.

두 번째 알고리즘의 플롯은 다음과 같다.8비트 서명 번호 2번째 알고리즘.

예상대로 작은 숫자의 경우 실패하지만 첫 번째 버전보다 큰 숫자의 경우에도 실패한다.

확실히 이것이 올바른 버전에 대한 더 나은 출발점:

#define DIVIDE_WITH_ROUND(N, D) (((N) == 0) ? 0:(((N * 10)/D) + 5)/10)

만약 당신의 분모가 10 이상이라면, 이것은 정확하게 작동할 것이다.

D == 1에 대해서는 특별한 경우가 필요하며, 간단히 N을 반환한다. D== 2, = N/2 + (N & 1) // 홀수일 경우 반올림한다.

D >=3 또한 N이 충분히 커지면 문제가 생긴다.분모가 크면 분자만 큰 것으로 나타났다.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 나누기(truncation)만 하면 오차 < 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%이다.

2021년 5월 2일 업데이트:

아래 나의 원래 대답에 있는 코드는 의 정수만을 위한 것이다.음의 정수에 대한 적절한 반올림을 처리하려면 여기 내 프로젝트 repo의 기법을 참조하십시오. 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 다다미 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. 위의 모든 것을 테스트하기 위한 여러 유닛 테스트.

원본 답변:

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

전체 답변:

이 대답들 중 일부는 미친 듯이 보인다!코드페이스가 못을 박았지만! (@0xC0 참조)여기서 DeFACE의 답변).함수형보다 형식 없는 매크로나 gcc 문장의 표현형식이 정말 마음에 드는데, 그래서 내가 하고 있는 일(즉, 수학적으로 왜 이런 일이 일어나는지)에 대한 자세한 설명과 함께 이 답을 써서 다음과 같은 두 가지 형태로 표현했다.

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 프로그래밍에서 고정 산술 - 이 답변에서 정수 반올림을 가장 가까운 전체 정수로 한 다음 10위(십진수 오른쪽에 소수 1자리), 100위(2자리), 1000위(3자리) 등으로 하는 방법을 검토한다.내 코드 설명의 섹션에 대한 응답 검색BASE 2 CONCEPT:더 자세한 것은!
  2. gcc의 성명 표현에 대한 나의 관련 답변: MIN과 MAX in C
  3. 고정 유형을 사용하는 기능 형식: 반올림 정수 분할(잘라내기 대신)
  4. 정수분할의 동작은?
  5. 가장 가까운 정수 대신 반올림하려면 비슷한 패턴: 반올림 정수 분할(잘림 대신)을 따르십시오.

참조:

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

todo: 음의 입력에 대해 테스트하고, 작동하면 이 답변을 업데이트하십시오.

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

From 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));    \
}                           \
)

다음은 부동소수점이나 조건부 분기가 없는 양의 피연산자와 음의 피연산자 모두에 대해 가장 가까운 정수로 정확히 반올림한다(아래 어셈블리 출력 참조).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)과 동일한 부호를 가지며, divisor(y)의 크기는 절반이다.따라서 배당에 라운딩을 추가하면 정수 구분이 결과 지수를 자르기 전에 그 크기가 커진다.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

@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)))
#define CEIL(a, b) (((a) / (b)) + (((a) % (b)) > 0 ? 1 : 0))

또 다른 유용한 MACRO(Must Have):

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

여기 내 해결책이 있다.나는 그것이 더 읽기 쉽다는 것과 분기가 없기 때문에 그것을 좋아한다.

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);
}
int a, b;
int c = a / b;
if(a % b) { c++; }

나머지가 있는지 확인하면 정수분할의 몫을 수동으로 반올림할 수 있다.

대신 다음과 같은 것을 사용해야 한다.

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...인 경우에만 언더플로우된다.

반올림하는 수학 ceil 함수를 사용해봐.수학 실!

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. 만약 x-y[0.5]라면 y가 해결책이다.
  4. 그렇지 않으면 y+1이 해결책이다.

쓰여진 대로, 정수 산수를 하고 있는데, 이것은 자동적으로 소수점 결과를 잘라낸다.부동 소수점 산술을 수행하려면 상수를 부동 소수점 값으로 변경하십시오.

int a = round(59.0 / 4);

또는 에 그들을 캐스팅한다.float또는 기타 부동 소수점 유형:

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

어느 쪽이든 최종 라운딩은 선수단과 함께 해야 한다.round()의 기능을 하다.math.h머리글은 반드시#include <math.h>C99 호환 컴파일러를 사용하십시오.

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

eg 59/4 지수 = 14, tempY = 2, 나머지 = 3, 나머지 >=Y 따라서 지수 = 15;

양의 정수를 나누려면 위쪽으로 이동하여 실제 b0의 오른쪽에 있는 비트를 확인하십시오.즉, 100/8은 12.5이지만 12를 반환할 것이다.(100<1/8)을 하면 b0을 확인한 다음 결과를 다시 아래로 이동한 후 반올림할 수 있다.

어떤 알고리즘에서는 '가장 중요한' 것이 동점일 때 일관된 편견이 필요하다.

// 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)플랫폼에 의존할 가능성이 높다.

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

부분 부분 part 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(썸-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; 
}

첫 번째 코드 라인은 전체 단어를 통해 분자 기호 비트를 복제하여 0(양) 또는 -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

정수 눈금이 0으로 잘리는 반면, 잘라진 눈금은 음의 무한대로 바뀐다.이렇게 하면 분자의 부호와 상관없이 항상 반올림 값을 추가하기 때문에 반올림 시프트가 훨씬 간단해진다.

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의 정수 눈금은 0을 향해 잘리며, 라운드div, 라운드div(3, 2) == 1과 라운드div(-3, 2) == -1이다.rounddiv_away 함수 템플릿은 절반 지점을 0, 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이 0을 향해 잘린다.즉, 리마인더의 절대값(-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)(표시를 변경할 때 오버플로우될 수 있음).위의 두 템플릿의 일부를 조합하여 반올림(인피니티+인피니티)이나 반올림(-인피니티)도 가능하다.

나도 같은 어려움에 부딪쳤다.아래 코드는 양의 정수에 대해 작동해야 한다.

아직 컴파일하지 않았지만 구글 스프레드시트(I know, 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;

이것은 물론 당신의 잘못된 입력 데이터로 인해 잘못된 반환 값을 갖게 되면서 발생하는 문제를 다루지 않는다.

참조URL: https://stackoverflow.com/questions/2422712/rounding-integer-division-instead-of-truncating

반응형