IT story

표준 C ++ 라이브러리에서`int pow (int base, int exponent)`가 아닌 이유는 무엇입니까?

hot-time 2020. 8. 12. 20:40
반응형

표준 C ++ 라이브러리에서`int pow (int base, int exponent)`가 아닌 이유는 무엇입니까?


나는 그것을 찾을 수 없을 것 같습니다. C ++ pow함수가 floats 및 doubles를 제외하고 "power"함수를 구현하지 않는 이유가 있습니까?

구현이 사소하다는 것을 알고 있으며 표준 라이브러리에 있어야하는 작업을하고있는 것처럼 느껴집니다. 강력한 power 함수 (즉, 일관되고 명시적인 방식으로 오버플로 처리)는 작성하는 것이 재미 있지 않습니다.


사실 그렇습니다.

C ++ 11부터 pow(int, int)--- 의 템플릿 구현이 있으며 더 일반적인 경우는 http://en.cppreference.com/w/cpp/numeric/math/pow의 (7)을 참조하십시오 .


편집 : 순수 주의자들은 실제로 "승진 된"타이핑이 사용되기 때문에 이것이 정확하지 않다고 주장 할 수 있습니다. 어떤 식 으로든 매개 변수 int대한 올바른 결과 또는 오류를 얻습니다 int.


현재 C++11, 전원 함수 (및 기타) 제품군에 특수 사례가 추가되었습니다. C++11 [c.math] /11모든 float/double/long double오버로드를 나열한 후 상태 (내 강조 및 의역) :

또한 매개 변수에 해당하는 인수 가 유형 또는 정수 유형 을 갖는 경우 매개 변수에 해당하는 모든 인수 가 효과적으로 캐스트 되도록 보장하기에 충분한 추가 오버로드가 있어야합니다 .doubledoubledoubledouble

따라서 기본적으로 정수 매개 변수는 연산을 수행하기 위해 두 배로 업그레이드됩니다.


이전에는 C++11(질문을 받았을 때) 정수 오버로드가 없었습니다.

나는 어느 쪽도 밀접의 제작자와 연관되지 않았기 때문에 CC++(내가 비록 그들의 창조의 일 입니다 오히려 이전)이나 표준을 만든 ANSI / ISO위원회의 한 부분이 반드시 나의 부분에 대한 의견입니다. 나는 그것이 정보에 입각 한 의견 이라고 생각하고 싶지만, 아내가 당신에게 말할 것입니다 (자주 그리고 많은 격려가 필요하지 않음), 나는 이전에 틀 렸습니다 :-)

그 가치에 대한 가정은 다음과 같습니다.

나는 의심 원래 사전 ANSI가 이유 있다고 C는 완전히 불필요하기 때문에이 기능을하지 않았다입니다. 첫째, 정수 거듭 제곱을 수행하는 완벽하게 좋은 방법이 이미있었습니다 (복수를 사용하고 단순히 정수로 다시 변환하여 변환 전에 정수 오버플로 및 언더 플로를 확인).

둘째, 당신이 기억해야 할 또 다른 점은의 원래 의도가 있다는 것이다 CA와이었다 시스템 언어 프로그래밍, 그리고 부동 소수점하는 것은 전혀 그 분야에서 바람직 있는지 의문이다.

초기 사용 사례 중 하나가 UNIX를 코딩하는 것이었기 때문에 부동 소수점은 거의 쓸모가 없었을 것입니다. C가 기반을 둔 BCPL은 또한 거듭 제곱을 사용하지 않았습니다 (메모리에서 부동 소수점이 전혀 없음).

제쳐두고, 적분 전력 연산자는 아마도 라이브러리 호출이 아니라 이항 연산자 일 것입니다. 당신은 두 개의 정수를 추가하지 마십시오 x = add (y, z)하지만과 x = y + z의 부분 - 언어 적절한 보다는 도서관.

셋째, 적분 력의 구현이 비교적 사소하기 때문에 언어 개발자가 더 유용한 정보를 제공하는 데 시간을 더 잘 활용할 수있을 것입니다 (기회 비용에 대한 아래 설명 참조).

그것은 또한 원본과 관련이 C++있습니다. 원래 구현은 사실상 C코드 를 생성하는 번역기 였기 때문에 C. 원래 의도는 C-with-classes-plus-a-little-bit-of-extra-math-stuff가 아니라 C-with-classes였습니다.

이전 C++11표준에 추가 된 적이없는 이유 는 표준 설정 기관에 따라야 할 특정 지침이 있다는 것을 기억해야합니다. 예를 들어 ANSI C새 언어를 만드는 것이 아니라 기존 관행을 체계화하는 작업을 특별히 맡았습니다 . 그렇지 않으면 그들은 미쳐서 우리에게 Ada를 줄 수 있습니다 :-)

이 표준의 이후 반복에는 특정 지침이 있으며 근거 문서에서 찾을 수 있습니다 (위원회가 언어 자체에 대한 근거가 아닌 특정 결정을 내린 이유에 대한 근거).

예를 들어, C99근거 문서는 C89추가 할 수있는 내용을 제한하는 두 가지 기본 원칙을 구체적으로 전달합니다 .

  • 언어를 작고 단순하게 유지하십시오.
  • 작업을 수행하는 한 가지 방법 만 제공하십시오.

가이드 라인 ( 특정한 것이 아닐 수도 있음 )은 개별 작업 그룹에 대해 정해져 있으므로 C++위원회 (및 기타 모든 ISO 그룹)도 제한합니다.

또한 표준 설정 기관은 자신이 내리는 모든 결정에 기회 비용 (결정을 내리기 위해 포기해야하는 것을 의미하는 경제 용어)이 있음을 인식합니다. 예를 들어, $ 10,000 uber-gaming 기계를 구입하는 기회 비용은 약 6 개월 동안 다른 절반과의 따뜻한 관계 (또는 모든 관계)입니다.

Eric Gunnerson 은 왜 항상 Microsoft 제품에 추가되지 않는지에 대한 -100 점 설명 으로 이를 잘 설명합니다. 기본적으로 기능은 구멍에서 100 점을 시작하므로 고려할 가치가 상당히 추가되어야합니다.

다시 말해, 통합 전력 연산자 (솔직히 반 정도의 코더가 10 분 만에 작동) 또는 멀티 스레딩을 표준에 추가 하시겠습니까? 나 자신을 위해 나는 후자를 선호하고 UNIX와 Windows에서 다른 구현에 대해 고민 할 필요가 없습니다.

또한 표준 라이브러리 (해시, btrees, 빨강-검정 트리, 사전, 임의지도 등)에 대한 수천 개의 컬렉션을보고 싶습니다만, 이론적 근거는 다음과 같습니다.

표준은 구현 자와 프로그래머 간의 조약입니다.

그리고 표준기구의 구현 자 수는 프로그래머 (또는 적어도 기회 비용을 이해하지 못하는 프로그래머) 수보다 훨씬 큽니다. 모든 재료가 추가 된 경우, 다음의 표준이 C++될 것이다 C++215x아마 완전히 삼백년 그 이후 컴파일러 개발자에 의해 구현 될 것입니다.

어쨌든, 그것은 문제에 대한 나의 (다소 방대한) 생각입니다. 질보다는 양을 기준으로 투표 만하면 곧 다른 사람들을 물 밖으로 날려 버릴 것입니다. 듣기 주셔서 감사합니다 :-)


고정 너비 정수 유형의 경우 거의 모든 가능한 입력 쌍이 유형을 오버플로합니다. 대부분의 가능한 입력에 대해 유용한 결과를 제공하지 않는 함수를 표준화하는 용도는 무엇입니까?

함수를 유용하게 만들려면 큰 정수 유형이 필요하며 대부분의 큰 정수 라이브러리는 함수를 제공합니다.


편집 : 질문에 대한 주석에서 static_rtti는 "대부분의 입력으로 인해 오버플로가 발생합니까? exp 및 double pow도 마찬가지입니다. 불평하는 사람이 보이지 않습니다."라고 씁니다. 이것은 올바르지 않습니다.

exp(실제로 내 사례를 더 강하게 만들지 만) 요점을 벗어난 것이므로 제쳐두고 double pow(double x, double y). (x, y) 쌍의 어떤 부분에 대해이 함수가 유용한 작업을 수행합니까 (예 : 단순히 오버플로 또는 언더 플로가 아님)?

I'm actually going to focus only on a small portion of the input pairs for which pow makes sense, because that will be sufficient to prove my point: if x is positive and |y| <= 1, then pow does not overflow or underflow. This comprises nearly one-quarter of all floating-point pairs (exactly half of non-NaN floating-point numbers are positive, and just less than half of non-NaN floating-point numbers have magnitude less than 1). Obviously, there are a lot of other input pairs for which pow produces useful results, but we've ascertained that it's at least one-quarter of all inputs.

Now let's look at a fixed-width (i.e. non-bignum) integer power function. For what portion inputs does it not simply overflow? To maximize the number of meaningful input pairs, the base should be signed and the exponent unsigned. Suppose that the base and exponent are both n bits wide. We can easily get a bound on the portion of inputs that are meaningful:

  • If the exponent 0 or 1, then any base is meaningful.
  • If the exponent is 2 or greater, then no base larger than 2^(n/2) produces a meaningful result.

Thus, of the 2^(2n) input pairs, less than 2^(n+1) + 2^(3n/2) produce meaningful results. If we look at what is likely the most common usage, 32-bit integers, this means that something on the order of 1/1000th of one percent of input pairs do not simply overflow.


Because there's no way to represent all integer powers in an int anyways:

>>> print 2**-4
0.0625

That's actually an interesting question. One argument I haven't found in the discussion is the simple lack of obvious return values for the arguments. Let's count the ways the hypthetical int pow_int(int, int) function could fail.

  1. Overflow
  2. Result undefined pow_int(0,0)
  3. Result can't be represented pow_int(2,-1)

The function has at least 2 failure modes. Integers can't represent these values, the behaviour of the function in these cases would need to be defined by the standard - and programmers would need to be aware of how exactly the function handles these cases.

Overall leaving the function out seems like the only sensible option. The programmer can use the floating point version with all the error reporting available instead.


Short answer:

A specialisation of pow(x, n) to where n is a natural number is often useful for time performance. But the standard library's generic pow() still works pretty (surprisingly!) well for this purpose and it is absolutely critical to include as little as possible in the standard C library so it can be made as portable and as easy to implement as possible. On the other hand, that doesn't stop it at all from being in the C++ standard library or the STL, which I'm pretty sure nobody is planning on using in some kind of embedded platform.

Now, for the long answer.

pow(x, n) can be made much faster in many cases by specialising n to a natural number. I have had to use my own implementation of this function for almost every program I write (but I write a lot of mathematical programs in C). The specialised operation can be done in O(log(n)) time, but when n is small, a simpler linear version can be faster. Here are implementations of both:


    // Computes x^n, where n is a natural number.
    double pown(double x, unsigned n)
    {
        double y = 1;
        // n = 2*d + r. x^n = (x^2)^d * x^r.
        unsigned d = n >> 1;
        unsigned r = n & 1;
        double x_2_d = d == 0? 1 : pown(x*x, d);
        double x_r = r == 0? 1 : x;
        return x_2_d*x_r;
    }
    // The linear implementation.
    double pown_l(double x, unsigned n)
    {
        double y = 1;
        for (unsigned i = 0; i < n; i++)
            y *= x;
        return y;
    }

(I left x and the return value as doubles because the result of pow(double x, unsigned n) will fit in a double about as often as pow(double, double) will.)

(Yes, pown is recursive, but breaking the stack is absolutely impossible since the maximum stack size will roughly equal log_2(n) and n is an integer. If n is a 64-bit integer, that gives you a maximum stack size of about 64. No hardware has such extreme memory limitations, except for some dodgy PICs with hardware stacks that only go 3 to 8 function calls deep.)

As for performance, you'll be surprised by what a garden variety pow(double, double) is capable of. I tested a hundred million iterations on my 5-year-old IBM Thinkpad with x equal to the iteration number and n equal to 10. In this scenario, pown_l won. glibc pow() took 12.0 user seconds, pown took 7.4 user seconds, and pown_l took only 6.5 user seconds. So that's not too surprising. We were more or less expecting this.

Then, I let x be constant (I set it to 2.5), and I looped n from 0 to 19 a hundred million times. This time, quite unexpectedly, glibc pow won, and by a landslide! It took only 2.0 user seconds. My pown took 9.6 seconds, and pown_l took 12.2 seconds. What happened here? I did another test to find out.

I did the same thing as above only with x equal to a million. This time, pown won at 9.6s. pown_l took 12.2s and glibc pow took 16.3s. Now, it's clear! glibc pow performs better than the three when x is low, but worst when x is high. When x is high, pown_l performs best when n is low, and pown performs best when x is high.

So here are three different algorithms, each capable of performing better than the others under the right circumstances. So, ultimately, which to use most likely depends on how you're planning on using pow, but using the right version is worth it, and having all of the versions is nice. In fact, you could even automate the choice of algorithm with a function like this:

double pown_auto(double x, unsigned n, double x_expected, unsigned n_expected) {
    if (x_expected < x_threshold)
        return pow(x, n);
    if (n_expected < n_threshold)
        return pown_l(x, n);
    return pown(x, n);
}

As long as x_expected and n_expected are constants decided at compile time, along with possibly some other caveats, an optimising compiler worth its salt will automatically remove the entire pown_auto function call and replace it with the appropriate choice of the three algorithms. (Now, if you are actually going to attempt to use this, you'll probably have to toy with it a little, because I didn't exactly try compiling what I'd written above. ;))

On the other hand, glibc pow does work and glibc is big enough already. The C standard is supposed to be portable, including to various embedded devices (in fact embedded developers everywhere generally agree that glibc is already too big for them), and it can't be portable if for every simple math function it needs to include every alternative algorithm that might be of use. So, that's why it isn't in the C standard.

footnote: In the time performance testing, I gave my functions relatively generous optimisation flags (-s -O2) that are likely to be comparable to, if not worse than, what was likely used to compile glibc on my system (archlinux), so the results are probably fair. For a more rigorous test, I'd have to compile glibc myself and I reeeally don't feel like doing that. I used to use Gentoo, so I remember how long it takes, even when the task is automated. The results are conclusive (or rather inconclusive) enough for me. You're of course welcome to do this yourself.

Bonus round: A specialisation of pow(x, n) to all integers is instrumental if an exact integer output is required, which does happen. Consider allocating memory for an N-dimensional array with p^N elements. Getting p^N off even by one will result in a possibly randomly occurring segfault.


One reason for C++ to not have additional overloads is to be compatible with C.

C++98 has functions like double pow(double, int), but these have been removed in C++11 with the argument that C99 didn't include them.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3286.html#550

Getting a slightly more accurate result also means getting a slightly different result.


The World is constantly evolving and so are the programming languages. The fourth part of the C decimal TR¹ adds some more functions to <math.h>. Two families of these functions may be of interest for this question:

  • The pown functions, that takes a floating point number and an intmax_t exponent.
  • The powr functions, that takes two floating points numbers (x and y) and compute x to the power y with the formula exp(y*log(x)).

It seems that the standard guys eventually deemed these features useful enough to be integrated in the standard library. However, the rational is that these functions are recommended by the ISO/IEC/IEEE 60559:2011 standard for binary and decimal floating point numbers. I can't say for sure what "standard" was followed at the time of C89, but the future evolutions of <math.h> will probably be heavily influenced by the future evolutions of the ISO/IEC/IEEE 60559 standard.

Note that the fourth part of the decimal TR won't be included in C2x (the next major C revision), and will probably be included later as an optional feature. There hasn't been any intent I know of to include this part of the TR in a future C++ revision.


¹ You can find some work-in-progress documentation here.


Perhaps because the processor's ALU didn't implement such a function for integers, but there is such an FPU instruction (as Stephen points out, it's actually a pair). So it was actually faster to cast to double, call pow with doubles, then test for overflow and cast back, than to implement it using integer arithmetic.

(for one thing, logarithms reduce powers to multiplication, but logarithms of integers lose a lot of accuracy for most inputs)

Stephen is right that on modern processors this is no longer true, but the C standard when the math functions were selected (C++ just used the C functions) is now what, 20 years old?


A very simple reason:

5^-2 = 1/25

Everything in the STL library is based on the most accurate, robust stuff imaginable. Sure, the int would return to a zero (from 1/25) but this would be an inaccurate answer.

I agree, it's weird in some cases.

참고URL : https://stackoverflow.com/questions/2398442/why-isnt-int-powint-base-int-exponent-in-the-standard-c-libraries

반응형