高精度浮動小数点高精度浮動小数点; High Precision Float
Boost multiprecisionをC++/CLIで.NETライブラリにする

概要
高精度浮動小数点とは有効桁数が長く高精度の計算を行える浮動小数点型である。
.NETの組み込み型であるdoubleは15-16桁の有効桁数しかなく、微積分や非線形フィッティングなどで精度が求められたときこの有効桁数では満足行く計算ができない。

高精度浮動小数点にはboostのmultiprecisionを活用することにする。ただC/C++で書かれているので、他のC#で書かれたプログラムとの連携がしづらい。
そこで.NETライブラリとしてラッパーする。

手順は以下のようになる。
1.C++/CLIでboostのmultiprecisionをアンマネージリソースとして管理し、演算を行うクラスを作る
2.作ったクラスをライブラリとしてパーケージ化
3.C#で作ったクラスの単体テストを行う。

要件は以下のようになる。
1.コンストラクタと内部値を変えないメソッド・プロパティのみで構成し、Stringクラスのように参照型であることを意識せず扱えるようにする
2.アンマネージリソースはDisposeを呼ばなくとも自動で解放してくれるようにする
  (全ての変数に対しusing句を使って宣言するのはあまりにも可読性が損なわれるので)
3..NETの組み込み型との連携が円滑に行える
4.C++/CLIの例外を.NET側に出さない
5.NullオブジェクトはNaNとして扱う

C++/CLIでboostのmultiprecisionをアンマネージリソースとして管理し、演算を行うクラスを作る
BoostMultiPrecisionNET.h

#pragma once

#include <boost/multiprecision/cpp_bin_float.hpp>
using namespace System;

namespace BoostMultiPrecisionNETCore{
    using namespace boost::multiprecision;

    typedef number<backends::cpp_bin_float<128, backends::digit_base_2, void, boost::int16_t, -2046, 2047>, et_off>  cpp_bin_float_128;
    typedef number<backends::cpp_bin_float<256, backends::digit_base_2, void, boost::int16_t, -4094, 4095>, et_off>  cpp_bin_float_256;
    typedef number<backends::cpp_bin_float<384, backends::digit_base_2, void, boost::int16_t, -8190, 8191>, et_off>  cpp_bin_float_384;
    typedef number<backends::cpp_bin_float<512, backends::digit_base_2, void, boost::int16_t, -8190, 8191>, et_off>  cpp_bin_float_512;
    typedef number<backends::cpp_bin_float<640, backends::digit_base_2, void, boost::int16_t, -16382, 16383>, et_off>  cpp_bin_float_640;
    typedef number<backends::cpp_bin_float<768, backends::digit_base_2, void, boost::int16_t, -16382, 16383>, et_off>  cpp_bin_float_768;
    typedef number<backends::cpp_bin_float<896, backends::digit_base_2, void, boost::int16_t, -16382, 16383>, et_off>  cpp_bin_float_896;
    typedef number<backends::cpp_bin_float<1024, backends::digit_base_2, void, boost::int16_t, -16382, 16383>, et_off>  cpp_bin_float_1024;
}

namespace BoostMultiPrecisionNET{
    using namespace BoostMultiPrecisionNETCore;

    public ref class Precision256 : public IFormattable{
        cpp_bin_float_256 *m;

        public:
        Precision256();
        Precision256(const Precision256 ^v);
        Precision256(Int32 v);
        Precision256(Int64 v);
        Precision256(Single v);
        Precision256(Double v);
        Precision256(Decimal v);
        Precision256(String ^str);
        ~Precision256();
        !Precision256();

        static operator Precision256 ^ (Int32 v);
        static operator Precision256 ^ (Int64 v);
        static operator Precision256 ^ (Single v);
        static operator Precision256 ^ (Double v);
        static operator Precision256 ^ (Decimal v);
        static operator Precision256 ^ (String^ v);

        static explicit operator Double (const Precision256 ^v);

        static Precision256^ operator+(const Precision256 ^v);
        static Precision256^ operator-(const Precision256 ^v);

        static Precision256^ operator+(const Precision256 ^v1, const Precision256 ^v2);
        static Precision256^ operator-(const Precision256 ^v1, const Precision256 ^v2);
        static Precision256^ operator*(const Precision256 ^v1, const Precision256 ^v2);
        static Precision256^ operator/(const Precision256 ^v1, const Precision256 ^v2);
        static Precision256^ operator%(const Precision256 ^v1, const Precision256 ^v2);

        static bool operator==(const Precision256 ^v1, const Precision256 ^v2);
        static bool operator!=(const Precision256 ^v1, const Precision256 ^v2);
        static bool operator<(const Precision256 ^v1, const Precision256 ^v2);
        static bool operator<=(const Precision256 ^v1, const Precision256 ^v2);
        static bool operator>(const Precision256 ^v1, const Precision256 ^v2);
        static bool operator>=(const Precision256 ^v1, const Precision256 ^v2);

        static Precision256^ Abs(const Precision256 ^x);
        static Precision256^ Sqrt(const Precision256 ^x);
        static Precision256^ Floor(const Precision256 ^x);
        static Precision256^ Ceiling(const Precision256 ^x);
        static Precision256^ Truncate(const Precision256 ^x);
        static Precision256^ Round(const Precision256 ^x);
        static Precision256^ Exp(const Precision256 ^x);
        static Precision256^ Log(const Precision256 ^x);
        static Precision256^ Log10(const Precision256 ^x);
        static Precision256^ Cos(const Precision256 ^x);
        static Precision256^ Sin(const Precision256 ^x);
        static Precision256^ Tan(const Precision256 ^x);
        static Precision256^ Acos(const Precision256 ^x);
        static Precision256^ Asin(const Precision256 ^x);
        static Precision256^ Atan(const Precision256 ^x);
        static Precision256^ Cosh(const Precision256 ^x);
        static Precision256^ Sinh(const Precision256 ^x);
        static Precision256^ Tanh(const Precision256 ^x);
        static Precision256^ Pow(const Precision256 ^x, const Precision256 ^y);
        static Precision256^ Pow2(const Precision256 ^x, Int32 y);
        static Precision256^ Atan2(const Precision256 ^x, const Precision256 ^y);
        static Precision256^ Sign(const Precision256 ^x);

        static Boolean IsNaN(const Precision256 ^x);
        static Boolean IsInfinity(const Precision256 ^x);
        static Boolean IsPositiveInfinity(const Precision256 ^x);
        static Boolean IsNegativeInfinity(const Precision256 ^x);
        static Boolean IsNull(const Precision256 ^x);

        static property Precision256^ Zero{
            Precision256^ get();
        }

        static property Precision256^ PositiveInfinity{
            Precision256^ get();
        }

        static property Precision256^ NegativeInfinity{
            Precision256^ get();
        }

        static property Precision256^ MaxValue{
            Precision256^ get();
        }

        static property Precision256^ MinValue{
            Precision256^ get();
        }

        static property Precision256^ Epsilon{
            Precision256^ get();
        }

        static property Precision256^ NaN{
            Precision256^ get();
        }

        static property Precision256^ E{
            Precision256^ get();
        }

        static property Precision256^ PI{
            Precision256^ get();
        }

        static property Int32 SignificantFigures{
            Int32 get();
        }

        bool Equals(Object^ obj) override;

        String^ ToString() override;
        String^ ToString(String^ format);
        virtual String^ ToString(String^ format, IFormatProvider^ format_provider);

        private:
        Precision256(const cpp_bin_float_256 &m);
        void SetValue(const cpp_bin_float_256 &m);
    };

    //他の高精度浮動小数点クラスも同様
}

Precision256.cpp

#include "stdafx.h"
#include "BoostMultiPrecisionNET.h"

using namespace BoostMultiPrecisionNET;
using namespace boost::multiprecision;
using namespace Runtime::InteropServices;

Precision256::Precision256(){
    this->SetValue(cpp_bin_float_256(0));
}

Precision256::Precision256(const Precision256^ v){
    if(IsNull(v)){
        throw gcnew ArgumentNullException();
    }

    this->SetValue(*(v->m));
}

Precision256::Precision256(Int32 v){
    this->SetValue(cpp_bin_float_256(v));
}

Precision256::Precision256(Int64 v){
    this->SetValue(cpp_bin_float_256(v));
}

Precision256::Precision256(Single v){
    this->SetValue(cpp_bin_float_256(v));
}

Precision256::Precision256(Decimal v) : Precision256(v.ToString("E29")){}

Precision256::Precision256(Double v){
    this->SetValue(cpp_bin_float_256(v));
}

Precision256::Precision256(String ^str){
    if((Object^)str == nullptr){
        throw gcnew ArgumentNullException();
    }

    const char *chars = nullptr;
    cpp_bin_float_256 *m = nullptr;

    try{
        chars = (const char*)(Marshal::StringToHGlobalAnsi(str)).ToPointer();
        m = new cpp_bin_float_256(chars);
        this->SetValue(*m);
    } catch(OutOfMemoryException^){
        throw;
    } catch(...){
        throw gcnew FormatException();
    } finally{
        if(chars != nullptr){
            Marshal::FreeHGlobal((IntPtr)(void*)chars);
        }
        if(m != nullptr){
            delete m;
        }
    }
}

Precision256::Precision256(const cpp_bin_float_256 &m){
    this->SetValue(m);
}

void Precision256::SetValue(const cpp_bin_float_256 &m){
    static int constract_count = 0;

    constract_count++;
    if(!(constract_count & 0xFFFF)){
        GC::Collect();
        constract_count = 0;
    }

    try{
        this->m = new cpp_bin_float_256(m);
    } catch(...){
        throw gcnew OutOfMemoryException();
    }
}

Precision256::operator Precision256 ^ (Int32 v){
    return gcnew Precision256(v);
}

Precision256::operator Precision256 ^ (Int64 v){
    return gcnew Precision256(v);
}

Precision256::operator Precision256 ^ (Single v){
    return gcnew Precision256(v);
}

Precision256::operator Precision256 ^ (Double v){
    return gcnew Precision256(v);
}

Precision256::operator Precision256 ^ (Decimal v){
    return gcnew Precision256(v);
}

Precision256::operator Precision256 ^ (String^ v){
    return gcnew Precision256(v);
}

Precision256::operator Double(const Precision256^ v){
    if(IsNull(v)){
        return std::numeric_limits<double>::quiet_NaN();
    }

    return (Double)(*v->m);
}

Precision256^ Precision256::operator+(const Precision256 ^v){
    if(IsNull(v)){
        return NaN;
    }

    return gcnew Precision256(*(v->m));
}

Precision256^ Precision256::operator-(const Precision256 ^v){
    if(IsNull(v)){
        return NaN;
    }

    return gcnew Precision256(-(*(v->m)));
}

Precision256^ Precision256::operator+(const Precision256 ^v1, const Precision256 ^v2){
    if(IsNull(v1) || IsNull(v2)){
        return NaN;
    }

    return gcnew Precision256(*(v1->m) + *(v2->m));
}

Precision256^ Precision256::operator-(const Precision256 ^v1, const Precision256 ^v2){
    if(IsNull(v1) || IsNull(v2)){
        return NaN;
    }

    return gcnew Precision256(*(v1->m) - *(v2->m));
}

Precision256^ Precision256::operator*(const Precision256 ^v1, const Precision256 ^v2){
    if(IsNull(v1) || IsNull(v2)){
        return NaN;
    }

    return gcnew Precision256(*(v1->m) * *(v2->m));
}

Precision256^ Precision256::operator/(const Precision256 ^v1, const Precision256 ^v2){
    if(IsNull(v1) || IsNull(v2)){
        return NaN;
    }

    return gcnew Precision256(*(v1->m) / *(v2->m));
}

Precision256^ Precision256::operator%(const Precision256 ^v1, const Precision256 ^v2){
    if(IsNull(v1) || IsNull(v2)){
        return NaN;
    }

    return gcnew Precision256(fmod(*(v1->m), *(v2->m)));
}

bool Precision256::operator==(const Precision256 ^v1, const Precision256 ^v2){
    if(ReferenceEquals((Object^)v1, (Object^)v2)){
        return true;
    }
    if(IsNull(v1) || IsNull(v2)){
        return false;
    }

    return *(v1->m) == *(v2->m);
}

bool Precision256::operator!=(const Precision256 ^v1, const Precision256 ^v2){
    return !(v1 == v2);
}

bool Precision256::operator<(const Precision256 ^v1, const Precision256 ^v2){
    if(IsNull(v1) || IsNull(v2)){
        return false;
    }

    return *(v1->m) < *(v2->m);
}

bool Precision256::operator<=(const Precision256 ^v1, const Precision256 ^v2){
    if(IsNull(v1) || IsNull(v2)){
        return false;
    }

    return *(v1->m) <= *(v2->m);
}

bool Precision256::operator>(const Precision256 ^v1, const Precision256 ^v2){
    if(IsNull(v1) || IsNull(v2)){
        return false;
    }

    return *(v1->m) > *(v2->m);
}

bool Precision256::operator>=(const Precision256 ^v1, const Precision256 ^v2){
    if(IsNull(v1) || IsNull(v2)){
        return false;
    }

    return *(v1->m) >= *(v2->m);
}

Precision256^ Precision256::Abs(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(abs(*(x->m)));
}

Precision256^ Precision256::Sqrt(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(sqrt(*(x->m)));
}

Precision256^ Precision256::Floor(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(floor(*(x->m)));
}

Precision256^ Precision256::Ceiling(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(ceil(*(x->m)));
}

Precision256^ Precision256::Truncate(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(trunc(*(x->m)));
}

Precision256^ Precision256::Round(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(round(*(x->m)));
}

Precision256^ Precision256::Exp(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(exp(*(x->m)));
}

Precision256^ Precision256::Log(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    const cpp_bin_float_256 &m = *(x->m);

    if(m > 0){
        return gcnew Precision256(log(m));
    }
    if(m == 0){
        return NegativeInfinity;
    }

    return NaN;
}

Precision256^ Precision256::Log10(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    const cpp_bin_float_256 &m = *(x->m);

    if(m > 0){
        return gcnew Precision256(log10(m));
    }
    if(m == 0){
        return NegativeInfinity;
    }

    return NaN;
}

Precision256^ Precision256::Cos(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(cos(*(x->m)));
}

Precision256^ Precision256::Sin(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(sin(*(x->m)));
}

Precision256^ Precision256::Tan(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(tan(*(x->m)));
}

Precision256^ Precision256::Acos(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(acos(*(x->m)));
}

Precision256^ Precision256::Asin(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(asin(*(x->m)));
}

Precision256^ Precision256::Atan(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(atan(*(x->m)));
}

Precision256^ Precision256::Cosh(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(cosh(*(x->m)));
}

Precision256^ Precision256::Sinh(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(sinh(*(x->m)));
}

Precision256^ Precision256::Tanh(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(tanh(*(x->m)));
}

Precision256^ Precision256::Pow(const Precision256 ^x, const Precision256 ^y){
    if(IsNull(x) || IsNull(y)){
        return NaN;
    }

    return gcnew Precision256(pow(*(x->m), *(y->m)));
}

Precision256^ Precision256::Pow2(const Precision256 ^x, Int32 y){
    if(IsNull(x)){
        return NaN;
    }

    return gcnew Precision256(ldexp(*(x->m), y));
}

Precision256^ Precision256::Atan2(const Precision256 ^x, const Precision256 ^y){
    if(IsNull(x) || IsNull(y)){
        return NaN;
    }

    return gcnew Precision256(atan2(*(x->m), *(y->m)));
}

Precision256^ Precision256::Sign(const Precision256 ^x){
    if(IsNull(x)){
        return NaN;
    }

    const cpp_bin_float_256 &m = *(x->m);

    if(isnan(m)){
        return NaN;
    }

    return gcnew Precision256(m > 0 ? +1 : m < 0 ? -1 : 0);
}

Boolean Precision256::IsNaN(const Precision256 ^x){
    if(IsNull(x)){
        return true;
    }

    return isnan(*(x->m));
}

Boolean Precision256::IsInfinity(const Precision256 ^x){
    if(IsNull(x)){
        return false;
    }

    return !isnan(*(x->m)) && !isfinite(*(x->m));
}

Boolean Precision256::IsPositiveInfinity(const Precision256 ^x){
    if(IsNull(x)){
        return false;
    }

    return !isnan(*(x->m)) && !isfinite(*(x->m)) && (*(x->m) > 0);
}

Boolean Precision256::IsNegativeInfinity(const Precision256 ^x){
    if(IsNull(x)){
        return false;
    }

    return !isnan(*(x->m)) && !isfinite(*(x->m)) && (*(x->m) < 0);
}

Boolean Precision256::IsNull(const Precision256 ^x){
    return (Object^)x == nullptr || x->m == nullptr;
}

Precision256^ Precision256::Zero::get(){
    static bool is_initialized = false;
    static cpp_bin_float_256 val;

    if(!is_initialized){
        val = cpp_bin_float_256(0);
        is_initialized = true;
    }

    return gcnew Precision256(val);
}

Precision256^ Precision256::PositiveInfinity::get(){
    static bool is_initialized = false;
    static cpp_bin_float_256 val;

    if(!is_initialized){
        val = std::numeric_limits<cpp_bin_float_256>::infinity();
        is_initialized = true;
    }

    return gcnew Precision256(val);
}

Precision256^ Precision256::NegativeInfinity::get(){
    static bool is_initialized = false;
    static cpp_bin_float_256 val;

    if(!is_initialized){
        val = -std::numeric_limits<cpp_bin_float_256>::infinity();
        is_initialized = true;
    }

    return gcnew Precision256(val);
}

Precision256^ Precision256::MaxValue::get(){
    static bool is_initialized = false;
    static cpp_bin_float_256 val;

    if(!is_initialized){
        val = std::numeric_limits<cpp_bin_float_256>::max();
        is_initialized = true;
    }

    return gcnew Precision256(val);
}

Precision256^ Precision256::MinValue::get(){
    static bool is_initialized = false;
    static cpp_bin_float_256 val;

    if(!is_initialized){
        val = -std::numeric_limits<cpp_bin_float_256>::max();
        is_initialized = true;
    }

    return gcnew Precision256(val);
}

Precision256^ Precision256::Epsilon::get(){
    static bool is_initialized = false;
    static cpp_bin_float_256 val;

    if(!is_initialized){
        val = std::numeric_limits<cpp_bin_float_256>::min();
        is_initialized = true;
    }

    return gcnew Precision256(val);
}

Precision256^ Precision256::NaN::get(){
    static bool is_initialized = false;
    static cpp_bin_float_256 val;

    if(!is_initialized){
        val = std::numeric_limits<cpp_bin_float_256>::quiet_NaN();
        is_initialized = true;
    }

    return gcnew Precision256(val);
}

Precision256^ Precision256::E::get(){
    static bool is_initialized = false;
    static cpp_bin_float_256 val;

    if(!is_initialized){
        const char* string_val = "2."
            "7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274"
            "2746639193200305992181741359662904357290033429526059563073813232862794349076323382988075319525101901";

        val = cpp_bin_float_256(string_val);
        is_initialized = true;
    }

    return gcnew Precision256(val);
}

Precision256^ Precision256::PI::get(){
    static bool is_initialized = false;
    static cpp_bin_float_256 val;

    if(!is_initialized){
        const char* string_val = "3."
            "1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"
            "8214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196";

        val = cpp_bin_float_256(string_val);
        is_initialized = true;
    }

    return gcnew Precision256(val);
}

Int32 Precision256::SignificantFigures::get(){
    return 256 * 30103 / 100000;
}

bool Precision256::Equals(Object^ obj){
    Precision256^ v = dynamic_cast<Precision256^>(obj);

    return (Object^)v != nullptr ? v == this : false;
}

String^ Precision256::ToString(){
    if(IsNull(this)){
        throw gcnew NullReferenceException();
    }

    std::stringstream strstream;
    strstream << (*m);

    return gcnew String(strstream.str().c_str());
}

String^ Precision256::ToString(String^ format){
    if(IsNull(this)){
        throw gcnew NullReferenceException();
    }

    if(String::IsNullOrEmpty(format)){
        return this->ToString();
    }

    wchar_t format_type = format[0];
    String^ precision_type = format->Substring(1);

    Int32 precision_length;

    if(String::IsNullOrEmpty(precision_type)){
        precision_length = 5;
    }
    else if(!Int32::TryParse(precision_type, precision_length)){
        throw gcnew FormatException();
    }

    if(precision_length > SignificantFigures){
        precision_length = SignificantFigures;
    }

    std::stringstream strstream;

    switch(format_type){
        case L'E':
        case L'e':
            strstream << std::scientific;
            break;
        case L'F':
        case L'f':
            strstream << std::fixed;
            break;
        default:
            throw gcnew FormatException();
    }

    strstream << std::setprecision(precision_length) << (*m);

    return gcnew String(strstream.str().c_str());
}

String^ Precision256::ToString(String^ format, IFormatProvider^ format_provider){
    return this->ToString(format);
}

Precision256::~Precision256(){
    this->!Precision256();
}

Precision256::!Precision256(){
    if(this->m != nullptr){
        delete this->m;
        this->m = nullptr;
    }
}

C#で作ったクラスの単体テストを行う。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using BoostMultiPrecisionNET;

namespace MultiPrecision.Tests {
    [TestClass]
    public class Precision256Tests {
        [TestMethod]
        public void ConstractorTest() {
            Precision256 v1 = new Precision256();
            Precision256 v2 = new Precision256((int)1);
            Precision256 v3 = new Precision256((long)2);
            Precision256 v4 = new Precision256((float)3.4);
            Precision256 v5 = new Precision256((double)5.67);
            Precision256 v6 = new Precision256((decimal)8.90m);
            Precision256 v7 = new Precision256("1.23456");
            Precision256 v8 = new Precision256(v7);
            Precision256 v9 = new Precision256("1.23456");

            Assert.AreEqual((double)v1, 0, 1e-12);
            Assert.AreEqual((double)v2, 1, 1e-12);
            Assert.AreEqual((double)v3, 2, 1e-12);
            Assert.AreEqual((double)v4, 3.4, 1e-6);
            Assert.AreEqual((double)v5, 5.67, 1e-12);
            Assert.AreEqual((double)v6, 8.9, 1e-12);
            Assert.AreEqual((double)v7, 1.23456, 1e-12);
            Assert.AreEqual((double)v8, 1.23456, 1e-12);
            Assert.AreEqual(v7, v8);
            Assert.AreEqual(v7, v9);
            Assert.AreEqual(v7.Equals(null), false);
        }

        [TestMethod]
        public void CopyTest() {
            Precision256 v1 = 3.4;
            Precision256 v2 = v1;

            v2 = 5.6;

            Assert.AreEqual(v1, 3.4);
            Assert.AreEqual(v2, 5.6);
        }

        [TestMethod]
        public void CastTest() {
            Precision256 v1 = (int)1;
            Precision256 v2 = (long)2;
            Precision256 v3 = (float)3.4;
            Precision256 v4 = (double)5.67;
            Precision256 v5 = (decimal)8.90m;
            Precision256 v6 = (Precision256)"1.23456";

            Assert.AreEqual((double)v1, 1, 1e-12);
            Assert.AreEqual((double)v2, 2, 1e-12);
            Assert.AreEqual((double)v3, 3.4, 1e-6);
            Assert.AreEqual((double)v4, 5.67, 1e-12);
            Assert.AreEqual((double)v5, 8.9, 1e-12);
            Assert.AreEqual((double)v6, 1.23456, 1e-12);
        }

        [TestMethod]
        public void OperatorTest() {
            Precision256 v1 = (Precision256)"1.1";
            Precision256 v2 = (Precision256)"1.2";
            Precision256 v3 = (Precision256)"1.2";
            Precision256 v4 = (Precision256)"1.3";

            Assert.AreEqual((double)(+v1), +1.1, 1e-12);
            Assert.AreEqual((double)(-v1), -1.1, 1e-12);

            Assert.AreEqual((double)(v1 + v2), 1.1 + 1.2, 1e-12);
            Assert.AreEqual((double)(v1 - v2), 1.1 - 1.2, 1e-12);
            Assert.AreEqual((double)(v1 * v2), 1.1 * 1.2, 1e-12);
            Assert.AreEqual((double)(v1 / v2), 1.1 / 1.2, 1e-12);
            Assert.AreEqual((double)(v1 % v2), 1.1 % 1.2, 1e-12);

            Assert.AreEqual(v1 < v2, true);
            Assert.AreEqual(v2 < v3, false);
            Assert.AreEqual(v3 < v4, true);

            Assert.AreEqual(v1 < v2, true);
            Assert.AreEqual(v2 <= v3, true);
            Assert.AreEqual(v3 < v4, true);

            Assert.AreEqual(v1 > v2, false);
            Assert.AreEqual(v2 > v3, false);
            Assert.AreEqual(v3 > v4, false);

            Assert.AreEqual(v1 > v2, false);
            Assert.AreEqual(v2 >= v3, true);
            Assert.AreEqual(v3 > v4, false);

            Assert.AreEqual(v2 == v3, true);
            Assert.AreEqual(v2 != v3, false);

            v1 += v2;
            Assert.AreEqual((double)(v1), 1.1 + 1.2, 1e-12);

            v1 -= v2;
            Assert.AreEqual((double)(v1), 1.1, 1e-12);

            v1 *= v2;
            Assert.AreEqual((double)(v1), 1.1 * 1.2, 1e-12);

            v1 /= v2;
            Assert.AreEqual((double)(v1), 1.1, 1e-12);

            v1 %= v2;
            Assert.AreEqual((double)(v1), 1.1 % 1.2, 1e-12);
        }

        [TestMethod]
        public void ConstantTest() {
            Assert.AreEqual(Precision256.Zero == 0, true);
            Assert.AreEqual((double)Precision256.E, Math.E);
            Assert.AreEqual((double)Precision256.PI, Math.PI);

            Assert.AreEqual(double.IsPositiveInfinity((double)Precision256.PositiveInfinity), true);
            Assert.AreEqual(double.IsNegativeInfinity((double)Precision256.NegativeInfinity), true);

            Assert.AreEqual(double.IsNaN((double)Precision256.PositiveInfinity), false);
            Assert.AreEqual(double.IsNaN((double)Precision256.NegativeInfinity), false);
            Assert.AreEqual(double.IsNaN((double)Precision256.NaN), true);

            Assert.AreEqual(double.IsInfinity((double)Precision256.PositiveInfinity), true);
            Assert.AreEqual(double.IsInfinity((double)Precision256.NegativeInfinity), true);
            Assert.AreEqual(double.IsInfinity((double)Precision256.NaN), false);
        }

        [TestMethod]
        public void MathFunctionTest() {
            for(double v1 = -5; v1 <= 5; v1 += 0.25) {
                Precision256 v2 = v1;

                Assert.AreEqual(Math.Abs(v1), (double)Precision256.Abs(v2), $"abs({v1})");
                Assert.AreEqual(Math.Sign(v1), (double)Precision256.Sign(v2), $"sign({v1})");

                Assert.AreEqual(Math.Sin(v1), (double)Precision256.Sin(v2), $"sin({v1})");
                Assert.AreEqual(Math.Cos(v1), (double)Precision256.Cos(v2), $"cos({v1})");
                Assert.AreEqual(Math.Tan(v1), (double)Precision256.Tan(v2), $"tan({v1})");

                Assert.AreEqual(Math.Sinh(v1), (double)Precision256.Sinh(v2), $"sinh({v1})");
                Assert.AreEqual(Math.Cosh(v1), (double)Precision256.Cosh(v2), $"cosh({v1})");
                Assert.AreEqual(Math.Tanh(v1), (double)Precision256.Tanh(v2), $"tanh({v1})");

                Assert.AreEqual(Math.Asin(v1), (double)Precision256.Asin(v2), $"asin({v1})");
                Assert.AreEqual(Math.Acos(v1), (double)Precision256.Acos(v2), $"acos({v1})");
                Assert.AreEqual(Math.Atan(v1), (double)Precision256.Atan(v2), $"atan({v1})");
                Assert.AreEqual(Math.Atan2(v1, 1.0), (double)Precision256.Atan2(v2, 1.0), $"atan2({v1})");

                Assert.AreEqual(Math.Floor(v1), (double)Precision256.Floor(v2), $"Floor({v1})");
                Assert.AreEqual(Math.Ceiling(v1), (double)Precision256.Ceiling(v2), $"ceil({v1})");
                Assert.AreEqual(Math.Truncate(v1), (double)Precision256.Truncate(v2), $"truncate({v1})");
                
                //端数が0.5であるときの丸め方向が異なるため除外
                //Assert.AreEqual(Math.Round(v1), (double)Precision256.Round(v2), $"round({v1})");

                Assert.AreEqual(Math.Exp(v1), (double)Precision256.Exp(v2), $"exp({v1})");

                Assert.AreEqual(Math.Log(v1), (double)Precision256.Log(v2), $"log({v1})");
                Assert.AreEqual(Math.Log10(v1), (double)Precision256.Log10(v2), $"log10({v1})");

                Assert.AreEqual(Math.Pow(v1, 2), (double)Precision256.Pow(v2, 2), $"pow({v1}, 2)");
                Assert.AreEqual(v1 * 4, (double)Precision256.Pow2(v2, 2), $"pow2({v1}, 2)");

                Assert.AreEqual(Math.Sqrt(v1), (double)Precision256.Sqrt(v2), $"aqrt({v1})");
            }
        }

        [TestMethod]
        public void InvalidTest() {
            Precision256 v1 = (Precision256)"1.1";
            Precision256 v2 = null;
            Precision256 v3 = 1.23;

            v3.Dispose();

            Assert.AreEqual((double)v2, double.NaN);
            Assert.AreEqual((double)(+v2), double.NaN);
            Assert.AreEqual((double)(-v2), double.NaN);

            Assert.AreEqual((double)v3, double.NaN);
            Assert.AreEqual((double)(+v3), double.NaN);
            Assert.AreEqual((double)(-v3), double.NaN);

            Assert.AreEqual((double)(v1 + v2), double.NaN);
            Assert.AreEqual((double)(v1 - v2), double.NaN);
            Assert.AreEqual((double)(v1 * v2), double.NaN);
            Assert.AreEqual((double)(v1 / v2), double.NaN);
            Assert.AreEqual((double)(v1 % v2), double.NaN);

            Assert.AreEqual(v1 < v2, false);
            Assert.AreEqual(v1 <= v2, false);
            Assert.AreEqual(v1 > v2, false);
            Assert.AreEqual(v1 >= v2, false);
            Assert.AreEqual(v1 > v2, false);
            Assert.AreEqual(v1 >= v2, false);
            Assert.AreEqual(v1 != v2, true);
            Assert.AreEqual(v1 == v2, false);

            Assert.AreEqual((double)Precision256.Abs(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Sign(v2), double.NaN);

            Assert.AreEqual((double)Precision256.Sin(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Cos(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Tan(v2), double.NaN);

            Assert.AreEqual((double)Precision256.Sinh(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Cosh(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Tanh(v2), double.NaN);

            Assert.AreEqual((double)Precision256.Asin(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Acos(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Atan(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Atan2(v2, v1), double.NaN);

            Assert.AreEqual((double)Precision256.Floor(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Ceiling(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Truncate(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Round(v2), double.NaN);

            Assert.AreEqual((double)Precision256.Exp(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Log(v2), double.NaN);
            Assert.AreEqual((double)Precision256.Log10(v2), double.NaN);

            Assert.AreEqual((double)Precision256.Pow(v2, v1), double.NaN);
            Assert.AreEqual((double)Precision256.Pow2(v2, 2), double.NaN);
            Assert.AreEqual((double)Precision256.Sqrt(v2), double.NaN);
        }

        [TestMethod]
        public void ToStringTest() {
            string str = "3.14159265358979323846264338327950288419716939937510582097e-10";

            Precision256 v1 = (Precision256)str;

            Assert.AreEqual(v1.ToString(), "3.14159e-10");
            Assert.AreEqual(v1.ToString("E15"), "3.141592653589793e-10");
            Assert.AreEqual(v1.ToString("F15"), "0.000000000314159");
            Assert.AreEqual($"{v1:E15}", "3.141592653589793e-10");
            Assert.AreEqual($"{v1:F15}", "0.000000000314159");
        }
    }
}
※構成マネージャーのプラットフォームがx64であるときは、以上のテスト項目がテストエクスプローラーの項目に挙げられず、テストが実行できないので注意

メモメモ
高精度浮動小数点高精度浮動小数点