math toolkit for real-time development讀書筆記一三角函數快速計算(1)

一、基礎知識

根據高中知識我們知道,很多函數都可以用泰勒級數展開。正余弦泰勒級數展開如下:

將其進一步抽象為公式可知:

正弦和余弦的泰勒級數具有高度結構化的模式,可拆解為以下核心特征:

1. 符號交替特性

  • 正弦級數:項的符號以 (-1)^{n}交替,即 +,?,+,?,…?
  • 余弦級數:符號同樣由 (-1)^{n}控制,但首項為正(n=0 時),即 +,?,+,?,…
    作用:正負項交替抵消了部分累積誤差,避免數值無意義地發散(如大角度場景中,若所有項同號,和會迅速溢出)。

2. 冪次規律

  • 正弦:包含 x 的奇次冪(x^{1},x^{3},x^{5},…),對應通項中的 2n+1。
  • 余弦:包含 x 的偶次冪(x^{0},x^{2},x^{4}…),對應通項中的 2n(注意 x^{0}=1 為首項)。
    直觀理解:正弦是奇函數(僅含奇次冪),余弦是偶函數(僅含偶次冪),與函數的奇偶性代數定義一致。

3. 分母的階乘主導

  • 分母為分子指數的階乘,即奇次冪項分母為 (2n+1)!,偶次冪項為 (2n)!。
  • 階乘的超指數增長:例如 10!=3,628,800,20!≈2.43*10^{18},遠快于 x^{n}的增長(即使 x 較大)。
    關鍵作用:確保級數對任意 x 收斂,且小角度下項值急劇衰減(如 x=π/6 時,第 3 項已小于 2*10^{-4})。

????????在任何計算機數學的教科書或手冊中,你也都會找到這些函數的定義和相關性質。然而僅有公式并不足以在實際應用中解決問題。公式是基礎,但只有結合計算特性的優化,才能真正解決現實問題

如果直接利用泰勒級數進行計算,將遇到幾方面的問題:

  • 階乘的計算邊界:在編程中,n! 很快會超出數值類型范圍(如 32 位整數最大表示 20!),需使用浮點類型或大數庫,或通過遞推避免顯式計算階乘。
  • 機器精度的限制:計算機無法表示無限精度,當項值小于機器的最小精度時,其對總和的貢獻在數值上為零,此時截斷級數是合理的。

????????無窮級數的問題在于它們…… 嗯…… 是無窮的。如果我有一臺無限算力的計算機和無限的時間,我現在就可以結束跳過后續的內容,關上電腦愉快的下班各回各家各找各媽了。

????????但在現實世界中,你不可能等待計算無窮多項。計算機也無法處理無限項計算。以 sin(1000) 為例,直接展開需數千項才能使階乘主導項衰減,而單精度浮點數在計算前幾項時就會因 xn 過大而溢出(如 1000^{10}=10^{30} ,已接近單精度上限 3.4*10^{38})。幸運的是,你也不需要無限的精度。計算機中的任何實數都只是一個近似值,受限于計算機的字長。當計算機依次計算級數中的高階項時,這些項最終會變得小于計算機的位分辨率。在那之后,計算更多的項就毫無意義了。

????????在這個級數中,隨著 n 的增大,各項變得越來越小,但級數的和卻是無窮大。數學家們必然且確實會關注冪級數的收斂性;不過請放心,如果有人給出一個如 sin (x) 這樣的函數的級數展開式,他們必定已確保其收斂性。對于正弦和余弦函數,符號交替的特性確保了小項不會累積成大的數值。

????????如果高階項變得可以忽略不計,你可以截斷級數以獲得多項式近似。處理任何無窮級數時都必須這樣做。有時你可以估算引入的誤差,但大多數情況下,程序員只需保留足夠多的項,以確保誤差控制在合理范圍內。

????????通過一個例子來理解可能更直觀。表 5.1計算了 x=30°(即 π/6 弧度)的正弦和余弦值。在所示的精度范圍內,兩個級數在第五項后均已收斂(由于余弦項的每一項比正弦項少一個 x 的冪次,其收斂速度通常稍慢)。正如預期,由于分母中階乘的存在,各項的絕對值迅速減小。

表5.1
項數n正弦項(-1)^{n}\frac{x^{2n+1}}{(2n+1)!}正弦項累加和
0(-1)^{0}\frac{(\pi /6)^{2*0+1}}{(2*0+1)!} =\frac{\pi }{6}\approx 0.523598780.52359878
1(-1)^{1}\frac{(\pi /6)^{2*1+1}}{(2*1+1)!} =-1*\frac{\frac{\pi }{6}^{3}}{3!}\approx -0.023796430.49980235
2(-1)^{2}\frac{(\pi /6)^{2*2+1}}{(2*2+1)!} =1*\frac{\frac{\pi }{6}^{5}}{5!}\approx 0.000334620.50013697
3(-1)^{3}\frac{(\pi /6)^{2*3+1}}{(2*3+1)!} =-1*\frac{\frac{\pi }{6}^{7}}{7!}\approx- 0.000002570.50013440
4(-1)^{2}\frac{(\pi /6)^{2*4+1}}{(2*4+1)!} =1*\frac{\frac{\pi }{6}^{9}}{9!}\approx 0.000000010.50013441
真實值sin(π/6)=0.5誤差:1.34*10^{-5}
項數n余弦項(-1)^{n}\frac{x^{2n}}{(2n)!}余弦項累加和
0(-1)^{0}\frac{(\pi /6)^{2*0}}{(2*0)!} =\frac{\pi }{6}^{0}= 11
1(-1)^{1}\frac{(\pi /6)^{2*1}}{(2*1)!} =-1*\frac{\frac{\pi }{6}^{2}}{2!}\approx -0.137071870.86292813
2(-1)^{2}\frac{(\pi /6)^{2*2}}{(2*2)!} =1*\frac{\frac{\pi }{6}^{4}}{4!}\approx 0.002790310.86571844
3(-1)^{3}\frac{(\pi /6)^{2*3}}{(2*3)!} =-1*\frac{\frac{\pi }{6}^{6}}{6!}\approx- 0.000032980.86568546
4(-1)^{2}\frac{(\pi /6)^{2*4}}{(2*4)!} =1*\frac{\frac{\pi }{6}^{8}}{8!}\approx -0.000000390.86568585
真實值cos(π/6)=0.8660254

誤差:3.39*10^{-5}

????????對于三角函數的任何實際計算,最終結果都要求我必須限制 x 的范圍,就像我對平方根函數所做的那樣。顯然,范圍越小,所需的項數就越少。我還沒有向你展示如何限制范圍,但暫時假設這是可以做到的。那么需要多少項呢?

????????在這種情況下,計算所需的項數相當直接。由于級數中的項具有交替的符號,我不必擔心諸如許多小數項累加為大數之類的潛在問題。正如你在表 5.1 中看到的,中間結果圍繞最終結果振蕩。因此,我可以確定誤差總是小于(有時遠小于)第一個被忽略項的大小。我需要做的就是為角度范圍的代表性值計算方程中每個項的值,然后將結果與我所使用的數值表示中的最低有效位(LSB)精度進行比較。由于我無法預先知道你可能使用的數值格式,因此最好將這兩個步驟分開。項值如表 5.2 和 5.3 所示。請注意,對于更小的范圍,誤差減小得有多顯著。

?????????在表 5.4 和 5.5 中,我使用這些結果計算了典型計算機表示所需的項數。通常,預期尾數范圍為 24 位(32 位浮點數)或 56 位(C 語言雙精度使用的八字節格式)。較小的數值有時在專用嵌入式系統中有用,而最大的數值對應雙精度和數值協處理器的字長。同樣,只需看一眼這些表格,你就會相信,花一些計算能力來減少輸入參數的范圍是值得的。

????????既然我已經知道需要在截斷級數中包含多少項,接下來仍需設計代碼對其進行計算。缺乏經驗的程序員可能會直接按方程 [5.7] 的寫法進行編程,為每一項反復計算x^{n}和n!。一個簡單的停止準則可能只是將每一項的值與某個誤差準則進行比較(在這種情況下,表 5.2 至 5.5 并非必需)。

????????事實上,這正是我最初實現該函數的方式,只是為了證明自己能夠做到。這是一個糟糕的實現,因為它需要反復將x提升到越來越高的冪次,更不用說為每一項調用兩個耗時函數的開銷了。誠然,該函數對任何輸入(無論多大)都能給出正確答案,但付出的運行時性能代價卻非常高昂。

double factorial(unsigned long n){double retval = 1.0;for(; n>1; --n)retval *= (double)n;return retval;
}
double sine(double x){double first_term;double next_term = x;double retval = x;double sign = 1.0;double eps = 1.0e-16;int n = 1;do{cout << next_term << endl;first_term = next_term;n += 2;sign *= -1.0;next_term = sign * pow(x, n)/factorial(n);retval += next_term;
}while((abs(next_term) > eps));return retval;
}

?????????通過觀察到每一項都可以由前一項遞推計算,我們可以顯著優化代碼效率。回顧方程 [5.9] 中正弦函數的通項公:

該級數的下一項時:

前后項相比知;

我們以R_{n+1}代表其比值:

?改進版本消除了對 pow () 和 factorial () 的調用。此版本如下代碼所示。

double sine(double x){double last = x;double retval = x;double eps = 1.0e-16;long n = 1;do{n += 2;last *= (-x * x/((double)n * (double)(n-1)));retval += last;
}while((abs(last) > eps));return retval;
}

?如上 中的代碼效率尚可,許多人可能想就此打住。然而,這個函數的實現方式仍然相當糟糕。我可以通過僅計算一次 x2 來稍作改進,但還有一種更好的方法 —— 方程 [5.11] 中的關系給出了線索:每一項都是下一項的一個因子。事實上,這甚至包括值 x,它存在于每一項中。因此,我可以將方程 [5.7] 和 [5.8] 因式分解為:

????????這種公式化方法被稱為霍納法則,它幾乎是計算該級數的最優方式。請注意,每一步的分母都是兩個連續整數的乘積 —— 在正弦級數中為 2×3、4×5、6×7,等等。一旦你發現這一規律,就能憑記憶寫出這些函數的計算式。如果無法使用現成的正弦函數,而你又急需一個,直接對這兩個方程進行編碼其實相當不錯。

????????不過仍有幾個細節需要解決。首先,我遇到一個小問題:由于必須保存所有中間結果,所示方法會占用大量棧空間。如果你的編譯器是高度優化的,或者你不介意棧空間消耗,這不會有問題。但如果你使用的是簡單編譯器和 80x87 數學協處理器,你會像我一樣很快發現,協處理器可能會發生棧溢出。

????????式5-12可以重新改寫為:

????????但對于計算機實現而言,我可以通過另一輪變換消除一半的符號變化。這可能并不明顯,但如果我仔細地將所有前導負號折疊到括號內的表達式中,就能得到等效的形式。

????????這已經是相當高效的實現方式了。這些方程雖然看起來不簡潔,但計算速度非常快。就像我可以在方程 [5.12] 和 [5.13] 的右側添加更多項一樣,也可以在方程 [5.15] 和 [5.16] 的左側添加項。只需記住,這次符號是交替變化的,從右端顯示的正號開始。

??????? 如下代碼展示了這些方程的直接實現。所使用的項數適用于 32 位浮點精度和 ±45° 的范圍。由于大多數計算機執行乘法的速度比除法快,我通過將常數存儲為其倒數來調整了算法(不用擔心涉及常數的表達式,大多數編譯器會對這些部分進行優化)。注意名稱前添加的下劃線,這是為了強調(雙關語)這些函數僅在有限范圍內有用的事實。我會在后面的全范圍版本中使用這些函數。

// Find the Sine of an Angle <= 45
double _sine(double x){double s1 = 1.0/(2.0*3.0);double s2 = 1.0/(4.0*5.0);double s3 = 1.0/(6.0*7.0);double s4 = 1.0/(8.0*9.0);double z = x * x;return ((((s4*z-1.0)*s3*z+1.0)*s2*z-1.0)*s1*z+1.0)*x;
}
// Find the Cosine of an Angle <= 45
double _cosine(double x){double c1 = 1.0/(1.0*2.0);double c2 = 1.0/(3.0*4.0);double c3 = 1.0/(5.0*6.0);double c4 = 1.0/(7.0*8.0);double z = x * x;return (((c4*z-1.0)*c3*z+1.0)*c2*z-1.0)*c1*z+1.0;
}

?????????第二個細節?我悄悄省去了一個用于判斷使用多少項的測試。由于必須從內到外計算霍納法則,你必須知道 “內部” 從哪里開始。這意味著你不能在計算過程中測試各項;必須預先知道需要多少項。在這種情況下,對于小的 x 值,霍納法則的額外效率抵消了跳過內部項計算可能獲得的任何收益。這一結果與第 4 章中關于平方根的討論得出的結論相似:有時固定次數地計算某些內容會更簡單(而且通常更快),而不是測試某個終止條件。如果這能讓你更放心,可以為 x 非常接近零的特殊情況添加一個測試;否則,最好計算完整的表達式。

????????你可能想知道,既然我已經在方程 [5.5] 中表明余弦可以由正弦推導而來,為什么還要同時包含正弦和余弦函數。原因很簡單:即使你只對其中一個函數感興趣,實際上也需要能夠計算這兩個函數。當結果接近零時,一種近似最準確;當結果接近一時,另一種近似最準確。根據輸入值的不同,你最終會使用其中一種或另一種。

霍納法則的計算必須從內到外逐層展開,這意味著無法在循環中動態判斷項數,必須預先確定需要計算的項數。這一限制帶來兩個關鍵問題:

1. 為何放棄動態項數測試?

  • 霍納法則的結構性限制:其嵌套乘法形式(如 sin(x)=x?(1?3!x2??(1?5×4x2??(?))))要求先計算最內層項,再向外逐層展開。若在計算中動態判斷是否終止(如 “當項值小于誤差閾值時停止”),需存儲所有中間層結果,這會抵消霍納法則的空間優化優勢。
  • 小角度場景的效率平衡:對于小 x(如 x≈0),級數收斂極快,理論上只需 1-2 項即可滿足精度。但預定義固定項數(如 4 項)的計算成本可能低于動態測試的判斷開銷(如條件分支、浮點比較)。正如第 4 章平方根計算的結論:固定次數的計算有時比動態終止更高效

為何同時實現正弦與余弦函數?

盡管余弦可通過正弦推導(cos(x)=sin(2π??x)),但獨立實現兩者仍有必要:

1. 精度互補性
  • 正弦在 0 附近更精確:當 x≈0 時,正弦級數首項 x 直接逼近真實值,而余弦級數首項為 1,次項 ?2x2? 對小 x 的修正量極小,需更多項才能達到同等精度。
  • 余弦在 ? 附近更精確:當 x≈2π? 時,余弦值接近 0,其級數收斂更快;而正弦值接近 1,需更多項修正首項 x 的較大偏差。
2. 輸入范圍的動態選擇
  • 根據輸入 x 的范圍選擇計算路徑:
    • 若 x 靠近 kπ(正弦主導區間),優先計算正弦,再通過三角恒等式推導余弦。
    • 若 x 靠近 kπ+2π?(余弦主導區間),直接計算余弦更高效。
  • 避免大角度誤差累積:例如,計算 cos(179°) 時,若通過 sin(1°) 推導,需先將 179° 約簡為 1°,再調用正弦函數。此過程可能引入額外的約簡誤差,而直接計算余弦可避免中間步驟

????????至此,我已證明如果輸入參數的范圍可以限制,就能實現一個良好且高效的正弦函數。接下來需要說明的是,我確實可以限制范圍。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/81265.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/81265.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/81265.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

uni-app 中適配 App 平臺

文章目錄 前言? 1. App 使用的 Runtime 架構&#xff1a;**WebView 原生容器&#xff08;plus runtime&#xff09;**&#x1f4cc; 技術棧核心&#xff1a; ? 2. WebView Native 的通信機制詳解&#xff08;JSBridge&#xff09;&#x1f4e4; Web → Native 調用&#xf…

SpringBoot基礎(靜態資源導入)

靜態資源導入 在WebMvcAutoConfiguration自動配置類中 有一個添加資源的方法&#xff1a; public void addResourceHandlers(ResourceHandlerRegistry registry) { //如果靜態資源已經被自定義了&#xff0c;則直接生效if (!this.resourceProperties.isAddMappings()) {logg…

基于OpenCV的人臉識別:LBPH算法

文章目錄 引言一、概述二、代碼實現1. 代碼整體結構2. 導入庫解析3. 訓練數據準備4. 標簽系統5. 待識別圖像加載6. LBPH識別器創建7. 模型訓練8. 預測執行9. 結果輸出 三、 LBPH算法原理解析四、關鍵點解析五、改進方向總結 引言 人臉識別是計算機視覺領域的一個重要應用&…

ElasticSearch重啟之后shard未分配問題的解決

以下是Elasticsearch重啟后分片未分配問題的完整解決方案&#xff0c;結合典型故障場景與最新實踐&#xff1a; 一、快速診斷定位 ?檢查集群狀態 GET /_cluster/health?pretty # status為red/yellow時需關注unassigned_shards字段值 ? 2.查看未分配分片詳情 …

CSS- 3.1 盒子模型-塊級元素、行內元素、行內塊級元素和display屬性

本系列可作為前端學習系列的筆記&#xff0c;代碼的運行環境是在HBuilder中&#xff0c;小編會將代碼復制下來&#xff0c;大家復制下來就可以練習了&#xff0c;方便大家學習。 HTML系列文章 已經收錄在前端專欄&#xff0c;有需要的寶寶們可以點擊前端專欄查看&#xff01; 點…

Git/GitLab日常使用的命令指南來了!

在 GitLab 中拉取并合并代碼的常見流程是通過 Git 命令來完成的。以下是一個標準的 Git 工作流&#xff0c;適用于從遠程倉庫&#xff08;如 GitLab&#xff09;拉取代碼、切換分支、合并更新等操作。 &#x1f310; 一、基礎命令&#xff1a;拉取最新代碼 # 拉取遠程倉庫的所…

HTML 表格與div深度解析區別及常見誤區

一、HTML<div>元素詳解 <div>是HTML中最基本的塊級容器元素&#xff0c;本身沒有語義&#xff0c;主要用于組織和布局頁面內容。以下是其核心用法&#xff1a; 1. 基礎結構與特性 <div><!-內部可包含任意HTML元素 --><h2>標題</h2><p…

mybatisPlus 新增時 其他字段的值和 id 保持一致實現方法

MyBatis-Plus 實現 sp_id_path 與 id 同步的方案 要實現新增時 sp_id_path 自動與 id 保持一致&#xff0c;需要在實體類和插入邏輯中做相應處理。MyBatis-Plus 提供了幾種方式來實現這一需求&#xff1a; 方案一&#xff1a;使用 MyBatis-Plus 的自動填充功能 這是最優雅的…

蘭亭妙微設計:為生命科技賦予人性化的交互語言

在醫療科技日新月異的今天&#xff0c;卓越的硬件性能唯有匹配恰如其分的交互語言&#xff0c;方能真正發揮價值。作為專注于醫療UI/UX設計的專業團隊&#xff0c;蘭亭妙微設計&#xff08;www.lanlanwork.com&#xff09;始終相信&#xff1a;每一處像素的排布&#xff0c;都應…

Tcping詳細使用教程

Tcping詳細使用教程 下載地址 https://download.elifulkerson.com/files/tcping/0.39/在windows環境下安裝tcping 在以上的下載地中找到exe可執行文件&#xff0c;其中tcping.exe適用于32位Windows系統&#xff0c;tcping64.exe適用于64位Windows操作系統。 其實tcping是個…

springCloud/Alibaba常用中間件之Seata分布式事務

文章目錄 SpringCloud Alibaba:依賴版本補充Seata處理分布式事務(AT模式)AT模式介紹核心組件介紹AT的工作流程&#xff1a;兩階段提交&#xff08;**2PC**&#xff09; Seata-AT模式使用Seata(2.0.0)下載、配置和啟動Seata案例實戰前置代碼添加全局注解 GlobalTransactional Sp…

COMSOL隨機參數化表面流體流動模擬

基于粗糙度表面的裂隙流研究對于理解地下水的流動、污染物傳輸以及與之相關的地質災害&#xff08;如滑坡&#xff09;等方面具有重要意義。本研究通過蒙特卡洛方法生成隨機表面形貌&#xff0c;并利用COMSOL Multiphysics對隨機參數化表面的微尺度流體流動進行模擬。 參數化…

初識——QT

QT安裝方法 一、項目創建流程 創建項目 入口&#xff1a;通過Qt Creator的歡迎頁面或菜單欄&#xff08;文件→新建項目&#xff09;創建新項目。 項目類型&#xff1a;選擇「Qt Widgets Application」。 路徑要求&#xff1a;項目路徑需為純英文且不含特殊字符。 構建系統…

7-15 計算圓周率

π?131?352!?3573!??357?(2n1)n!?? 輸入格式&#xff1a; 輸入在一行中給出小于1的閾值。 輸出格式&#xff1a; 在一行中輸出滿足閾值條件的近似圓周率&#xff0c;輸出到小數點后6位。 輸入樣例&#xff1a; 0.01輸出樣例&#xff1a; 3.132157 我的代碼 #i…

【圖片識別工具】批量單據識別批量重命名,批量OCR識別圖片文字并重命名,批量改名工具的使用步驟和注意事項

一、適用場景 ??財務與發票管理??&#xff1a;企業需處理大量電子發票或掃描件&#xff0c;通過OCR識別發票代碼、金額等關鍵信息&#xff0c;自動重命名為發票號_金額.pdf格式&#xff0c;便于歸檔與稅務審計。 ??物流單據處理??&#xff1a;物流公司需從運單中提取單…

Modbus TCP轉Profinet網關:數字化工廠異構網絡融合的核心樞紐

在現代工業生產中&#xff0c;隨著智能制造和工業互聯網的不斷發展&#xff0c;數字化工廠成為了制造業升級的重要方向。數字化工廠的核心在于實現設備、數據和人的互聯互通&#xff0c;而這其中&#xff0c;通信協議扮演著至關重要的角色。今天&#xff0c;我們就來探討開疆智…

win11平臺下的docker-desktop中的volume位置問題

因為需要搞個本地的mysql數據庫&#xff0c;而且本地安裝的程序較多&#xff0c;不想再安mysql了&#xff0c;就想到使用docker來安裝。而且因為數據巨大&#xff0c;所以想到直接使用轉移data文件夾的方式。 各種查詢&#xff0c;而且還使用ai查詢&#xff0c;他們都提到&…

【MySQL】項目實踐

個人主頁&#xff1a;Guiat 歸屬專欄&#xff1a;MySQL 文章目錄 1. 項目實踐概述1.1 項目實踐的重要性1.2 項目中MySQL的典型應用場景 2. 數據庫設計流程2.1 需求分析與規劃2.2 設計過程示例2.3 數據庫設計工具 3. 電子商務平臺實踐案例3.1 系統架構3.2 數據庫Schema設計3.3 數…

React學習———CSS Modules(樣式模塊化)

CSS Modules CSS Modules&#xff08;樣式模塊化&#xff09;是一種用于模塊化和局部作用域化CSS樣式的技術&#xff0c;讓CSS只在當前組件內生效&#xff0c;避免全局樣式沖突的技術方案 工作原理 文件命名&#xff1a;通常以.module.css、.module.less、.module.scss等結尾…

agent 智能體應用產品:生圖、生視頻、代碼等

生圖片 Lovart&#xff1a;全球首個設計 Agent https://www.lovart.ai/ 生視頻 AI 視頻 Agent 產品&#xff1a;Medeo https://www.medeo.app/ 代碼 vscode copilot、cursor、trae 其他research manus grok等各個大模型產品