Skip to content

A modern .NET library that can PInvoke to gmp and mpfr, that enable both high performance and best .NET convenience.

License

Notifications You must be signed in to change notification settings

sdcb/Sdcb.Arithmetic

Repository files navigation

Sdcb.Arithmetic main QQ

Sdcb.Arithmetic is a modern .NET library that can PInvoke to gmp and mpfr, that enable both high performance and best .NET convenience.

Known classes in Sdcb.Arithmetic:

Class Native name Library
GmpInteger mpz_t Sdcb.Arithmetic.Gmp
GmpFloat mpf_t Sdcb.Arithmetic.Gmp
GmpRational mpq_t Sdcb.Arithmetic.Gmp
GmpRandom gmp_randstate_t Sdcb.Arithmetic.Gmp
MpfrFloat mpfr_t Sdcb.Arithmetic.Mpfr

NuGet Packages

libgmp

Package Id Version License Notes
Sdcb.Arithmetic.Gmp NuGet MIT .NET binding for libgmp
Sdcb.Arithmetic.Gmp.runtime.win-x64 NuGet LGPL native lib in Windows x64
Sdcb.Arithmetic.Gmp.runtime.win-x86 NuGet LGPL native lib in Windows x86
Sdcb.Arithmetic.Gmp.runtime.linux-x64 NuGet LGPL native lib in Linux x64
Sdcb.Arithmetic.Gmp.runtime.linux-x86 NuGet LGPL native lib in Linux x86
Sdcb.Arithmetic.Gmp.runtime.linux-arm NuGet LGPL native lib in Linux ARM
Sdcb.Arithmetic.Gmp.runtime.linux-arm64 NuGet LGPL native lib in Linux ARM64
Sdcb.Arithmetic.Gmp.runtime.linux-musl-x64 NuGet LGPL native lib in Linux Musl x64
Sdcb.Arithmetic.Gmp.runtime.linux-musl-arm64 NuGet LGPL native lib in Linux Musl ARM64
Sdcb.Arithmetic.Gmp.runtime.osx-arm64 NuGet LGPL native lib in macOS ARM64
Sdcb.Arithmetic.Gmp.runtime.osx-x64 NuGet LGPL native lib in macOS x64
Sdcb.Arithmetic.Gmp.runtime.android-arm NuGet LGPL native lib in Android ARM
Sdcb.Arithmetic.Gmp.runtime.android-arm64 NuGet LGPL native lib in Android ARM64
Sdcb.Arithmetic.Gmp.runtime.android-x86 NuGet LGPL native lib in Android x86
Sdcb.Arithmetic.Gmp.runtime.android-x64 NuGet LGPL native lib in Android x64

Update: This library also tested and works good in Loongarch64(龙芯)

mpfr

Package Id Version License Notes
Sdcb.Arithmetic.Mpfr NuGet MIT .NET binding for libmpfr
Sdcb.Arithmetic.Mpfr.runtime.win-x64 NuGet LGPL native lib in Windows x64
Sdcb.Arithmetic.Mpfr.runtime.win-x86 NuGet LGPL native lib in Windows x86
Sdcb.Arithmetic.Mpfr.runtime.linux-x64 NuGet LGPL native lib in Linux x64
Sdcb.Arithmetic.Mpfr.runtime.linux-x86 NuGet LGPL native lib in Linux x86
Sdcb.Arithmetic.Mpfr.runtime.linux-arm NuGet LGPL native lib in Linux ARM
Sdcb.Arithmetic.Mpfr.runtime.linux-arm64 NuGet LGPL native lib in Linux ARM64
Sdcb.Arithmetic.Mpfr.runtime.linux-musl-x64 NuGet LGPL native lib in Linux Musl x64
Sdcb.Arithmetic.Mpfr.runtime.linux-musl-arm64 NuGet LGPL native lib in Linux Musl ARM64
Sdcb.Arithmetic.Mpfr.runtime.osx-arm64 NuGet LGPL native lib in macOS ARM64
Sdcb.Arithmetic.Mpfr.runtime.osx-x64 NuGet LGPL native lib in macOS x64
Sdcb.Arithmetic.Mpfr.runtime.android-arm NuGet LGPL native lib in Android ARM
Sdcb.Arithmetic.Mpfr.runtime.android-arm64 NuGet LGPL native lib in Android ARM64
Sdcb.Arithmetic.Mpfr.runtime.android-x86 NuGet LGPL native lib in Android x86
Sdcb.Arithmetic.Mpfr.runtime.android-x64 NuGet LGPL native lib in Android x64

Native dynamic library name mapping

Defined in GmpNativeLoader.cs and MpfrNativeLoader.cs

OS gmp dynamic lib mpfr dynamic lib
Windows libgmp-10.dll libmpfr-6.dll
Linux libgmp.so.10 libmpfr.so.6
MacOS libgmp.10.dylib libmpfr.6.dylib
Android libgmp.so libmpfr.so
Others gmp.10 mpfr.6

Examples

Calculate 1,000,000 length of π using Sdcb.Arithmetic.Gmp:

// Install NuGet package: Sdcb.Arithmetic.Gmp
// Install NuGet package: Sdcb.Arithmetic.Gmp.runtime.win-x64(for windows)
using Sdcb.Arithmetic.Gmp;

Console.WriteLine(CalcPI().ToString("N1000000"));

GmpFloat CalcPI(int inputDigits = 1_000_000)
{
    const double DIGITS_PER_TERM = 14.1816474627254776555; // = log(53360^3) / log(10)
    int DIGITS = (int)Math.Max(inputDigits, Math.Ceiling(DIGITS_PER_TERM));
    uint PREC = (uint)(DIGITS * Math.Log2(10));
    int N = (int)(DIGITS / DIGITS_PER_TERM);
    const int A = 13591409;
    const int B = 545140134;
    const int C = 640320;
    const int D = 426880;
    const int E = 10005;
    const double E3_24 = (double)C * C * C / 24;

    using PQT pqt = ComputePQT(0, N);

    GmpFloat pi = new(precision: PREC);
    // pi = D * sqrt((mpf_class)E) * PQT.Q;
    pi.Assign(GmpFloat.From(D, PREC) * GmpFloat.Sqrt((GmpFloat)E, PREC) * (GmpFloat)pqt.Q);
    // pi /= (A * PQT.Q + PQT.T);
    GmpFloat.DivideInplace(pi, pi, GmpFloat.From(A * pqt.Q + pqt.T, PREC));
    return pi;

    PQT ComputePQT(int n1, int n2)
    {
        int m;

        if (n1 + 1 == n2)
        {
            PQT res = new()
            {
                P = GmpInteger.From(2 * n2 - 1)
            };
            GmpInteger.MultiplyInplace(res.P, res.P, 6 * n2 - 1);
            GmpInteger.MultiplyInplace(res.P, res.P, 6 * n2 - 5);

            GmpInteger q = GmpInteger.From(E3_24);
            GmpInteger.MultiplyInplace(q, q, n2);
            GmpInteger.MultiplyInplace(q, q, n2);
            GmpInteger.MultiplyInplace(q, q, n2);
            res.Q = q;

            GmpInteger t = GmpInteger.From(B);
            GmpInteger.MultiplyInplace(t, t, n2);
            GmpInteger.AddInplace(t, t, A);
            GmpInteger.MultiplyInplace(t, t, res.P);
            // res.T = (A + B * n2) * res.P;            
            if ((n2 & 1) == 1) GmpInteger.NegateInplace(t, t);
            res.T = t;

            return res;
        }
        else
        {
            m = (n1 + n2) / 2;
            PQT res1 = ComputePQT(n1, m);
            using PQT res2 = ComputePQT(m, n2);
            GmpInteger p = res1.P * res2.P;
            GmpInteger q = res1.Q * res2.Q;

            // t = res1.T * res2.Q + res1.P * res2.T
            GmpInteger.MultiplyInplace(res1.T, res1.T, res2.Q);
            GmpInteger.MultiplyInplace(res1.P, res1.P, res2.T);
            GmpInteger.AddInplace(res1.T, res1.T, res1.P);
            res1.P.Dispose();
            res1.Q.Dispose();
            return new PQT
            {
                P = p,
                Q = q,
                T = res1.T,
            };
        }
    }
}

public ref struct PQT
{
    public GmpInteger P;
    public GmpInteger Q;
    public GmpInteger T;

    public readonly void Dispose()
    {
        P?.Dispose();
        Q?.Dispose();
        T?.Dispose();
    }
}

Technical notes

Why choosing struct in class design instead of raw memory IntPtr design?

  • (1) Struct in class design:

    class GmpInteger
    {
      public readonly Mpz_t Raw;
    
      public unsafe void DoWork()
      {
          fixed (Mpz_t* ptr = &Raw)
          {
              GmpLib.__dowork((IntPtr)ptr);
          }
      }
    }
    
    struct Mpz_t
    {
      public int A, B;
      public IntPtr Limbs;
    }
  • (2) Raw memory IntPtr design:

    class GmpInteger : IDisposable
    {
      public readonly IntPtr Raw;
    
      public unsafe GmpInteger()
      {
          Raw = Marshal.AllocHGlobal(sizeof(Mpz_t));
      }
    
      public void DoWork()
      {
          GmpLib.__dowork(Raw);
      }
    
      public void Dispose()
      {
          Marshal.FreeHGlobal(Raw);
      }
    }

    Here is some benchmark I tested for both DoWork senario and initialize-dispose senario:

    Details:

    • init & dispose combines following actions:
      • allocating struct memory
      • calling mpz_init
      • calling mpz_free
      • free the memory
      • Measure the operations-per-seconds, higher ops is better
    • dowork contains following actions:
      • create a GmpFloat to 1.5 with precision=1000(v = 1, a = 1.5)
      • Calling MultiplyInplace(v *= a) 10*1024*1024 times
      • Measure the duration, lower is better

    Here is the tested results in my laptop:

    case/senario init & dispose dowork
    Struct in class 82,055,792 ops 1237ms
    Raw memory IntPtr 15,543,619 ops 1134ms

    As you can see, raw memory IntPtr design will benifits ~8.33% faster in dowork senario above, but struct in class design will be 5.2x faster in init & dispose senario.

    Finally I choosed the struct in class design, here is some existing Raw memory IntPtr design work if you also wants to check or test:

    Struct in class performance results environment:

    In the future, Raw memory IntPtr design can be pick-up if a handy, good performance memory allocator was found.

About

A modern .NET library that can PInvoke to gmp and mpfr, that enable both high performance and best .NET convenience.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages