Tại sao không có `int pow (int base, int exponent) 'trong các thư viện C ++ chuẩn?


116

Tôi cảm thấy như tôi không thể tìm thấy nó. Có lý do gì mà hàm C ++ powkhông triển khai hàm "power" cho bất kỳ thứ gì ngoại trừ floats và doubles không?

Tôi biết việc triển khai là không đáng kể, tôi chỉ cảm thấy như mình đang làm công việc đáng lẽ phải ở trong một thư viện tiêu chuẩn. Một hàm power mạnh mẽ (tức là xử lý tràn theo một số cách nhất quán, rõ ràng) không thú vị khi viết.


4
Đây là một câu hỏi hay, và tôi không nghĩ câu trả lời có nhiều ý nghĩa. Số mũ âm không hoạt động? Lấy int không dấu làm số mũ. Hầu hết các đầu vào khiến nó bị tràn? Exp và double pow cũng vậy, chả thấy ai phàn nàn cả. Vậy tại sao chức năng này không chuẩn?
static_rtti

2
@static_rtti: "Điều này cũng đúng với exp và double pow" là hoàn toàn sai. Tôi sẽ nói rõ hơn trong câu trả lời của tôi.
Stephen Canon

11
C ++ thư viện chuẩn có double pow(int base, int exponent)từ C ++ 11 (§26.8 [c.math] / 11 viên đạn điểm 2)
Cubbi

Bạn cần phải quyết định giữa 'việc thực hiện là tầm thường' và 'không thú vị khi viết'.
Marquis of Lorne

Câu trả lời:


66

Kể từ đó C++11, các trường hợp đặc biệt đã được thêm vào bộ chức năng quyền lực (và những người khác). C++11 [c.math] /11trạng thái, sau khi liệt kê tất cả các float/double/long doublequá tải (sự nhấn mạnh của tôi và được diễn giải):

Hơn nữa, sẽ có thêm các quá tải đủ để đảm bảo rằng, nếu bất kỳ đối số nào tương ứng với một doubletham số có kiểu doublehoặc kiểu số nguyên, thì tất cả các đối số tương ứng với doublecác tham số đều được truyền đến double.

Vì vậy, về cơ bản, các tham số số nguyên sẽ được nâng cấp lên gấp đôi để thực hiện thao tác.


Trước C++11(đó là khi câu hỏi của bạn được hỏi), không có quá tải số nguyên nào tồn tại.

Vì tôi không liên kết chặt chẽ với những người sáng tạo Ccũng như C++trong những ngày họ sáng tạo (mặc dù tôi đã khá già), cũng không phải là thành viên của các ủy ban ANSI / ISO đã tạo ra các tiêu chuẩn, đây là ý kiến ​​của tôi. Tôi muốn nghĩ đó là ý kiến sáng suốt nhưng vợ tôi sẽ nói với bạn (thường xuyên và không cần nhiều lời động viên), tôi đã sai trước đây :-)

Giả định, cho những gì nó có giá trị, theo sau.

Tôi nghi ngờ rằng lý do bản gốc trước ANSI Ckhông có tính năng này là vì nó hoàn toàn không cần thiết. Đầu tiên, đã có một cách hoàn toàn tốt để thực hiện lũy thừa số nguyên (với nhân đôi và sau đó chỉ cần chuyển đổi lại thành số nguyên, kiểm tra tràn số nguyên và tràn số nguyên trước khi chuyển đổi).

Thứ hai, một điều khác bạn phải nhớ là mục đích ban đầu của nó Clà như một ngôn ngữ lập trình hệ thống , và liệu dấu phẩy động có được mong muốn trong lĩnh vực đó hay không.

Vì một trong những trường hợp sử dụng ban đầu của nó là viết mã UNIX, dấu phẩy động sẽ trở nên vô dụng. BCPL, dựa trên C, cũng không sử dụng cho quyền hạn (nó không có dấu phẩy động, từ bộ nhớ).

Ngoài ra, một toán tử lũy thừa có thể sẽ là một toán tử nhị phân chứ không phải là một lệnh gọi thư viện. Bạn không thêm hai số nguyên x = add (y, z)nhưng với x = y + z- một phần của ngôn ngữ thích hợp chứ không phải thư viện.

Thứ ba, vì việc triển khai sức mạnh tổng hợp là tương đối nhỏ, nên gần như chắc chắn rằng các nhà phát triển ngôn ngữ sẽ sử dụng thời gian của họ để cung cấp nhiều thứ hữu ích hơn (xem phần nhận xét bên dưới về chi phí cơ hội).

Điều đó cũng phù hợp với bản gốc C++. Vì việc triển khai ban đầu chỉ là một trình biên dịch tạo ra Cmã, nó mang nhiều thuộc tính của C. Mục đích ban đầu của nó là C-with-class, không phải C-with-class-plus-a-little-bit-of-extra-math-things.

Về lý do tại sao nó chưa bao giờ được thêm vào các tiêu chuẩn trước đây C++11, bạn phải nhớ rằng các cơ quan thiết lập tiêu chuẩn có các hướng dẫn cụ thể để tuân theo. Ví dụ: ANSI Cđược giao nhiệm vụ đặc biệt để hệ thống hóa thực hành hiện có, không phải để tạo ra một ngôn ngữ mới. Nếu không, họ có thể phát điên và cho chúng tôi Ada :-)

Các lần lặp lại sau của tiêu chuẩn đó cũng có các hướng dẫn cụ thể và có thể được tìm thấy trong các tài liệu lý luận (lý do tại sao ủy ban đưa ra các quyết định nhất định, không phải lý do cho chính ngôn ngữ).

Ví dụ, C99tài liệu cơ sở cụ thể đưa ra hai trong số các C89nguyên tắc hướng dẫn giới hạn những gì có thể được thêm vào:

  • Giữ ngôn ngữ nhỏ và đơn giản.
  • Chỉ cung cấp một cách để thực hiện một thao tác.

Hướng dẫn (không nhất thiết là những hướng dẫn cụ thể đó) được đặt ra cho các nhóm làm việc riêng lẻ và do đó cũng giới hạn các C++ủy ban (và tất cả các nhóm ISO khác).

Ngoài ra, các cơ quan thiết lập tiêu chuẩn nhận thấy rằng có một chi phí cơ hội (một thuật ngữ kinh tế có nghĩa là những gì bạn phải bỏ qua cho một quyết định được đưa ra) cho mỗi quyết định họ đưa ra. Ví dụ: chi phí cơ hội để mua chiếc máy chơi game uber trị giá 10.000 đô la đó là mối quan hệ thân tình (hoặc có thể là tất cả các mối quan hệ) với nửa kia của bạn trong khoảng sáu tháng.

Eric Gunnerson giải thích rõ điều này với lời giải thích -100 điểm của anh ấy về lý do tại sao mọi thứ không phải lúc nào cũng được thêm vào các sản phẩm của Microsoft - về cơ bản, một tính năng bắt đầu 100 điểm trong lỗ hổng vì vậy nó phải thêm khá nhiều giá trị để được xem xét.

Nói cách khác, bạn có muốn có một toán tử điện tích hợp (thành thật mà nói, bất kỳ lập trình viên trung cấp nào cũng có thể hoàn thành trong mười phút) hay đa luồng được thêm vào tiêu chuẩn? Đối với bản thân tôi, tôi muốn có cái sau và không phải bận tâm về các triển khai khác nhau trong UNIX và Windows.

Tôi cũng muốn xem hàng nghìn hàng nghìn bộ sưu tập trong thư viện tiêu chuẩn (mã băm, btrees, cây đỏ-đen, từ điển, bản đồ tùy ý, v.v.) nhưng, theo cơ sở lý luận:

Tiêu chuẩn là một hiệp ước giữa người thực hiện và người lập trình.

Và số lượng người triển khai trên các cơ quan tiêu chuẩn nhiều hơn nhiều so với số lượng lập trình viên (hoặc ít nhất là những người lập trình không hiểu chi phí cơ hội). Nếu tất cả những thứ đó được thêm vào, tiêu chuẩn tiếp theo C++sẽ được C++215xvà có thể sẽ được các nhà phát triển trình biên dịch thực hiện đầy đủ trong ba trăm năm sau đó.

Dù sao, đó là những suy nghĩ (khá lớn) của tôi về vấn đề này. Nếu chỉ có phiếu bầu được trao dựa trên số lượng hơn là chất lượng, thì tôi sẽ sớm loại bỏ tất cả những người khác. Cảm ơn vì đã lắng nghe :-)


2
FWIW, tôi không nghĩ C ++ tuân theo "Chỉ cung cấp một cách để thực hiện một hoạt động" là một ràng buộc. Đúng như vậy, vì ví dụ to_stringvà lambdas đều là những tiện ích cho những việc bạn có thể làm. Tôi cho rằng người ta có thể giải thích "chỉ một cách để thực hiện một thao tác" rất lỏng lẻo để cho phép cả hai cách đó và đồng thời cho phép hầu hết mọi sự trùng lặp về chức năng mà người ta có thể tưởng tượng, bằng cách nói "aha! Không! Bởi vì sự tiện lợi tạo nên đó là một hoạt động khác biệt một cách tinh tế so với giải pháp thay thế tương đương chính xác nhưng dài hơi hơn! ". Điều này chắc chắn đúng với lambdas.
Steve Jessop,

@Steve, vâng, điều đó đã bị nói xấu về phía tôi. Nói chính xác hơn là có các hướng dẫn cho từng ủy ban thay vì tất cả các ủy ban đều tuân theo các hướng dẫn giống nhau. Câu trả lời điều chỉnh để clarifyl
paxdiablo

2
Chỉ một điểm (trong số rất ít): "bất kỳ con khỉ mã nào cũng có thể lao lên trong vòng mười phút". Chắc chắn, và nếu 100 con khỉ mã (thuật ngữ xúc phạm tốt đẹp, BTW) làm điều đó mỗi năm (có thể là một ước tính thấp), chúng ta sẽ lãng phí 1000 phút. Rất hiệu quả, bạn có nghĩ vậy không?
Jürgen A. Erhard

1
@ Jürgen, nó không có ý xúc phạm (vì tôi không thực sự gắn nhãn cho bất kỳ ai cụ thể), nó chỉ là một dấu hiệu powkhông thực sự đòi hỏi nhiều kỹ năng. Chắc chắn tôi thà có tiêu chuẩn cung cấp một cái gì đó mà sẽ đòi hỏi nhiều kỹ năng, và cho kết quả trong vài phút nhiều lãng phí nếu các nỗ lực đã được nhân đôi.
paxdiablo

2
@ eharo2, chỉ cần thay thế "một nửa coder tốt" trong văn bản hiện tại bằng "mã khỉ". Tôi cũng không nghĩ nó xúc phạm nhưng tôi nghĩ tốt nhất là nên thận trọng và thành thật mà nói, từ ngữ hiện tại có cùng ý tưởng.
paxdiablo

41

Đối với bất kỳ kiểu tích phân có chiều rộng cố định nào, gần như tất cả các cặp đầu vào có thể làm tràn kiểu. Việc sử dụng tiêu chuẩn hóa một hàm không mang lại kết quả hữu ích cho phần lớn các đầu vào có thể có của nó là gì?

Bạn cần có một kiểu số nguyên lớn để làm cho hàm trở nên hữu ích và hầu hết các thư viện số nguyên lớn đều cung cấp hàm.


Chỉnh sửa: Trong một bình luận về câu hỏi, static_rtti viết "Hầu hết các đầu vào đều khiến nó bị tràn? Exp và double pow cũng vậy, tôi không thấy ai phàn nàn." Điều này là không đúng.

Hãy bỏ qua một bên exp, bởi vì đó là vấn đề bên cạnh (mặc dù nó thực sự sẽ làm cho trường hợp của tôi mạnh mẽ hơn), và tập trung vào double pow(double x, double y). Đối với phần nào của cặp (x, y), hàm này làm điều gì đó hữu ích (tức là không chỉ đơn giản là tràn hoặc tràn)?

Tôi thực sự sẽ chỉ tập trung vào một phần nhỏ của các cặp đầu vào có powý nghĩa, bởi vì điều đó sẽ đủ để chứng minh quan điểm của tôi: nếu x dương và | y | <= 1, sau đó powkhông tràn hoặc tràn. Con số này bao gồm gần một phần tư tất cả các cặp dấu phẩy động (chính xác một nửa số dấu phẩy động không phải NaN là dương và chỉ chưa đầy một nửa số dấu phẩy động không NaN có độ lớn nhỏ hơn 1). Rõ ràng, có rất nhiều cặp đầu vào khác powtạo ra kết quả hữu ích, nhưng chúng tôi đã chắc chắn rằng đó là ít nhất một phần tư tổng số đầu vào.

Bây giờ chúng ta hãy xem xét một hàm lũy thừa số nguyên có độ rộng cố định (tức là không phải bignum). Đối với những đầu vào phần nào mà nó không chỉ bị tràn? Để tối đa hóa số cặp đầu vào có ý nghĩa, cơ số phải được ký và số mũ không có dấu. Giả sử rằng cơ số và số mũ đều nrộng bit. Chúng ta có thể dễ dàng bị ràng buộc về phần đầu vào có ý nghĩa:

  • Nếu số mũ 0 hoặc 1, thì bất kỳ cơ số nào cũng có nghĩa.
  • Nếu số mũ lớn hơn hoặc bằng 2, thì không có cơ số nào lớn hơn 2 ^ (n / 2) tạo ra kết quả có nghĩa.

Do đó, trong số 2 ^ (2n) cặp đầu vào, ít hơn 2 ^ (n + 1) + 2 ^ (3n / 2) tạo ra kết quả có ý nghĩa. Nếu chúng ta xem xét cách sử dụng phổ biến nhất, số nguyên 32 bit, điều này có nghĩa là thứ gì đó theo thứ tự 1/1000 của một phần trăm cặp đầu vào không chỉ đơn giản là tràn.


8
Dù sao tất cả những điều này là tranh luận. Chỉ vì một hàm không hợp lệ đối với một số hoặc nhiều đầu vào không làm cho nó kém hữu ích.
static_rtti

2
@static_rtti: pow(x,y)không tràn xuống 0 đối với bất kỳ x nếu | y | <= 1. Có một dải đầu vào rất hẹp (x lớn, y gần bằng -1) mà luồng dưới xảy ra, nhưng kết quả vẫn có ý nghĩa trong dải đó.
Stephen Canon

2
Sau khi suy nghĩ kỹ hơn, tôi đồng ý về quy trình dưới đây. Tuy nhiên, tôi vẫn nghĩ rằng điều này không liên quan đến câu hỏi.
static_rtti

7
@ybungalobill: Tại sao bạn lại chọn đó là lý do? Personnaly, tôi thích sự hữu ích cho một số lượng lớn các vấn đề và các lập trình viên, khả năng tạo các phiên bản được tối ưu hóa phần mềm độc hại nhanh hơn so với việc triển khai ngây thơ mà hầu hết các lập trình viên có thể sẽ viết, v.v. Tiêu chí của bạn có vẻ hoàn toàn độc đoán, và nói thẳng ra là khá vô nghĩa.
static_rtti

5
@StephenCanon: Về mặt sáng sủa, lập luận của bạn cho thấy rằng việc triển khai số nguyên rõ ràng-đúng-và-tối ưu powchỉ đơn giản là một bảng tra cứu nhỏ. :-)
R .. GitHub DỪNG TRỢ GIÚP ICE

11

Bởi vì không có cách nào để biểu diễn tất cả các lũy thừa số nguyên theo kiểu int:

>>> print 2**-4
0.0625

3
Đối với một kiểu số có kích thước hữu hạn, không có cách nào để biểu diễn tất cả các lũy thừa của kiểu đó trong kiểu đó do tràn. Nhưng quan điểm của bạn về quyền hạn tiêu cực có giá trị hơn.
Chris Lutz

1
Tôi thấy số mũ âm là thứ mà một triển khai tiêu chuẩn có thể xử lý, bằng cách lấy int không dấu làm số mũ hoặc trả về 0 khi số mũ âm được gọi là đầu vào và int là đầu ra dự kiến.
Dan O

3
hoặc có riêng biệt int pow(int base, unsigned int exponent)float pow(int base, int exponent)
Ponkadoodle

4
Họ chỉ có thể khai báo nó là hành vi không xác định để chuyển một số nguyên âm.
Johannes Schaub - litb

2
Trên tất cả các triển khai hiện đại, int pow(int base, unsigned char exponent)dù sao thì bất cứ thứ gì vượt quá cũng đều vô dụng. Cơ sở là 0 hoặc 1, và số mũ không quan trọng, nó là -1, trong trường hợp này chỉ bit cuối cùng của số mũ mới quan trọng hoặc base >1 || base< -1trong trường hợp đó là exponent<256hình phạt do tràn.
MSalters

9

Đó thực sự là một câu hỏi thú vị. Một đối số mà tôi chưa tìm thấy trong cuộc thảo luận là thiếu các giá trị trả về rõ ràng cho các đối số. Hãy đếm những cách mà int pow_int(int, int)hàm hypthetical có thể bị lỗi.

  1. Tràn ra
  2. Kết quả không xác định pow_int(0,0)
  3. Kết quả không thể được đại diện pow_int(2,-1)

Chức năng có ít nhất 2 chế độ lỗi. Các số nguyên không thể đại diện cho các giá trị này, hành vi của hàm trong những trường hợp này sẽ cần được xác định theo tiêu chuẩn - và các lập trình viên cần phải biết cách hàm xử lý chính xác các trường hợp này.

Nhìn chung, bỏ chức năng có vẻ như là lựa chọn hợp lý duy nhất. Lập trình viên có thể sử dụng phiên bản dấu phẩy động với tất cả các báo cáo lỗi có sẵn để thay thế.


Nhưng không phải hai trường hợp đầu tiên cũng áp dụng cho a powbetween float? Lấy hai phao lớn, nâng một cái lên bằng sức mạnh của cái kia và bạn có Overflow. Và pow(0.0, 0.0)sẽ gây ra vấn đề tương tự như điểm thứ 2 của bạn. Điểm thứ 3 của bạn là sự khác biệt thực sự duy nhất giữa việc triển khai hàm lũy thừa cho số nguyên so với số thực.
numbermaniac

7

Câu trả lời ngắn:

Sự chuyên biệt về pow(x, n)vị trí nlà một số tự nhiên thường hữu ích cho hiệu suất thời gian . Nhưng thư viện tiêu chuẩn chung của thư viện pow()vẫn hoạt động khá tốt ( đáng ngạc nhiên! ) Cho mục đích này và điều tối quan trọng là phải đưa càng ít càng tốt vào thư viện C tiêu chuẩn để nó có thể di động và dễ thực hiện nhất có thể. Mặt khác, điều đó hoàn toàn không ngăn cản nó nằm trong thư viện chuẩn C ++ hoặc STL, mà tôi khá chắc rằng không ai có ý định sử dụng trong một số loại nền tảng nhúng.

Bây giờ, cho câu trả lời dài.

pow(x, n)có thể được thực hiện nhanh hơn nhiều trong nhiều trường hợp bằng cách chuyên về nmột số tự nhiên. Tôi đã phải sử dụng việc triển khai hàm này của riêng mình cho hầu hết mọi chương trình tôi viết (nhưng tôi viết rất nhiều chương trình toán học bằng C). Hoạt động chuyên biệt có thể được thực hiện O(log(n))kịp thời, nhưng khi quy nmô nhỏ, phiên bản tuyến tính đơn giản hơn có thể nhanh hơn. Dưới đây là cách triển khai của cả hai:


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

(Tôi đã rời đi xvà giá trị trả về tăng gấp đôi bởi vì kết quả của pow(double x, unsigned n)sẽ khớp với mức nhân đôi thường xuyên pow(double, double).)

(Đúng, pownlà đệ quy, nhưng việc phá vỡ ngăn xếp là hoàn toàn không thể vì kích thước ngăn xếp tối đa sẽ gần bằng log_2(n)nlà một số nguyên. Nếu nlà số nguyên 64 bit, điều đó cung cấp cho bạn kích thước ngăn xếp tối đa khoảng 64. Không có phần cứng nào có kích thước cực đại như vậy giới hạn bộ nhớ, ngoại trừ một số PIC tinh ranh có ngăn xếp phần cứng chỉ có 3 đến 8 lệnh gọi hàm.)

Về hiệu suất, bạn sẽ ngạc nhiên bởi những gì một giống vườn có thể làm pow(double, double)được. Tôi đã thử nghiệm một trăm triệu lần lặp lại trên chiếc IBM Thinkpad 5 tuổi của mình với xsố lần lặp nbằng và bằng 10. Trong trường hợp này, tôi pown_lđã thắng. glibc pow()mất 12,0 giây của người dùng, pownmất 7,4 giây của người dùng và pown_lchỉ mất 6,5 giây của người dùng. Vì vậy, điều đó không quá ngạc nhiên. Chúng tôi đã ít nhiều mong đợi điều này.

Sau đó, tôi để xlà hằng số (tôi đặt nó thành 2,5), và tôi lặp lại ntừ 0 đến 19 một trăm triệu lần. Lần này, khá bất ngờ, glibc powđã giành chiến thắng, và một trận đấu long trời lở đất! Người dùng chỉ mất 2,0 giây. My pownmất 9,6 giây và pown_lmất 12,2 giây. Điều gì đã xảy ra ở đây? Tôi đã làm một bài kiểm tra khác để tìm hiểu.

Tôi đã làm điều tương tự như trên chỉ xbằng một triệu. Lần này, powngiành chiến thắng ở 9,6 giây. pown_lmất 12,2 giây và glibc pow mất 16,3 giây. Bây giờ, nó rõ ràng! glibc hoạt động powtốt hơn ba khi xở mức thấp, nhưng kém nhất khi xở mức cao. Khi xcao, hoạt động pown_ltốt nhất khi nthấp và hoạt động powntốt nhất khi xcao.

Vì vậy, đây là ba thuật toán khác nhau, mỗi thuật toán có khả năng hoạt động tốt hơn các thuật toán khác trong những trường hợp thích hợp. Vì vậy, cuối cùng, việc sử dụng cái nào nhiều khả năng phụ thuộc vào cách bạn định sử dụng pow, nhưng sử dụng đúng phiên bản là điều đáng giá và có tất cả các phiên bản đều tốt. Trên thực tế, bạn thậm chí có thể tự động hóa việc lựa chọn thuật toán với một chức năng như sau:

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

Miễn là x_expectedn_expectedlà các hằng số được quyết định tại thời điểm biên dịch, cùng với một số lưu ý khác, trình biên dịch tối ưu hóa giá trị của nó sẽ tự động loại bỏ toàn bộ pown_autolệnh gọi hàm và thay thế nó bằng lựa chọn thích hợp trong ba thuật toán. (Bây giờ, nếu bạn thực sự định sử dụng cái này, có lẽ bạn sẽ phải đùa giỡn với nó một chút, vì tôi đã không chính xác thử biên dịch những gì tôi đã viết ở trên.;))

Mặt khác, glibc pow hoạt động và glibc đã đủ lớn. Tiêu chuẩn C được cho là có thể di động, bao gồm các thiết bị nhúng khác nhau (trên thực tế, các nhà phát triển nhúng ở khắp mọi nơi thường đồng ý rằng glibc đã quá lớn đối với họ) và nó không thể di động nếu đối với mọi hàm toán học đơn giản, nó cần bao gồm mọi thuật toán thay thế có thể được sử dụng. Vì vậy, đó là lý do tại sao nó không nằm trong tiêu chuẩn C.

chú thích: Trong kiểm tra hiệu suất thời gian, tôi đã đưa ra các cờ tối ưu hóa tương đối hào phóng cho các chức năng của mình ( -s -O2) có khả năng tương đương với, nếu không tệ hơn, những gì có khả năng được sử dụng để biên dịch glibc trên hệ thống của tôi (Archlinux), vì vậy kết quả có thể là hội chợ. Đối với một bài kiểm tra nghiêm ngặt hơn, tôi sẽ phải biên dịch glibc bản thân mình và tôi reeeally không cảm thấy như làm điều đó. Tôi đã từng sử dụng Gentoo, vì vậy tôi nhớ nó mất bao lâu, ngay cả khi tác vụ được tự động hóa . Kết quả là đủ cho tôi (hay đúng hơn là không thể kết luận). Tất nhiên, bạn có thể tự làm việc này.

Vòng thưởng: Chuyên môn hóa pow(x, n)tất cả các số nguyên là công cụ nếu yêu cầu đầu ra số nguyên chính xác, điều này sẽ xảy ra. Xem xét cấp phát bộ nhớ cho một mảng N chiều có p ^ N phần tử. Việc tắt p ^ N dù chỉ một sẽ dẫn đến một segfault có thể xảy ra ngẫu nhiên.


Tôi đoán nếu bạn loại bỏ được đệ quy, bạn sẽ tiết kiệm được thời gian cần thiết cho việc phân bổ ngăn xếp. Và vâng, chúng tôi đã gặp phải tình huống pow làm chậm mọi thứ và chúng tôi phải triển khai pow của riêng mình.
Sambatyon

"Không ai có trí nhớ cực hạn như vậy" là sai. PIC thường có một ngăn xếp cuộc gọi giới hạn cho tối đa 3 (ví dụ là PIC10F200) đến 8 (ví dụ là 16F722A) các cuộc gọi (PIC sử dụng ngăn xếp phần cứng cho các lệnh gọi hàm).
12431234123412341234123

ồ, người đàn ông thật tàn bạo lol. OK, vì vậy nó sẽ không hoạt động trên các PIC đó.
enigmaticPhysicist

Đối với cơ sở số nguyên cũng như lũy thừa, giống như câu hỏi đang hỏi, các trình biên dịch (gcc và clang) sẽ dễ dàng tạo ra một vòng lặp không rẽ nhánh từ việc triển khai lặp lại (thay vì đệ quy). Điều này tránh các sai lệch nhánh từ mỗi bit của n. godbolt.org/z/L9Kb98 . gcc và clang không thể tối ưu hóa định nghĩa đệ quy của bạn thành một vòng lặp đơn giản và thực sự phân nhánh trên mỗi bit của n. (Đối với pown_iter(double,unsigned)họ vẫn chi nhánh, nhưng một cành SSE2 hoặc thực hiện SSE4.1 nên có thể trong x86 asm hoặc với intrinsics C Nhưng thậm chí là tốt hơn so với đệ quy.)
Peter Cordes

Khỉ thật, bây giờ tôi phải làm lại tất cả các điểm chuẩn với một phiên bản dựa trên vòng lặp chỉ để chắc chắn. Tôi sẽ nghĩ về nó.
enigmaticPhysicist

6

Một lý do để C ++ không có thêm quá tải là để tương thích với C.

C ++ 98 có các chức năng như double pow(double, int), nhưng chúng đã bị loại bỏ trong C ++ 11 với đối số rằng C99 không bao gồm chúng.

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

Nhận được một kết quả chính xác hơn một chút cũng có nghĩa là nhận được một kết quả hơi khác .


3

Thế giới không ngừng phát triển và các ngôn ngữ lập trình cũng vậy. Phần thứ tư của số thập phân C TR ¹ thêm một số hàm khác vào <math.h>. Hai họ của các hàm này có thể được quan tâm cho câu hỏi này:

  • Các pownhàm, nhận một số dấu phẩy động và một intmax_tsố mũ.
  • Các powrhàm, nhận hai số dấu phẩy động ( xy) và tính lũy xthừa yvới công thức exp(y*log(x)).

Có vẻ như những kẻ tiêu chuẩn cuối cùng đã coi những tính năng này đủ hữu ích để được tích hợp trong thư viện tiêu chuẩn. Tuy nhiên, điều hợp lý là các chức năng này được khuyến nghị bởi tiêu chuẩn ISO / IEC / IEEE 60559: 2011 cho các số dấu phẩy động nhị phân và thập phân. Tôi không thể nói chắc "tiêu chuẩn" nào đã được tuân theo vào thời điểm C89, nhưng những diễn biến trong tương lai của <math.h>có thể sẽ bị ảnh hưởng nặng nề bởi những phát triển trong tương lai của tiêu chuẩn ISO / IEC / IEEE 60559 .

Lưu ý rằng phần thứ tư của TR thập phân sẽ không được đưa vào C2x (bản sửa đổi C lớn tiếp theo) và có thể sẽ được đưa vào sau này như một tính năng tùy chọn. Tôi không biết có ý định đưa phần TR này vào bản sửa đổi C ++ trong tương lai.


¹ Bạn có thể tìm thấy một số tài liệu đang tiến hành tại đây .


Có bất kỳ triển khai hợp lý nào trong đó sử dụng pownvới số mũ lớn hơn LONG_MAXbao giờ nên mang lại giá trị khác với sử dụng không LONG_MAX, hoặc trong đó giá trị nhỏ hơn LONG_MINsẽ mang lại giá trị khác với LONG_MIN? Tôi tự hỏi lợi ích thu được từ việc sử dụng intmax_tcho một số mũ là gì?
supercat

@supercat Không có ý kiến, xin lỗi.
Morwenn

Có thể đáng giá khi đề cập rằng, nhìn vào Tiêu chuẩn, nó dường như cũng xác định một chức năng "crpown" tùy chọn, nếu được định nghĩa, sẽ là một phiên bản làm tròn chính xác của "pown"; Tiêu chuẩn khác không quy định mức độ chính xác cần thiết. Thực hiện một "pown" nhanh và chính xác vừa phải là điều dễ dàng, nhưng đảm bảo làm tròn chính xác trong mọi trường hợp sẽ tốn kém hơn nhiều.
supercat

2

Có lẽ vì ALU của bộ xử lý không thực hiện một chức năng như vậy cho số nguyên, nhưng có một lệnh FPU như vậy (như Stephen đã chỉ ra, nó thực sự là một cặp). Vì vậy, thực sự nhanh hơn để ép kiểu thành nhân đôi, gọi pow với nhân đôi, sau đó kiểm tra tràn và truyền ngược lại, hơn là triển khai nó bằng cách sử dụng số học số nguyên.

(đối với một điều, logarit làm giảm lũy thừa cho phép nhân, nhưng logarit của số nguyên làm mất rất nhiều độ chính xác đối với hầu hết các đầu vào)

Stephen nói đúng rằng trên các bộ xử lý hiện đại, điều này không còn đúng nữa, nhưng tiêu chuẩn C khi các hàm toán học được chọn (C ++ chỉ sử dụng các hàm C) bây giờ là bao nhiêu, 20 tuổi?


5
Tôi không biết bất kỳ kiến ​​trúc hiện tại nào có hướng dẫn FPU cho pow. x86 có một y log2 xlệnh ( fyl2x) có thể được sử dụng như phần đầu tiên của một powhàm, nhưng một powhàm được viết theo cách đó cần hàng trăm chu kỳ để thực thi trên phần cứng hiện tại; một quy trình lũy thừa số nguyên được viết tốt nhanh hơn nhiều lần.
Stephen Canon

Tôi không biết rằng "hàng trăm" là chính xác, dường như khoảng 150 chu kỳ đối với fyl2x sau đó là f2xm1 trên hầu hết các CPU hiện đại và điều đó được kết nối với các hướng dẫn khác. Nhưng bạn nói đúng rằng việc triển khai số nguyên được điều chỉnh tốt sẽ nhanh hơn nhiều (những ngày này) vì IMUL đã được tăng tốc hơn rất nhiều so với các hướng dẫn dấu phẩy động. Mặc dù vậy, khi tiêu chuẩn C được viết ra, IMUL khá đắt và việc sử dụng nó trong một vòng lặp có lẽ mất nhiều thời gian hơn so với sử dụng FPU.
Ben Voigt

2
Đã thay đổi phiếu bầu của tôi sau khi sửa chữa; Tuy nhiên, hãy nhớ (a) rằng tiêu chuẩn C đã trải qua một lần sửa đổi lớn (bao gồm cả việc mở rộng lớn thư viện toán học) vào năm 1999 và (b) rằng tiêu chuẩn C không được viết cho bất kỳ kiến ​​trúc bộ xử lý cụ thể nào - sự hiện diện hoặc không có hướng dẫn FPU trên x86 về cơ bản không liên quan gì đến chức năng mà ủy ban C lựa chọn để chuẩn hóa.
Stephen Canon

Nó không bị ràng buộc với bất kỳ kiến ​​trúc nào, đúng, nhưng chi phí tương đối của một phép nội suy bảng tra cứu (thường được sử dụng để triển khai dấu phẩy động) so với phép nhân số nguyên đã thay đổi khá nhiều đối với tất cả các kiến ​​trúc mà tôi đoán.
Ben Voigt

1

Đây là một triển khai O (log (n)) thực sự đơn giản của pow () hoạt động cho bất kỳ kiểu số nào, bao gồm cả số nguyên :

template<typename T>
static constexpr inline T pown(T x, unsigned p) {
    T result = 1;

    while (p) {
        if (p & 0x1) {
            result *= x;
        }
        x *= x;
        p >>= 1;
    }

    return result;
}

Nó tốt hơn việc triển khai O (log (n)) của enigmaticPhysicist vì nó không sử dụng đệ quy.

Nó cũng gần như luôn nhanh hơn việc triển khai tuyến tính của anh ấy (miễn là p> ~ 3) vì:

  • nó không yêu cầu bất kỳ bộ nhớ bổ sung nào
  • nó chỉ thực hiện nhiều hơn ~ 1,5 lần hoạt động trên mỗi vòng lặp
  • nó chỉ cập nhật thêm ~ 1,25 lần bộ nhớ mỗi vòng lặp

-2

Như một vấn đề của thực tế, nó có.

Vì C ++ 11 có một triển khai theo mẫu của pow(int, int)--- và các trường hợp tổng quát hơn nữa, hãy xem (7) trong http://en.cppreference.com/w/cpp/numeric/math/pow


CHỈNH SỬA: những người theo chủ nghĩa thuần túy có thể tranh luận điều này là không chính xác, vì thực tế đã sử dụng cách đánh máy "quảng cáo". Bằng cách này hay cách khác, người ta nhận được một intkết quả chính xác , hoặc một lỗi, trên intcác tham số.


2
điều này là không đúng. Quá tải (7) pow ( Arithmetic1 base, Arithmetic2 exp )sẽ được truyền đến doublehoặc long doublenếu bạn đã đọc mô tả: "7) Một tập hợp các quá tải hoặc một mẫu hàm cho tất cả các tổ hợp các đối số của kiểu số học không thuộc phạm vi 1-3). Nếu bất kỳ đối số nào có kiểu tích phân, nó được ép kiểu thành đôi. Nếu bất kỳ đối số nào là dài kép, thì kiểu trả về được quảng bá cũng là kép dài, nếu không thì kiểu trả về luôn là kép. "
phuclv

điều gì không chính xác ở đây? Tôi chỉ đơn thuần nói rằng ngày nay (kể từ C ++ 11), một pow ( , ) được tạo khuôn mẫu đã có trong thư viện tiêu chuẩn, điều này không xảy ra vào năm 2010.
Dima Pasechnik

5
Không, không. Templeates thúc đẩy các loại này thành đôi hoặc dài gấp đôi. Vì vậy, nó hoạt động trên gấp đôi bên dưới.
Trismegistos

1
@Trismegistos Nó vẫn cho phép tham số int. Nếu mẫu này không có ở đó, việc chuyển các tham số int sẽ khiến nó diễn giải các bit trong int dưới dạng một float, gây ra các kết quả không mong muốn tùy ý. Điều tương tự cũng xảy ra với các giá trị đầu vào hỗn hợp. ví dụ pow(1.5f, 3)= 1072693280nhưng pow(1.5f, float(3))=3.375
Mark Jeronimus

2
OP đã yêu cầu int pow(int, int), nhưng C ++ 11 chỉ cung cấp double pow(int, int). Xem lời giải thích của @phuclv.
xuhdev

-4

Một lý do rất đơn giản:

5^-2 = 1/25

Mọi thứ trong thư viện STL đều dựa trên những thứ mạnh mẽ và chính xác nhất có thể tưởng tượng được. Chắc chắn, int sẽ trở về 0 (từ 1/25) nhưng đây sẽ là một câu trả lời không chính xác.

Tôi đồng ý, nó kỳ lạ trong một số trường hợp.


3
Yêu cầu đối số thứ hai không dấu rõ ràng là cần thiết. Có nhiều ứng dụng chỉ yêu cầu lũy thừa số nguyên không âm.
enigmaticPhysicist,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.