ACM數論之旅11---淺談指數與對數(長篇)(今天休息,不學太難的數論> 3<)


 

 

c/c++語言中,關於指數,對數的函數我也就知道那么多

exp(),pow(),sqrt(),log(),log10(),

 

 

 

 

 

 

 

exp(x)就是計算e的x次方,sqrt(x)就是對x開根號

pow()函數可是十分強大的( ̄ε ̄)

pow(a, b)可以算a的b次方,但是b不限於整數,小數也可以

所以pow(x, 0.5)相當於sqrt(x)

  pow(M_E, x)相當於exp(x)  (M_E就是e)

 

math.h1

 

 

這是我在math.h發現的可以直接用

 1 #ifdef __STRICT_ANSI__
 2 #undef __STRICT_ANSI__
 3 #endif
 4 #include<cstdio>
 5 #include<cmath>
 6 int main(){
 7     printf("%.20f\n", M_E);
 8     printf("%.20f\n", M_PI);
 9 }
10 /*
11 在math頭文件的前面,最好加上最上面那三行
12 因為編譯器不同,系統不同,都有可能導致用不了M_E
13 比如codeforces,不加前三行,無法識別M_E
14 */ 

 

 

但是函數總有他存在的意義,要不然大家都用pow(x, 0.5),沒人用sqrt了

所以我認為,后者比前者要快,或者可能更精確(o゚ω゚o)

 

 

 

 

 

比如sqrt,我來給大家講一個鬼故事(ΘˍΘ=),有一個比sqrt還要快計算出根號的函數

一個關於被稱作“魔數”0x5F3759DF的故事

以下摘自http://www.guokr.com/post/90718/

(不看可以跳過)

 

 

 

 

 

http://www.douban.com/note/93460299/

Quake-III Arena (雷神之錘3)是90年代的經典游戲之一。該系列的游戲不但畫面和內容不錯,而且即使計算機配置低,也能極其流暢地運行。這要歸功於它3D引擎的開發者約翰-卡馬克(John Carmack)。事實上早在90年代初DOS時代,只要能在PC上搞個小動畫都能讓人驚嘆一番的時候,John Carmack就推出了石破天驚的Castle Wolfstein, 然后再接再勵,doom, doomII, Quake...每次都把3-D技術推到極致。他的3D引擎代碼資極度高效,幾乎是在壓榨PC機的每條運算指令。當初MS的Direct3D也得聽取他的意見,修改了不少API。

最近,QUAKE的開發商ID SOFTWARE 遵守GPL協議,公開了QUAKE-III的原代碼,讓世人有幸目睹Carmack傳奇的3D引擎的原碼。
這是QUAKE-III原代碼的下載地址:
http://www.fileshack.com/file.x?fid=7547

(下面是官方的下載網址,搜索 “quake3-1.32b-source.zip” 可以找到一大堆中文網頁的
ftp://ftp.idsoftware.com/idstuff/source/quake3-1.32b-source.zip)

我們知道,越底層的函數,調用越頻繁。3D引擎歸根到底還是數學運算。那么找到最底層的數學運算函數(在game/code/q_math.c), 必然是精心編寫的。里面有很多有趣的函數,很多都令人驚奇,估計我們幾年時間都學不完。

在game/code/q_math.c里發現了這樣一段代碼。它的作用是將一個數開平方並取倒,經測試這段代碼比(float)(1.0/sqrt(x))快4倍:
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;

x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

#ifndef Q3_VM
#ifdef __linux__
assert( !isnan(y) ); // bk010122 - FPE?
#endif
#endif
return y;
}

函數返回1/sqrt(x),這個函數在圖像處理中比sqrt(x)更有用。
注意到這個函數只用了一次疊代!(其實就是根本沒用疊代,直接運算)。編譯,實驗,這個函數不僅工作的很好,而且比標准的sqrt()函數快4倍!要知道,編譯器自帶的函數,可是經過嚴格仔細的匯編優化的啊!

這個簡潔的函數,最核心,也是最讓人費解的,就是標注了“what the fuck?”的一句
i = 0x5f3759df - ( i >> 1 );

再加上y = y * ( threehalfs - ( x2 * y * y ) );
兩句話就完成了開方運算!而且注意到,核心那句是定點移位運算,速度極快!特別在很多沒有乘法指令的RISC結構CPU上,這樣做是極其高效的。

算法的原理其實不復雜,就是牛頓迭代法,用x-f(x)/f'(x)來不斷的逼近f(x)=a的根。

簡單來說比如求平方根,f(x)=x^2=a ,f'(x)= 2*x,f(x)/f'(x)=x/2,把f(x)代入

x-f(x)/f'(x)后有(x+a/x)/2,現在我們選a=5,選一個猜測值比如2,
那么我們可以這么算
5/2 = 2.5; (2.5+2)/2 = 2.25; 5/2.25 = xxx; (2.25+xxx)/2 = xxxx ...
這樣反復迭代下去,結果必定收斂於sqrt(5),沒錯,一般的求平方根都是這么算的
但是卡馬克(quake3作者)真正牛B的地方是他選擇了一個神秘的常數0x5f3759df 來計算那個猜測值
就是我們加注釋的那一行,那一行算出的值非常接近1/sqrt(n),這樣我們只需要2次牛 頓迭代就可以達到我們所需要的精度.
好吧 如果這個還不算NB,接着看:


普渡大學的數學家Chris Lomont看了以后覺得有趣,決定要研究一下卡馬克弄出來的
這個猜測值有什么奧秘。Lomont也是個牛人,在精心研究之后從理論上也推導出一個
最佳猜測值,和卡馬克的數字非常接近, 0x5f37642f。卡馬克真牛,他是外星人嗎?

傳奇並沒有在這里結束。Lomont計算出結果以后非常滿意,於是拿自己計算出的起始
值和卡馬克的神秘數字做比賽,看看誰的數字能夠更快更精確的求得平方根。結果是
卡馬克贏了... 誰也不知道卡馬克是怎么找到這個數字的。

最后Lomont怒了,采用暴力方法一個數字一個數字試過來,終於找到一個比卡馬克數
字要好上那么一丁點的數字,雖然實際上這兩個數字所產生的結果非常近似,這個暴
力得出的數字是0x5f375a86。

Lomont為此寫下一篇論文,"Fast Inverse Square Root"。

 

論文下載地址:
http://www.math.purdue.edu/~clomont/Math/Papers/2003/InvSqrt.pdf
http://www.matrix67.com/data/InvSqrt.pdf

參考:<IEEE Standard 754 for Binary Floating-Point Arithmetic><FAST INVERSE SQUARE ROOT>


最后,給出最精簡的1/sqrt()函數:
float InvSqrt(float x)
{
float xhalf = 0.5f*x;
int i = *(int*)&x; // get bits for floating VALUE
i = 0x5f375a86- (i>>1); // gives initial guess y0
x = *(float*)&i; // convert bits BACK to float
x = x*(1.5f-xhalf*x*x); // Newton step, repeating increases accuracy
return x;

大家可以嘗試在PC機、51、AVR、430、ARM、上面編譯並實驗,驚訝一下它的工作效率。

 

 

百度百科給的Carmack的sqrt()函數

static  float  CarmackSqrt ( float  x)
{
        float  xhalf = 0.5f * x;
         
        int  i = *( int *)&x;            // get bits for floating VALUE 
        i = 0x5f3759df - (i>>1);      // gives initial guess y0
        x = *( float *)&i;              // convert bits BACK to float
        x = x*(1.5f - xhalf*x*x);     // Newton step, repeating increases accuracy
        x = x*(1.5f - xhalf*x*x);     // Newton step, repeating increases accuracy
        x = x*(1.5f - xhalf*x*x);     // Newton step, repeating increases accuracy
        return  (1 / x);
}

 

 

 

 

 

 

 

 

 

 

 

 

看完故事是不是覺得技巧很重要

故事告一段落,要不要來練練手( ̄o ̄)

poj 2109

http://poj.org/problem?id=2109

題目大意: K ^ N = P, 給N 和 P, 求K。數據規模 :1<=n<= 200, 1<=p<10101 而且保證存在 k, 1<=k<=109 。

正常不就是   二分+高精度算法  嗎?

 

 

AC代碼:

1 #include<cstdio>
2 #include<cmath>
3 double n, p;
4 int main(){
5     while(scanf("%lf%lf", &n, &p) != EOF){
6         printf("%.0f\n", pow(p, 1/n));
7     }
8 }
View Code

 

 

哇哈哈,看到沒有,看到沒有,這就是技巧(*゚▽゚*)

double雖然精度只有16位左右,但是我們只要知道前16位就夠了,后面任憑他用科學計數法去表示吧,反正我們不需要。

因為當n錯一位,K的值前16位都會變化很大,所以這樣計算出來的K肯定是唯一的。

 

 

 

 

 

 

 

 

 

 

 

下面來說說對數:

 

C語言中,有兩個log函數,分別為log和log10函數

 

log()是以e為底數的,數學上我們是寫作ln(x)的

log10()是以10為底數的

 

那如果我想以2作為底數怎么辦

 

這么寫   log(x) / log(2) 數學公式,還記得嗎<( ̄︶ ̄)>

log1

 

 

 

定義類型:double log(double x);

     double log10(double x);

 

 

當然我們一般用double的,它不只能接受double

double log (double x);
      float log (float x);
long double log (long double x);
     double log (T x);           // additional overloads for integral types

最后一句模板T類型只有c++11支持,基本你不會自己去重載所以用不上
然后,從c++98開始,就支持 <complex> and <valarray>兩個類型了



待會我會講講
<complex>頭文件,這是復數類

 

 

 

 

在比較a^b和c^d次方,如果b和d非常大怎么辦

比如這題:hdu 5170

http://acm.hdu.edu.cn/showproblem.php?pid=5170

告訴你a,b,c,d,要你比較a^b和c^d,輸出"<",">"或"="

1a,b,c,d1000

 

 

 

所以直接用log的性質

log(a^b) = b * log(a)

如果兩邊同時log一下再比較,那就方便多了(注意log有精度誤差)

完整性質:

log2

 

 

AC代碼:

 1 #include<cstdio>
 2 #include<cmath>
 3 int main(){
 4     int a, b, c, d;
 5     double l, r;
 6     while(~scanf("%d%d%d%d", &a, &b, &c, &d)){
 7         l = b * log(a);
 8         r = d * log(c);
 9         if(fabs(l - r) < 1e-6){//精度誤差,一般小於0.000001可以認為相等 
10             puts("=");
11         }else if(l < r){
12             puts("<");
13         }else{
14             puts(">");
15         }
16     }
17 }
18 //關於1e-6,有人寫1e-7,1e-8,連1e-10都有,看喜好咯 
View Code

 

 

或許有比較數學化的比較方法,但是精度用的好的人真是無敵了(☆゚∀゚)

 

 

 

 

 

 

 

 

有沒有想過遇到x^y^z怎么辦

cf 621D

http://codeforces.com/problemset/problem/621/D

給你三個數x,y,z,比較這12個式子,問你哪個式子最大

0.1 ≤ x, y, z ≤ 200.0

 

 

x^(y^z)

這個式子log一下

變成

原式 = y^z*log(x)

再log一下變成

= log(y^z*log(x))

= log(y^z) + log(log(x))

= z * log(y) + log(log(x))

 

本來這樣就可以比較了

可是題目的范圍是0.1

log()小數會產生負數

log負數就沒意義了

所以對於log(log(x))這么寫不行

 

 

 

 

那怎么辦

 

哼哼,技巧

double范圍 -1.7*10(-308)~1.7*10(308)
long double范圍 128 18-19 -1.2*10(-4932)~1.2*10(4932)

雖然他們兩精度都是16位,但是200的200次方long double竟然存的下

 

所以只要一次log就好了

然后愉快的寫代碼吧

 

AC代碼:

 1 #include<cstdio>
 2 #include<cmath>
 3 #include<iostream>
 4 using namespace std;
 5 char str[12][10] = {
 6     "x^y^z",
 7     "x^z^y",
 8     "(x^y)^z",
 9     "(x^z)^y",
10     "y^x^z",
11     "y^z^x",
12     "(y^x)^z",
13     "(y^z)^x",
14     "z^x^y",
15     "z^y^x",
16     "(z^x)^y",
17     "(z^y)^x",
18 };
19 long double x, y, z;
20 long double mx, t;
21 int pos;
22 void judge(int x){
23     //printf("t = %llf\n", t);
24     if(fabs(mx - t) <= 1e-6) return ;
25     else if(mx < t){
26         pos = x;
27         mx = t;
28     }
29 }
30 int main(){
31     cin >> x >> y >> z;
32     pos = 0;
33     mx = pow(y, z)*log(x);
34     t = pow(z, y)*log(x);
35     judge(1);
36     t = z*log(pow(x, y));
37     judge(2);
38     t = y*log(pow(x, z));
39     judge(3);
40     
41     t = pow(x, z)*log(y);
42     judge(4);
43     t = pow(z, x)*log(y);
44     judge(5);
45     t = z*log(pow(y, x));
46     judge(6);
47     t = x*log(pow(y, z));
48     judge(7);
49     
50     t = pow(x, y)*log(z);
51     judge(8);
52     t = pow(y, x)*log(z);
53     judge(9);
54     t = y*log(pow(z, x));
55     judge(10);
56     t = x*log(pow(z, y));
57     judge(11);
58     
59     printf("%s\n", str[pos]);
60 }
View Code

 

 

 

 

 

 

其實log()一個負數是可以解的

 

 

還記得當年大明湖畔的歐拉公式嗎

e = -1

 

因為e的i∏次方等於-1

所以log(-1) = i∏

 

所以負數迎刃而解

log(-2) = log(-1 * 2) = log(-1) + log(2)

 

那log(i)呢

 

根號-1等於i

所以log(i) = log( -1^(1/2) ) = 1/2 * log(-1) = 1/2 * i∏

 

那log(a + bi)

 

歐拉原公式寫作

eix = cosx + isinx

那么

log3

 

 

所以說嘛,年輕人就應該拿一本復變函數去看去(,,• ₃ •,,)

 

 

 

附上剛剛那題用復數計算的AC代碼

 

 1 #include <iostream>
 2 #include <complex>
 3 #include <string>
 4 using namespace std;
 5 bool bigger (complex<long double> a, complex<long double> b) {
 6     if (imag(a) == 0 && imag(b) == 0) {//沒有虛部 
 7         return real(a) > real(b);//比較實部 
 8     } else if (imag(a) == 0 && imag(b) != 0) { //有虛部的肯定小 
 9         return true;
10     } else if (imag(a) != 0 && imag(b) == 0) {
11         return false;
12     } else if (imag(a) != 0 && imag(b) != 0) {//都有虛部,按實部反過來比 
13         return real(a) < real(b);
14     }
15 }
16 
17 int main () {
18     long double ax, ay, az;
19     cin >> ax >> ay >> az;
20     
21     complex<long double> x (ax, 0.0L);
22     complex<long double> y (ay, 0.0L);
23     complex<long double> z (az, 0.0L);
24     
25     complex<long double> cmaz (3, 3);
26     string ans = "xd";
27     
28     if (bigger(z * log(y) + log(log(x)), cmaz)) {
29         cmaz = z * log(y) + log(log(x));
30         ans = "x^y^z";
31     }
32     if (bigger(y * log(z) + log(log(x)), cmaz)) {
33         cmaz = y * log(z) + log(log(x));
34         ans = "x^z^y";
35     }
36     if (bigger(log(y * z) + log(log(x)), cmaz)) {
37         cmaz = log(y * z) + log(log(x));
38         ans = "(x^y)^z";
39     }
40     
41     if (bigger(z * log(x) + log(log(y)), cmaz)) {
42         cmaz = z * log(x) + log(log(y));
43         ans = "y^x^z";
44     }
45     if (bigger(x * log(z) + log(log(y)), cmaz)) {
46         cmaz = x * log(z) + log(log(y));
47         ans = "y^z^x";
48     }
49     if (bigger(log(x * z) + log(log(y)), cmaz)) {
50         cmaz = log(x * z) + log(log(y));
51         ans = "(y^x)^z";
52     }
53     
54     if (bigger(y * log(x) + log(log(z)), cmaz)) {
55         cmaz = y * log(x) + log(log(z));
56         ans = "z^x^y";
57     }
58     if (bigger(x * log(y) + log(log(z)), cmaz)) {
59         cmaz = x * log(y) + log(log(z));
60         ans = "z^y^x";
61     }
62     if (bigger(log(x * y) + log(log(z)), cmaz)) {
63         cmaz = log(x * y) + log(log(z));
64         ans = "(z^x)^y";
65     }
66     
67     cout << ans << endl;
68 }
View Code

 

現在知道了吧

complex類就是這么用的,而且log支持接收復數類,真是太神奇了(*゚▽゚*)

 

 

 

 

 

 

 

 

 

 

這篇文章寫的我累死了π__π

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM