Shone.Math開源系列2
實數類型(含分數和無理數)的實現
作者:Shone
聲明:原創文章歡迎轉載,但請注明出處,https://www.cnblogs.com/ShoneSharp。
摘要: 計算機數值計算存在輸入進制誤差、計算過程的分數和無理數運算誤差,是很多編程開發的痛點所在。開源項目Shone.Math提供了統一的實數類型Real,支持分數和無理數計算,做到精度、性能和存儲的各方面平衡,可以消除輸入進制誤差和分數計算誤差,大幅減少無理數的計算過程誤差。
Shone.Math是一個支持Math<T>泛型數值計算和Real實數運算(浮點數、分數、PI,E,Log,Exp等無理數)的輕量級基礎數學庫。該項目開源地址https://github.com/shonescript/Shone.Math,是本人把多年代碼積累正式轉向.NET 5的第一個開源項目,請大家多多支持了。
本系列博客上個章節詳細介紹了Shone.Math的Math<T>的泛型實現,全面支持了系統數值類型。有評論提到了相關數據精度話題,因此今天就把Shone.Math的特色—“實數運算”提前進行介紹。
一、數值計算之殤
大家在編程過程中其實不斷在跟各種數值類型打交道,為什么沒有可以“一統江湖”的數值類型?目前還真沒有!
很多動態語言直接使用double(64位二進制浮點數)作為唯一數值,然並卵,立馬就會碰到下面經典的翻車案例,不信你打開編輯器測試下面公式:
0.1+0.2 竟然不等於 0.3,而是等於0.30000000000000004
如果使用整數類型int或long,碰到除法5/2竟然等於2,那更翻車翻的四腳朝天,因此一般通用計算必須采用浮點數就是這個道理,有誤差大家都知道,也就將就將就吧。
二、數值誤差難點
在有限的時間和空間約束下,計算機數值計算存在誤差主要在於三個方面:
(1)輸入時進制表達誤差:前面的0.1是十進制小數,用二進制浮點數表示會是個無限循環形式,只能截除尾數導致誤差。這是最無奈的,一輸入就是不精確。那么使用decimal(128位十進制浮點數)就沒有這個問題,但是十進制常規CPU不支持,計算速度慢十幾倍,大家也不大願意用,除了金融系統實在沒辦法。
(2)計算過程的分數運算誤差:除法運算可能產生不可規約分數,用小數點表達就是無限循環形式,必須截除尾數,上面的進制轉換誤差本質上也是分數導致的問題。該問題采用decimal十進制浮點數也沒用,只有使用分數形式才能准確表達,那么需要增加一倍存儲和接近四倍計算時間的開銷。
(3)計算過程的無理數運算誤差:類似PI、E、Sqrt、Log、Exp、Sin、Cos、等運算,都可能產生無理數,用小數表達就是無限不循環的形式,真是不死不休,一輩子也算不完!那些超算中心專門為了計算PI都使用高能計算機,還是算不完,這是用啥進制都沒用,用分數也不行。這也是分數表達不受待見的原因,你搞半天,碰到無理數運算,誤差立馬要算總賬,沒法控制。
無理數運算隨時存在,目前還沒有很好的解決方案,多數只能采用更高精度的浮點數如quad(128位二進制浮點數)、甚至bigfloat(可指定任意位數精度的二進制浮點數),但帶來存儲和計算時間增加的開銷也很大。
三、Shone.Math實數解決方案
Shone.Math實數有針對性解決了大部分上述問題,填了很多坑,盡量做到易用性、性能等各方面平衡。各位有興趣可以到開源項目地址,下載dll試用或代碼研究一下,有BUG、問題或建議可以在上面直接提出來,也可以pull參與項目代碼完善和實現。
Shone.Math實數把數值分為幾類,並通過類型派生進行表達和計算重載:
(1)可二進制有效表達的浮點數:結果值是有限不循環的二進制整數或小數,可使用1個double數值(如2.5r,與常規數值相同),放在基類Real中進行表達和計算;
(2)分子分母均可二進制有效表達的分數:結果值是無限循環的二進制浮點數,需要采用2個double數值的分數形式(如3\5r,采用反斜杠表示分子分母是一個整體),放在派生類Ration中進行表達和計算;
(3)可間接包含分數系數的各類無理數:結果值是無限不循環的二進制浮點數,但是可以針對常用的部分無理數采用專門的間接表達形式如下,基類為Irration,每個間接無理數都采用2個double數值的分數形式。
a)PI無理數:IrrationPI,如3\5pi,間接表示3/5*PI的計算值;
b)E無理數:IrrationE,如3\5e,間接表示3/5*PI的計算值;
c)Sqrt無理數:IrrationSqrt,如3\5sqt,間接表示sqrt(3/5)的計算值;
d)Sqrd無理數:IrrationSqrd,如3\5sqd,間接表示(3/5)*(3/5)的計算值;
e)Xp無理數:IrrationXp,如3\5xp,間接表示pow(10,3/5)的計算值;
f)Exp無理數:IrrationExp,如3\5exp,間接表示pow(E,3/5)的計算值;
g)Pow無理數:IrrationPow,如3\5pow,間接表示pow(3,5)的計算值;
h)Log無理數:IrrationLog,如3\5ln,間接表示底數為E的ln(3/5)計算值;
i)Log10無理數:IrrationLog10,如3\5lg,間接表示底數為10的lg(3/5)計算值;
j)Logx無理數:IrrationLog,如3\5log,間接表示底數為3的log(3, 5)計算值;
注意,上述有些間接無理數是互補運算(如sqrd與sqrt、xp與log10、exp與log等),如果兩個一起會消解,從而保持計算過程的精確性。
(4)其他無法間接表達的無理數:在計算過程中只要碰到這種類型,比如Sin、Cos三角函數等計算結果還是無理數時只能截斷尾數,轉化為第一類可二進制有效表達的浮點數,仍是Real表達。
四、Shone.Math實數解決問題
Shone.Math的Real實數類型設計和實現上盡量做到精度、性能和存儲各方面的平衡考慮,在有限的時間和空間內,可以有效減少計算機數值計算誤差。
(1)消除了輸入時進制表達誤差:0.1將被轉化為1\10r的分數表示,沒有進制轉換問題。
(2)消除了計算過程的分數運算誤差:支持純正的分數運算也沒有誤差。
(3)減少計算過程的無理數運算誤差:大量常用的PI、E、Sqrt、Log、Exp等無理數運算采用間接形式表達,直到實在無法間接表達時再截尾處理產生誤差,但總體上將大大減少常規計算的總體誤差。
當然無理數運算誤差不可能徹底解決,Shone.Math只是提出了一個可行的實現方法,具體好壞還有待在實際應用中考證。
五、Shone.Math實數Real使用方法
Shone.Math只有一個dll文件,除了.NET5系統外無任何外部依賴。注意:Shone.Math支持.NETCore3.1/5.0以上版本,一方面是擁抱未來向前看,另一方面是開始時發現.NET4和.NET5差好多內容,如MathF類,Math.Asinh,Acosh,Atanh,還有各種Span<T>,Memory<T>等高級類型,這也符合.NET5一統江湖的趨勢。
1、安裝Visual Studio 2019
更新到最新版,在選項設置中打開.net 5 preview支持。
2、下載nuget包或github代碼
Nuget包:https://www.nuget.org/packages/Shone.Math/1.0.7
源代碼:https://github.com/shonescript/Shone.Math/releases
3、引用nuget包或Shone.Math.dll到你的項目中
4、添加命名空間using Shone;
5、愉快地使用實數常量、方法
using Shone; // import Shone namespace
var d = Real.PI*3; // result is 3pi of IrrationPi
var x = 5.ToReal().Pow(3); // write in dot style
var ds = new double[]{5, 6, 7}.ToReal().Pow(3); // calculate array easily
6、Real也支持作為Math<T>計算
7、注意:Real只有基類對外暴露方法,其他派生類只能在計算過程中自動產生,比如進行Sqrt()時,如果結果可以間接表達,就會產生如3\5sqrt之類的IrrationSqrt無理數,使用時可以體會一下。
六、Real類型的不足之處
前面也說了,Shone.Math的Real類型解決了輸入和過程的分數誤差,但沒有徹底消除無理數誤差,只是進行推遲和消解,要進行等值判斷時其精度還只能與double類似。
Real類型設計為class,對大量數值計算而言,會產生堆上內存分配和相關GC,對性能有影響。我最早采用的是struct,但需要1個計算結果、2個double分子分母、還有1個byte的標記,占用存儲太大,而且有好多判斷,不容易調試。最終經過權衡使用現在的方案。
七、小結
基於.NET 5的開源項目Shone.Math,通過各種精巧實現,提供了統一的泛型數值計算靜態類Math<T>和實數類型Real,為開發各類自定義數值、幾何、空間、公式解析等泛型和高精度數值應用打下了堅實基礎。本系列下一章節將介紹Shone.Math的一些.NET5專用高級特性如ref, Span, Memory的泛型數值計算擴展。
聲明:原創文章歡迎轉載,但請注明出處,https://www.cnblogs.com/ShoneSharp。