C / C ++에서 정수 나누기의 빠른 상한


262

정수 값 xyC 및 C ++는 모두 q = x/y부동 소수점의 바닥을 몫으로 반환 합니다. 천장을 반환하는 방법에 관심이 있습니다. 예를 들어, ceil(10/5)=2ceil(11/5)=3.

확실한 접근 방식은 다음과 같습니다.

q = x / y;
if (q * y < x) ++q;

이를 위해서는 추가 비교와 곱셈이 필요합니다. 그리고 내가 본 (실제로 사용 된) 다른 방법은 float또는 로 캐스팅하는 것과 관련이 있습니다 double. 추가 곱셈 (또는 두 번째 나눗셈)과 분기를 피하고 부동 소수점 숫자로 캐스팅을 피하는보다 직접적인 방법이 있습니까?


70
나누기 명령어는 종종 몫과 나머지를 동시에 반환하므로 곱할 필요가 없으며 q = x/y + (x % y != 0);충분합니다
phuclv

2
@ LưuVĩnhPhúc이 의견은 받아 들여질만한 답변이어야합니다, imo.
Andreas Grapentin

1
@ LưuVĩnhPhúc 진지하게 답변으로 추가해야합니다. 난 그냥 codility test 동안 내 대답에 사용했습니다. 대답의 모드 부분이 어떻게 작동하는지 확실하지 않지만 작업을 수행했지만 매력처럼 작동했습니다.
Zachary Kraus

2
@AndreasGrapentin Miguel Figueiredo의 아래 답변은 Lưu Vĩnh Phúc이 위의 의견을 남기기 거의 1 년 전에 제출되었습니다. 매력적인 미겔의 솔루션이 얼마나 매력적이고 우아한 지 이해하지만,이 늦은 날짜에 수용된 답변을 바꾸려는 경향이 없습니다. 두 가지 접근 방식 모두 건전합니다. 그것에 대해 충분히 강하게 느낀다면 아래의 미겔의 답변을 상향 투표하여지지를 보여주십시오.
그리고

1
이상하게도, 제안 된 솔루션에 대한 제정신 측정 또는 분석을 보지 못했습니다. 뼈 근처에서의 속도에 대해서는 이야기하지만 아키텍처, 파이프 라인, 분기 명령 및 클럭주기에 대한 논의는 없습니다.
Rado

답변:


394

양수

unsigned int x, y, q;

반올림하려면 ...

q = (x + y - 1) / y;

또는 (x + y에서 오버플로 방지)

q = 1 + ((x - 1) / y); // if x != 0

6
@ bitc : 음수의 경우 C99가 0으로 반올림을 지정한다고 생각하므로 x/y나누기 한도입니다. C90은 반올림 방법을 지정하지 않았으며 현재 C ++ 표준도 그렇게 생각하지 않습니다.
David Thornley

6
Eric Lippert의 게시물을 참조하십시오 : stackoverflow.com/questions/921180/c-round-up/926806#926806
Mashmagar

3
참고 :이 오버플로가 발생할 수 있습니다. q = ((long long) x + y-1) / y는 그렇지 않습니다. 내 코드는 느리므로 숫자가 오버플로되지 않는다는 것을 알고 있다면 Sparky 버전을 사용해야합니다.
Jørgen Fogh

1
@ bitc : David의 요점은 결과가 부정적이라면 위의 계산을 사용하지 않을 것이라고 믿었습니다. 그냥 사용하십시오q = x / y;
caf

12
두 번째는, X는 0 CEIL (0 / Y) = 0 인 문제가 있지만, 1 리턴
Omry Yadan에게

78

양수의 경우 :

    q = x/y + (x % y != 0);

5
가장 일반적인 아키텍처의 나누기 명령에는 나머지 결과도 포함되므로 실제로 하나의 나누기가 필요하고 매우 빠릅니다.
phuclv

58

Sparky의 대답은이 문제를 해결하는 하나의 표준 방법이지만 내 의견에 썼 듯이 오버플로의 위험이 있습니다. 더 넓은 유형을 사용하면이 문제를 해결할 수 있지만 long longs 를 나누려면 어떻게해야 합니까?

Nathan Ernst의 답변은 하나의 솔루션을 제공하지만 함수 호출, 변수 선언 및 조건부와 관련이 있습니다. 이는 OPs 코드보다 짧지 않으며 최적화하기가 더 어려워 아마도 느릴 수 있습니다.

내 해결책은 이것입니다 :

q = (x % y) ? x / y + 1 : x / y;

모듈로와 나누기는 프로세서에서 동일한 명령을 사용하여 수행되기 때문에 OPs 코드보다 약간 빠릅니다. 컴파일러는 이들이 동등한 것을 알 수 있기 때문입니다. 적어도 gcc 4.4.1은 x86에서 -O2 플래그를 사용하여이 최적화를 수행합니다.

이론적으로 컴파일러는 Nathan Ernst의 코드에서 함수 호출을 인라인하고 동일한 것을 방출 할 수 있지만 gcc는 테스트 할 때 그렇게하지 않았습니다. 컴파일 된 코드를 단일 버전의 표준 라이브러리에 연결하기 때문일 수 있습니다.

마지막으로, 매우 엄격한 루프 상태에 있고 모든 데이터가 레지스터 또는 L1 캐시에있는 경우를 제외하고는 최신 머신에서는이 문제가 중요하지 않습니다. 그렇지 않으면 Nathan Ernst를 제외하고 이러한 모든 솔루션이 똑같이 빠를 것입니다.이 기능은 주 메모리에서 함수를 가져와야하는 경우 상당히 느려질 수 있습니다.


3
오버플로를 수정하는 더 쉬운 방법이있었습니다. 간단히 y / y를 줄이십시오.q = (x > 0)? 1 + (x - 1)/y: (x / y);
Ben Voigt

-1 : 값 비싼 %를 싼 값으로 거래하기 때문에 이것은 비효율적 인 방법입니다. OP 접근법보다 나쁩니다.
Yves Daoust

2
아니 그렇지 않아. 대답에서 설명한 것처럼 이미 나누기를 수행 할 때 % 연산자는 무료입니다.
Jørgen Fogh

1
그렇다면 표현 q = x / y + (x % y > 0);보다 쉬운 ? :가요?

그것은 "쉬운"의 의미에 달려 있습니다. 컴파일러의 번역 방법에 따라 더 빠르거나 빠를 수 있습니다. 내 추측은 느리지 만 확실하게 측정해야합니다.
Jørgen Fogh 2014 년

18

divcstdlib 의 함수를 사용하여 한 번의 호출로 몫 및 나머지를 얻은 다음 아래에서와 같이 천장을 개별적으로 처리 할 수 ​​있습니다

#include <cstdlib>
#include <iostream>

int div_ceil(int numerator, int denominator)
{
        std::div_t res = std::div(numerator, denominator);
        return res.rem ? (res.quot + 1) : res.quot;
}

int main(int, const char**)
{
        std::cout << "10 / 5 = " << div_ceil(10, 5) << std::endl;
        std::cout << "11 / 5 = " << div_ceil(11, 5) << std::endl;

        return 0;
}

12
더블 뱅의 흥미로운 사례로, 당신도 할 수 있습니다 return res.quot + !!res.rem;:)
Sam Harwell

ldiv가 항상 인수를 long long으로 홍보하지는 않습니까? 업 캐스트 또는 다운 캐스팅 비용이 들지 않습니까?
einpoklum

12

이건 어때요? (y가 음수가 아닌 것을 요구하므로 y가 음이 아닌 보증이없는 변수 인 드문 경우에는 사용하지 마십시오)

q = (x > 0)? 1 + (x - 1)/y: (x / y);

나는 1로 줄었고 y/y용어를 없애고 x + y - 1넘칠 가능성이있었습니다.

부호없는 유형이고 0을 포함 x - 1할 때 감싸는 것을 피 x합니다.

signed의 경우 x, 음수와 0은 여전히 ​​단일 사례로 결합됩니다.

아마도 현대의 범용 CPU에 큰 이점은 아니지만 임베디드 시스템에서는 다른 정답보다 훨씬 빠릅니다.


아무것도 계산하지 않아도 항상 0을 반환합니다.
Ruud Althuizen 8:21에

@Ruud : 사실이 아닙니다. 고려 X = -45와 y = 4
벤 보이트

7

긍정과 부정 모두에 대한 솔루션이 x있지만 y분기가 하나만 있고 분기가없는 긍정에 대한 솔루션이 있습니다 .

int ceil(int x, int y) {
    return x / y + (x % y > 0);
}

참고 경우 x입니다 긍정적 인 다음 부문은 0에 가까워, 그리고 알림이 0이 아닌 경우, 우리는 하나를 추가해야합니다.

경우 x부정적인는 다음 부문은 우리가 필요로 무엇을, 0에 가까워이며, 때문에 우리는 아무것도 추가하지 않습니다 x % y긍정적 아니다


y가 일정한 일반적인 경우가 있기 때문에 흥미 롭습니다
Wolf

1
mod는 나눗셈이 필요하므로 여기서는 하나의 나눗셈이 아니라 complier가 두 개의 유사한 나누기를 하나로 최적화 할 수 있습니다.
M.kazem Akhgary

4

양수 또는 음수에 적용됩니다.

q = x / y + ((x % y != 0) ? !((x > 0) ^ (y > 0)) : 0);

나머지가있는 경우, 검사가 있는지 xy같은 부호이며, 추가 1따라.


3

오히려 댓글을 달았지만 담당자가 충분하지 않습니다.

내가 아는 한 긍정적 인 주장과 2의 제곱 인 제수의 경우 이것이 가장 빠른 방법입니다 (CUDA에서 테스트).

//example y=8
q = (x >> 3) + !!(x & 7);

일반적인 긍정적 인 주장의 경우, 나는 그렇게하는 경향이 있습니다.

q = x/y + !!(x % y);

현대 CUDA에서 성능 과 솔루션 측면에서 어떻게 q = x/y + !!(x % y);쌓이는 지를 보는 것은 흥미로울 것 입니다. q = x/y + (x % y == 0);q = (x + y - 1) / y;
Greg Kramida


-2

O3로 컴파일, 컴파일러는 최적화를 잘 수행합니다.

q = x / y;
if (x % y)  ++q;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.