2014-10-01 2 views
1

음이 아닌 정수 c는 I 등가 최대 정수 x되도록빠른 정수 용액 (X-1)/2 = C

x*(x-1)/2 <= c 

를 찾는 효율적인 알고리즘을 필요로 주어 I 필요한 나는이 질문에 C++를 태그 defineteness을 위해서

x = floor((1 + sqrt(1 + 8*c))/2)  (1) 

, 그래서 대답은 그 언어로 작성된 함수이어야한다 : 효율적이고 안정적으로 정확한 알고리즘을 계산합니다. c은 부호없는 32 비트 int라고 가정 할 수 있습니다.

또한 최신 프로세서의 부동 소수점이 정수 알고리즘보다 빠르기 때문에 (1) (또는 부동 소수점 산술을 포함하는 동등한 표현식)이 항상 올바른 결과를 제공한다는 것을 증명할 수 있다면 유효 답변입니다. .

+0

'c'의 예상 범위는 무엇입니까? – phs

+0

@phs 'c'는 32 비트 부호없는 정수라고 가정 할 수 있습니다. – becko

+0

고맙습니다. 한 가지 더 궁금한 점이 있습니다. "효율성이 얼마나 효율적입니까?" 이미'O (1)'닫힌 폼을 찾았습니다. (실제로 쿼드 수식을 통해 아직 계수를 실행하지 않았다고 가정합니다.) 조금씩 복잡한 해킹을 찾고 있습니까? – phs

답변

5

IEEE가 제곱근을 포함한 모든 연산에 대해 올바른 반올림을 수행한다고 가정하려면 귀하가 작성한 표현식에 더하기 캐스팅을 더해 모든 입력에 대해 올바른 답을 제공하십시오.

다음은 비공식적 인 증거입니다. c은 32 비트 부호없는 정수로 53 비트 유효 숫자로 부동 소수점 유형으로 변환되므로 1 + 8*(double)c은 exact이고 sqrt(1 + 8*(double)c)은 올바르게 반올림됩니다. 1 + sqrt(1 + 8*(double)c)2**((32 + 3)/2) = 2**17.5보다 작기 때문에 2으로 나누기가 정확하기 때문에 마지막 기간이 2**((32 + 3)/2) = 2**17.5보다 작 으면 1보다 작고 따라서 (1 + sqrt(1 + 8*(double)c))/2이 하나의 ulp 내에서 정확하기 때문에 하나의 ulp 내에서 정확합니다.

마지막 사업은 바닥입니다. 문제의 경우는 (1 + sqrt(1 + 8*(double)c))/2을 정수로 반올림 한 것입니다. 이것은 sqrt(...)이 홀수 정수로 올림하는 경우에만 발생합니다.sqrt의 인수가 정수이기 때문에, 최악의 경우는 긍정적 인 홀수 정수 z에 대한 sqrt(z**2 - 1)처럼, 우리는 테일러 전개에 의해

z - sqrt(z**2 - 1) = z * (1 - sqrt(1 - 1/z**2)) >= 1/(2*z) 

바인딩. z2**17.5보다 작기 때문에 가장 가까운 정수와의 갭은 이상이며 결과는 2**17.5보다 작습니다. 즉,이 오류는 올바르게 반올림 된 sqrt에서 발생할 수 없습니다.

Yakk의 단순화를 채택, 우리는 추가 검사없이

(uint32_t)(0.5 + sqrt(0.25 + 2.0*c)) 

를 작성할 수 있습니다.

1

2 차 공식으로 시작하면 sqrt(1/4 + 2c)에 곧바로 도달하며 1/2 이상으로 올림합니다.

이제 부동 소수점으로 계산하면 부정확 할 수 있습니다.

이러한 부정확성을 처리하는 데는 두 가지 방법이 있습니다. 첫 번째는 신중하게 결정하는 것입니다. 계산 된 값이 중요 할 정도로 절반 가까이 있는지 확인하십시오. 중요하지 않은 경우 단순히 값을 반환하십시오. 그렇다면 우리는 여전히 두 가지 가치 중 하나에 대한 답을 구속 할 수 있습니다. 정수 수학에서이 두 값을 테스트하고 리턴하십시오.

그러나 우리는주의 깊은 비트로 처리 할 수 ​​없으며 값이 32 비트이면 sqrt(1/4 + 2c)0.5보다 작은 오류가 발생하며 double을 사용합니다. (float s로 보장 할 수 없습니다. 2^31float은 반올림하지 않고 +0.5을 처리 할 수 ​​없습니다.)

본질적으로 두 가지 가능성을 줄이기 위해 이차 수식을 사용하고 그 두 가지를 테스트합니다. 당신이 원하는 경우

uint64_t eval(uint64_t x) { 
    return x*(x-1)/2; 
} 
unsigned solve(unsigned c) { 
    double test = sqrt(0.25 + 2.*c); 
    if (eval(test+1.) <= c) 
    return test+1. 
    ASSERT(eval(test) <= c); 
    return test; 
} 

주 핵심 유형에 긍정적 인 double의 변환은 0으로 반올림 것을 당신은 floor의를 삽입 할 수 있습니다.

1

질문에 조금 접하게 될 수 있습니다. 그러나, 나의 주목을 끌었던 것은 특별한 공식이다. T n - 1 (여기서 T n은 삼각 숫자 입니다)의 삼각형 루트를 찾으려고합니다.

즉 :

T N = n 개의 * (N + 1)/2

T N - N = T N - 1 = N * (n-1)/2

nifty trick descriptions 우리가 T N 에드 here :

N = INT (SQRT (2 * c))

는 N 구함되도록 T N - 1이 경우 C ≤ 원래의 질문에서와 같은 이유로, n의 정의를 변경하지 않습니다.

계산적으로 계산하면 몇 가지 작업을 절약 할 수 있으므로 이론적으로는 정확한 솔루션 (1)보다 입니다. 실제로, 그것은 아마 거의 같습니다.

이 솔루션이나 David가 제시 한 솔루션은 귀하의 (1)과 같이 "정확한"것이 아닙니다. 정확한 (흰색 선 VS

Exact vs int(sqrt(2 * c))

층 ((1 + SQRT (1 개 + 8 * 온도))/2) INT VS (청색) (SQRT (2 * c)) (적색))


Exact vs int(sqrt(0.25 + 2 * c) + 0.5)

층 ((1 + SQRT (1 + 8 * c))/2) (파란색) 대 int (sqrt (0.25 + 2 * c) +0.5 정확한 (흰색 선) 대 (빨간색)

내 진짜 포인트는 삼각형 숫자가 사각형 등 파스칼의 삼각형, 피보나치 수열,에 연결되어있는 숫자의 재미 세트 있다는 것입니다. al.

제곱근을 필요로하지 않는 방식으로 문제를 재 배열하는 데 사용할 수있는 주위에 loads of identities이 있습니다. 당신이 didn를하는 경우 1 = N 2

나는 당신을 믿고있어 당신이 삼각수와 협력하고 있음을 알고 있지만 - T N + T n이 있다고 할 수있다 특별한 관심의

삼각형의 뿌리를 찾는 것은 같은 주제에 따라 this one과 같은 몇 가지 질문을 산출한다는 것을 깨닫지 못합니다.