zoukankan      html  css  js  c++  java
  • Shone.Math开源系列2 — 实数类型(含分数和无理数)的实现

    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数值的分数形式(如35r,采用反斜杠表示分子分母是一个整体),放在派生类Ration中进行表达和计算;

    (3)可间接包含分数系数的各类无理数:结果值是无限不循环的二进制浮点数,但是可以针对常用的部分无理数采用专门的间接表达形式如下,基类为Irration,每个间接无理数都采用2个double数值的分数形式。

    a)PI无理数:IrrationPI,如35pi,间接表示3/5*PI的计算值;

    b)E无理数:IrrationE,如35e,间接表示3/5*PI的计算值;

    c)Sqrt无理数:IrrationSqrt,如35sqt,间接表示sqrt(3/5)的计算值;

    d)Sqrd无理数:IrrationSqrd,如35sqd,间接表示(3/5)*(3/5)的计算值;

    e)Xp无理数:IrrationXp,如35xp,间接表示pow(10,3/5)的计算值;

    f)Exp无理数:IrrationExp,如35exp,间接表示pow(E,3/5)的计算值;

    g)Pow无理数:IrrationPow,如35pow,间接表示pow(3,5)的计算值;

    h)Log无理数:IrrationLog,如35ln,间接表示底数为E的ln(3/5)计算值;

    i)Log10无理数:IrrationLog10,如35lg,间接表示底数为10的lg(3/5)计算值;

    j)Logx无理数:IrrationLog,如35log,间接表示底数为3的log(3, 5)计算值;

    注意,上述有些间接无理数是互补运算(如sqrd与sqrt、xp与log10、exp与log等),如果两个一起会消解,从而保持计算过程的精确性。

    (4)其他无法间接表达的无理数:在计算过程中只要碰到这种类型,比如Sin、Cos三角函数等计算结果还是无理数时只能截断尾数,转化为第一类可二进制有效表达的浮点数,仍是Real表达。

    四、Shone.Math实数解决问题

    Shone.Math的Real实数类型设计和实现上尽量做到精度、性能和存储各方面的平衡考虑,在有限的时间和空间内,可以有效减少计算机数值计算误差。

    (1)消除了输入时进制表达误差:0.1将被转化为110r的分数表示,没有进制转换问题。

    (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()时,如果结果可以间接表达,就会产生如35sqrt之类的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。

    标签:Shone.Math 泛型 数值 计算 .NET 5 C#

  • 相关阅读:
    leetcode-滑动窗口
    leetcode刷题-双指针
    nlp
    机器学习
    tf-idf算法
    RNN和LSTM的理解
    DDD落地实践-战术实现心得
    DDD落地实践-战略设计心得
    测试平台系列(66) 数据驱动之基础Model
    Python小知识之对象的比较
  • 原文地址:https://www.cnblogs.com/ShoneSharp/p/ShoneMath-2.html
Copyright © 2011-2022 走看看