2009-06-30 66 views
11

Có thể tính toán pow (10, x) tại thời gian biên dịch không?Tôi có thể tính toán pow (10, x) tại thời gian biên dịch trong c?

Tôi có bộ xử lý không có hỗ trợ điểm động và phân chia số nguyên chậm. Tôi đang cố gắng thực hiện càng nhiều phép tính càng tốt vào thời gian biên dịch. Tôi có thể tăng tốc đáng kể một chức năng cụ thể nếu tôi vượt qua cả hai đối số xC/pow(10,x) làm đối số (x và C luôn là số nguyên không đổi, nhưng chúng là các hằng số khác nhau cho mỗi cuộc gọi). Tôi tự hỏi nếu tôi có thể làm cho các chức năng này gọi ít lỗi dễ bị ảnh hưởng bằng cách giới thiệu một macro mà không tự động 1/pow(10,x), thay vì buộc các lập trình để tính toán nó?

Có mẹo vặt xử lý trước không? Tôi có thể buộc trình biên dịch tối ưu hóa cuộc gọi thư viện không?

+0

Tôi tin rằng tôi đã nhìn thấy bằng chứng rằng bộ xử lý tiền xử lý hoàn tất (tôi nghĩ rằng nó là một máy băng được thực hiện trong một cuộc thi C bị xáo trộn trong bộ tiền xử lý.) Vì vậy, có một cách. Tuy nhiên, không biết theo cách đó. –

+0

Preprocessor #defines không thể đệ quy, vì chúng chỉ là thay thế văn bản. Vì vậy, giống như Greg, đây là một nơi KHÔNG dành thời gian của bạn tìm kiếm. :) –

+0

@Greg D: Tuy nhiên, bắt đầu với một máy Turing và thực hiện một số mũ của 10 chức năng tấn công tôi như tham vọng. –

Trả lời

8

Bạn có thể sử dụng ký pháp khoa học cho các giá trị dấu phẩy động là một phần của ngôn ngữ C.Có vẻ như rằng:

e = 1.602E-19 // == 1.602 * pow(10, -19) 

Số trước E (các E có lẽ vốn hay nhỏ 1.602e-19) là phần phân số nơi như (đã ký) chữ số thứ tự sau E là phần số mũ. Theo mặc định, số này thuộc loại double, nhưng bạn có thể đính kèm hậu tố dấu chấm động (f, F, l hoặc L) nếu bạn cần float hoặc long double.

tôi sẽ không khuyên bạn nên để đóng gói ngữ nghĩa này thành một vĩ mô:

  1. Nó sẽ không làm việc cho các biến, các giá trị dấu chấm động vv
  2. Các ký hiệu khoa học là dễ đọc hơn.
+5

Mặc dù bạn không khuyên dùng nó, đây chính xác là những gì tôi cần: '#define P10 (X) (1eX)', kết hợp với '#define fixedpt (giá trị, chữ số) ((giá trị) * (1 << 15)/P10 (chữ số)) 'cho tôi kết quả tôi muốn, không phụ thuộc vào các cài đặt tối ưu hóa. – AShelly

+0

Trình biên dịch nào hoạt động với '#define P10 (X) (1eX)'? Trong trình biên dịch Arduino, tôi cần '#define P10 (X) (1e ## X)'. – BigBobby

20

Có rất ít giá trị có thể trước khi bạn tràn int (hoặc thậm chí lâu dài). Để làm sáng tỏ, hãy biến nó thành bàn!

chỉnh sửa: Nếu bạn đang sử dụng phao (có vẻ như bạn), thì không thể gọi hàm pow() tại thời gian biên dịch mà không thực sự viết mã chạy trong quá trình tạo và xuất kết quả vào một tệp (chẳng hạn như tệp tiêu đề), sau đó được biên dịch.

+1

Lấy cảm hứng! Đối với quyền hạn của một cơ sở đã biết. Đó không phải là số thập phân. :) –

+0

C và x có liên quan theo cách chúng ta sẽ không tràn: được gán giá trị v, x để 0,1 <= v/pow (10, x) <1 và C được đặt thành 32768 * v. – AShelly

+0

điều bạn muốn nói là 'total = (total << 1) + (total << 3)' Và hầu hết trình biên dịch có thể tự động thực hiện khi bạn sử dụng 'total * = 10' – leiz

1

Thật không may, bạn không thể sử dụng bộ tiền xử lý để tính toán trước các cuộc gọi thư viện. Nếu x là tích phân, bạn có thể viết hàm của riêng mình, nhưng nếu đó là kiểu dấu phẩy động thì tôi không thấy cách nào tốt để làm điều này.

19

GCC sẽ thực hiện việc này ở mức tối ưu hóa đủ cao (-O1 có làm cho tôi). Ví dụ:

#include <math.h> 

int test() { 
     double x = pow(10, 4); 
     return (int)x; 
} 

Biên dịch tại -O1 -m32 tới:

 .file "test.c" 
     .text 
.globl test 
     .type test, @function 
test: 
     pushl %ebp 
     movl %esp, %ebp 
     movl $10000, %eax 
     popl %ebp 
     ret 
     .size test, .-test 
     .ident "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3" 
     .section  .note.GNU-stack,"",@progbits 

này hoạt động mà không có sự dàn diễn viên cũng - tất nhiên, bạn nhận được một hướng dẫn tải dấu chấm động trong đó, như sự Linux ABI chuyển các giá trị trả về dấu chấm động trong thanh ghi FPU.

+3

Tốt. Tôi tự hỏi làm thế nào họ kiểm tra nếu pow là một chức năng tinh khiết hay không trong một thời gian biên dịch hợp lý. Có thể họ có một danh sách các hàm đã biết như vậy? – akappa

+0

một số chức năng toán học có khả năng được biên dịch tích hợp sẵn; Ngoài ra, GCC có thuộc tính 'thuần khiết 'không chuẩn cho các hàm – Christoph

+0

' thuần 'không đủ để tối ưu hóa đơn vị biên dịch chéo của loại này; và GCC sẽ thực hiện xếp liên tục sau khi nội tuyến nếu chúng nằm trong cùng một đơn vị biên dịch. tinh khiết chủ yếu chỉ là một gợi ý cho trình biên dịch mà nó không cần phải làm mất hiệu lực dữ liệu trong thanh ghi của nó. – bdonlan

10

Bạn có thể làm điều đó với Boost.Preprocessor:

http://www.boost.org/doc/libs/1_39_0/libs/preprocessor/doc/index.html

Code:

phiên bản
#include <boost/preprocessor/repeat.hpp> 

#define _TIMES_10(z, n, data) * 10 
#define POW_10(n) (1 BOOST_PP_REPEAT(n, _TIMES_10, _)) 

int test[4] = {POW_10(0), POW_10(1), POW_10(2), POW_10(3)}; 
+1

tiếc là tôi chỉ sử dụng c. – AShelly

+1

Tuy nhiên OP yêu cầu giải pháp C và Boost là thư viện C++ – DaveR

+5

Nhưng bạn có thể sử dụng thư viện C (thư viện Boost.Preprocessor), chỉ là tiền xử lý, tôi đã kiểm tra;) Chỉ định cấu hình thư mục bao gồm của bạn và bao gồm nó! –

3

gần đây của GCC (khoảng 4.3) thêm khả năng sử dụng GMP và MPFR để làm một số tối ưu hóa thời gian biên dịch bằng cách đánh giá các hàm phức tạp hơn là hằng số. Cách tiếp cận đó để lại mã của bạn đơn giản và di động, và tin tưởng trình biên dịch để thực hiện việc nâng hạng nặng.

Tất nhiên, có những giới hạn đối với những gì nó có thể làm. Here's a link to the description in the changelog, bao gồm danh sách các chức năng được hỗ trợ bởi điều này. 'pow' là một trong số họ.

0

phát lại của bdonlan là tại chỗ nhưng lưu ý rằng bạn có thể thực hiện gần như mọi tối ưu hóa bạn đã chọn trên hộp biên dịch miễn là bạn sẵn sàng phân tích cú pháp và phân tích mã trong bộ tiền xử lý tùy chỉnh của riêng bạn. Nó là một nhiệm vụ tầm thường trong hầu hết các phiên bản của unix để ghi đè lên các quy tắc ngầm gọi trình biên dịch để gọi một bước tùy chỉnh của riêng bạn trước khi nó truy cập trình biên dịch.

4

Thực ra, bạn có M4 là cách xử lý trước mạnh hơn GCC. Điểm khác biệt chính giữa hai loại này là GCC không đệ quy trong khi M4 là. Nó làm cho những thứ có thể giống như làm số học tại thời gian biên dịch (và nhiều hơn nữa!). Mẫu mã dưới đây là những gì bạn muốn làm, phải không? Tôi đã làm cho nó cồng kềnh trong một nguồn một tệp; nhưng tôi thường đặt các định nghĩa macro của M4 trong các tệp riêng biệt và điều chỉnh các quy tắc Makefile của tôi. Bằng cách này, mã của bạn được lưu giữ từ các định nghĩa M4 xâm nhập xấu vào mã nguồn C mà tôi đã thực hiện ở đây.

$ cat foo.c 
define(M4_POW_AUX, `ifelse($2, 1, $1, `eval($1 * M4_POW_AUX($1, decr($2)))')')dnl 
define(M4_POW, `ifelse($2, 0, 1, `M4_POW_AUX($1, $2)')')dnl 

#include <stdio.h> 

int      main(void) 
{ 
    printf("2^0 = %d\n", M4_POW(2, 0)); 
    printf("2^1 = %d\n", M4_POW(2, 1)); 
    printf("2^4 = %d\n", M4_POW(2, 4)); 

    return 0; 
} 

Dòng lệnh để biên dịch mẫu mã này sử dụng khả năng của GCC và M4 để đọc từ đầu vào chuẩn.

$ cat foo.c | m4 - | gcc -x c -o m4_pow - 
$ ./m4_pow 
2^0 = 1 
2^1 = 2 
2^4 = 16 

Hy vọng trợ giúp này!

5

Thực ra, bằng cách khai thác bộ tiền xử lý C, bạn có thể lấy nó để tính C pow(10, x) cho bất kỳ số thực C và số x nào. Quan sát rằng, như @quinmars đã lưu ý, C cho phép bạn sử dụng cú pháp khoa học để biểu thị các hằng số số:

#define myexp 1.602E-19 // == 1.602 * pow(10, -19) 

để sử dụng cho các hằng số. Với điều này trong tâm trí, và một chút khéo léo, chúng ta có thể xây dựng một macro tiền xử lý mà sẽ đưa Cx và kết hợp chúng thành một thẻ lũy thừa:

#define EXP2(a, b) a ## b 
#define EXP(a, b) EXP2(a ## e,b) 
#define CONSTPOW(C,x) EXP(C, x) 

này bây giờ có thể được sử dụng như một giá trị hằng số:

const int myint = CONSTPOW(3, 4); // == 30000 
const double myfloat = CONSTPOW(M_PI, -2); // == 0.03141592653 
1

only 23 different powers of 10 có thể chính xác biểu diễn trong độ chính xác gấp đôi, do đó bạn có thể chỉ cần sử dụng một bảng tra cứu

double POW10[] = {1., 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 
1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; 

Nếu bạn không cần giá trị chính xác nhưng cần sử dụng quyền hạn lớn hơn 10 thì bạn có thể chỉ cần viết các phiên bản quyền hạn 10 của riêng mình sử dụng bảng tra cứu ở trên để nhanh chóng nhận được kết quả mà không cần nhân lại 10 lần nữa Và một lần nữa.

double pow10(int x) 
{ 
    if (x > 22) 
     return POW10[22]*pow10(x-22); 
    else if (x >= 0) 
     return POW10[x]; 
    else 
     return 1/pow10(-x); 
} 

Nếu không cần số mũ âm thì nhánh cuối cùng có thể bị xóa.

Bạn cũng có thể giảm kích thước bảng tra cứu thêm nếu bộ nhớ là hạn chế. Ví dụ bằng cách lưu trữ ngay cả quyền hạn của 10 và nhân với 10 khi số mũ là lẻ, kích thước bảng bây giờ chỉ là một nửa.

Các vấn đề liên quan