반올림 정수 분할(잘리는 대신)
나는 어떻게 숫자를 가장 가까운 정수로 반올림할 수 있는지 궁금했다.예를 들어 다음과 같은 경우:
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를 제대로 돌아야 하기 때문에 실제로 반복적이다.) 첫 번째 경우 넘치다가 흐른 경우가 많으면 두 사람은 서로를 취소한다.다음은 두 가지(잘못된) 알고리즘의 오류 그림이다.
이 그래프는 첫 번째 알고리즘이 작은 분모(0 < d < 10)에 대해서만 부정확하다는 것을 보여준다.의외로 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.
여기에는 다음이 포함된다.
- 다음 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 다다미 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
전체 답변:
이 대답들 중 일부는 미친 듯이 보인다!코드페이스가 못을 박았지만! (@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;
}
여기서 이 코드 중 일부를 실행하고 테스트하십시오.
관련 답변:
- C 프로그래밍에서 고정 점 산술 - 이 답변에서 정수 반올림을 가장 가까운 전체 정수로 한 다음 10위(십진수 오른쪽에 소수 1자리), 100위(2자리), 1000위(3자리) 등으로 하는 방법을 검토한다.내 코드 설명의 섹션에 대한 응답 검색
BASE 2 CONCEPT:
더 자세한 것은! - gcc의 성명 표현에 대한 나의 관련 답변: MIN과 MAX in C
- 고정 유형을 사용하는 기능 형식: 반올림 정수 분할(잘라내기 대신)
- 정수분할의 동작은?
- 가장 가까운 정수 대신 반올림하려면 비슷한 패턴: 반올림 정수 분할(잘림 대신)을 따르십시오.
참조:
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);
- 59.0/4의 정확한 부동액 값을 x로 한다(여기서는 14.750000).
- x보다 작은 정수를 y로 두십시오(여기서는 14).
- 만약 x-y[0.5]라면 y가 해결책이다.
- 그렇지 않으면 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
'programing' 카테고리의 다른 글
Axios/Vuex api 쿼리가 작동하지 않음, 매개 변수 오류 (0) | 2022.05.11 |
---|---|
Vue.js 문 닫는 동안 차단되지 않음(진행이 완료될 때까지 백엔드 대기) (0) | 2022.05.11 |
휘발성 대 원자성 (0) | 2022.05.11 |
강박 대 강박. (0) | 2022.05.11 |
AJAX 호출을 위해 Vuex Store 데이터를 준비하는 적절한 방법 (0) | 2022.05.10 |