dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.58k stars 4.55k forks source link

[API Proposal]: Add Decimal32, Decimal64, and Decimal128 from the IEEE 754-2019 standard. #81376

Open dakersnar opened 1 year ago

dakersnar commented 1 year ago

Background and motivation

This is a restructuring of the original API proposal here: https://github.com/dotnet/runtime/issues/69777

The existing System.Decimal type does not conform to the IEEE standard for decimal floating-point types. We have no plans to rehash System.Decimal, but adding Decimal32, Decimal64, and Decimal128 in addition would allow users to work within a standard that is being adopted by other languages and frameworks. There is also a future where hardware support for these types is more widely adopted, and having IEEE-conforming types will allow us to users to take advantage of performance gains.

For reference, here is a chart comparing the existing System.Decimal type to the IEEE types: Decimal Comparison Chart System.Decimal Decimal128 Decimal64 Decimal32
Size 128 bits 128 bits 64 bits 32 bits
Max value ~7.9e28 ~9.9e6144 ~9.9e384 ~9.9e96
Min value ~-7.9e28 ~-9.9e6144 ~-9.9e384 ~-9.9e96
Smallest magnitude non-zero value 1e-28 1e-6176 1e-398 1e-101
Decimal digits of precision 28 34 16 7

Alternative Designs

Risks/Considerations

API Proposal

Decimal32 is shown, but the API surfaces for Decimal64 and Decimal128 are nearly identical, bar some differences in the conversions (which are noted below). This proposal is for all three types.

namespace System.Numerics
{

    /// <summary>Defines an IEEE 754 floating-point type that is represented in a base-10 format.</summary>
    /// <typeparam name="TSelf">The type that implements the interface.</typeparam>
    public interface IDecimalFloatingPointIeee754<TSelf> // PLATINUM
        : IFloatingPointIeee754<TSelf>
        where TSelf : IDecimalFloatingPointIeee754<TSelf>
    {
        // IEEE Spec 5.3.2
        static abstract TSelf Quantize(TSelf x, TSelf y);
        static abstract TSelf Quantum(TSelf x);

        // IEEE Spec 5.7.3
        static abstract bool SameQuantum(TSelf x, TSelf y);
    }

    //
    // Summary:
    //     Represents a 32-bit IEEE decimal floating-point number
    [StructLayout(LayoutKind.Sequential)]
    public readonly struct Decimal32
        : IComparable<Decimal32>,
          IComparable,
          ISpanFormattable,
          ISpanParsable<Decimal32>,
          IEquatable<Decimal32>,
          IFloatingPoint<Decimal32>,/*PLATINUM: Replace with IDecimalFloatingPointIeee754<Decimal32>,*/
          IMinMaxValue<Decimal32>
    {
        internal readonly uint _value; // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128

        public Decimal32(int significand, int exponent); // NOTE: params are (long, int) for Decimal64 and (Int128, int) for Decimal128

        //
        // Parsing (INumberBase, IParsable, ISpanParsable, other)
        //

        public static Decimal32 Parse(string s);
        public static Decimal32 Parse(string s, NumberStyles style);
        public static Decimal32 Parse(ReadOnlySpan<char> s, IFormatProvider? provider);
        public static Decimal32 Parse(string s, IFormatProvider? provider);
        public static Decimal32 Parse(ReadOnlySpan<char> s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null);
        public static Decimal32 Parse(string s, NumberStyles style, IFormatProvider? provider);
        public static bool TryParse([NotNullWhen(true)] string? s, out Decimal32 result);
        public static bool TryParse(ReadOnlySpan<char> s, out Decimal32 result);
        public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);
        public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);
        public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);
        public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);

        //
        // Misc. Methods (including IComparable, IEquatable, other)
        //

        public int CompareTo(object? obj);
        public int CompareTo(Decimal32 other);
        public override bool Equals([NotNullWhen(true)] object? obj);
        public bool Equals(Decimal32 other);
        public override int GetHashCode();
        // 5.5.2 of the IEEE Spec
        public static uint EncodeDecimal(Decimal32 x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128
        public static Decimal32 DecodeDecimal(uint x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128
        public static uint EncodeBinary(Decimal32 x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128
        public static Decimal32 DecodeBinary(uint x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128

        //
        // Formatting (IFormattable, ISpanFormattable, other)
        //

        public override string ToString();
        public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format);
        public string ToString(IFormatProvider? provider);
        public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider);
        public bool TryFormat(Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan<char> format = default, IFormatProvider? provider = null);

        //
        // Explicit Convert To Decimal32
        // (T -> Decimal32 is lossy)
        //

        public static explicit operator Decimal32(int value); // NOTE: Decimal64 and Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(uint value); // NOTE: Decimal64 and Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(nint value);  // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(nuint value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(long value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(ulong value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(Int128 value);
        public static explicit operator Decimal32(UInt128 value);
        public static explicit operator Decimal32(Half value);
        public static explicit operator Decimal32(float value);
        public static explicit operator Decimal32(double value);
        public static explicit operator Decimal32(decimal value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(Decimal64 value);
        public static explicit operator Decimal32(Decimal128 value);

        //
        // Explicit Convert From Decimal32
        // (Decimal32 -> T is lossy)
        // - Includes a "checked" conversion if T cannot represent infinity and NaN
        //
        public static explicit operator byte(Decimal32 value);
        public static explicit operator checked byte(Decimal32 value);
        public static explicit operator sbyte(Decimal32 value);
        public static explicit operator checked sbyte(Decimal32 value);
        public static explicit operator char(Decimal32 value);
        public static explicit operator checked char(Decimal32 value);
        public static explicit operator short(Decimal32 value);
        public static explicit operator checked short(Decimal32 value);
        public static explicit operator ushort(Decimal32 value);
        public static explicit operator checked ushort(Decimal32 value);
        public static explicit operator int(Decimal32 value);
        public static explicit operator checked int(Decimal32 value);
        public static explicit operator uint(Decimal32 value);
        public static explicit operator checked uint(Decimal32 value);
        public static explicit operator nint(Decimal32 value);        
        public static explicit operator checked nint(Decimal32 value);
        public static explicit operator nuint(Decimal32 value);
        public static explicit operator checked nuint(Decimal32 value);
        public static explicit operator long(Decimal32 value);
        public static explicit operator checked long(Decimal32 value);
        public static explicit operator ulong(Decimal32 value);
        public static explicit operator checked ulong(Decimal32 value);
        public static explicit operator Int128(Decimal32 value);       
        public static explicit operator checked Int128(Decimal32 value);
        public static explicit operator UInt128(Decimal32 value);
        public static explicit operator checked UInt128(Decimal32 value);
        public static explicit operator Half(Decimal32 value);
        public static explicit operator float(Decimal32 value);
        public static explicit operator double(Decimal32 value);
        public static explicit operator decimal(Decimal32 value); // Doesn't have a "checked" for historical reasons

        //
        // Implicit Convert To Decimal32
        // (T -> Decimal32 is not lossy)
        //
        public static implicit operator Decimal32(byte value);
        public static implicit operator Decimal32(sbyte value);
        public static implicit operator Decimal32(char value);
        public static implicit operator Decimal32(short value);
        public static implicit operator Decimal32(ushort value);

        //
        // Implicit Convert From Decimal32
        // (Decimal32 -> T is not lossy)
        //
        public static implicit operator Decimal64(Decimal32 value);
        public static implicit operator Decimal128(Decimal32 value);

        //
        // IAdditionOperators
        //
        public static Decimal32 operator +(Decimal32 left, Decimal32 right);

        //
        // IAdditiveIdentity
        //
        static Decimal32 IAdditiveIdentity<Decimal32, Decimal32>.AdditiveIdentity { get; }

        //
        // IComparisonOperators
        //
        public static bool operator <(Decimal32 left, Decimal32 right);
        public static bool operator >(Decimal32 left, Decimal32 right);
        public static bool operator <=(Decimal32 left, Decimal32 right);
        public static bool operator >=(Decimal32 left, Decimal32 right);

        //
        // IDecimalFloatingPointIeee754
        //
        public static Decimal32 Quantize(Decimal32 x, Decimal32 y);
        public static Decimal32 Quantum(Decimal32 x);
        public static bool SameQuantum(Decimal32 x, Decimal32 y);

        //
        // IDecrementOperators
        //
        public static Decimal32 operator --(Decimal32 value);

        //
        // IDivisionOperators
        //
        public static Decimal32 operator /(Decimal32 left, Decimal32 right);

        //
        // IEqualityOperators
        //
        public static bool operator ==(Decimal32 left, Decimal32 right);
        public static bool operator !=(Decimal32 left, Decimal32 right);

        //
        // IExponentialFunctions
        //
        public static Decimal32 Exp(Decimal32 x); // PLATINUM
        public static Decimal32 ExpM1(Decimal32 x); // PLATINUM
        public static Decimal32 Exp2(Decimal32 x); // PLATINUM
        public static Decimal32 Exp2M1(Decimal32 x); // PLATINUM
        public static Decimal32 Exp10(Decimal32 x); // PLATINUM
        public static Decimal32 Exp10M1(Decimal32 x); // PLATINUM

        //
        // IFloatingPoint
        //
        public static Decimal32 Ceiling(Decimal32 x);
        public static Decimal32 Floor(Decimal32 x);
        public static Decimal32 Round(Decimal32 x);
        public static Decimal32 Round(Decimal32 x, int digits);
        public static Decimal32 Round(Decimal32 x, MidpointRounding mode);
        public static Decimal32 Round(Decimal32 x, int digits, MidpointRounding mode);
        public static Decimal32 Truncate(Decimal32 x);
        int IFloatingPoint<Decimal32>.GetExponentByteCount();
        int IFloatingPoint<Decimal32>.GetExponentShortestBitLength();
        int IFloatingPoint<Decimal32>.GetSignificandBitLength();
        int IFloatingPoint<Decimal32>.GetSignificandByteCount();
        bool IFloatingPoint<Decimal32>.TryWriteExponentBigEndian(Span<byte> destination, out int bytesWritten);
        bool IFloatingPoint<Decimal32>.TryWriteExponentLittleEndian(Span<byte> destination, out int bytesWritten);
        bool IFloatingPoint<Decimal32>.TryWriteSignificandBigEndian(Span<byte> destination, out int bytesWritten);
        bool IFloatingPoint<Decimal32>.TryWriteSignificandLittleEndian(Span<byte> destination, out int bytesWritten);

        //
        // IFloatingPointConstants
        //
        public static Decimal32 E { get; }
        public static Decimal32 Pi { get; }
        public static Decimal32 Tau { get; }

        //
        // IFloatingPointIeee754
        //
        public static Decimal32 Epsilon { get; }
        public static Decimal32 NaN { get; }
        public static Decimal32 NegativeInfinity { get; }
        public static Decimal32 NegativeZero { get; }
        public static Decimal32 PositiveInfinity { get; }
        public static Decimal32 Atan2(Decimal32 y, Decimal32 x); // PLATINUM
        public static Decimal32 Atan2Pi(Decimal32 y, Decimal32 x); // PLATINUM
        public static Decimal32 BitDecrement(Decimal32 x);
        public static Decimal32 BitIncrement(Decimal32 x);
        public static Decimal32 FusedMultiplyAdd(Decimal32 left, Decimal32 right, Decimal32 addend);
        public static Decimal32 Ieee754Remainder(Decimal32 left, Decimal32 right);
        public static int ILogB(Decimal32 x);
        public static Decimal32 Lerp(Decimal32 value1, Decimal32 value2, Decimal32 amount);
        public static Decimal32 ReciprocalEstimate(Decimal32 x);
        public static Decimal32 ReciprocalSqrtEstimate(Decimal32 x);
        public static Decimal32 ScaleB(Decimal32 x, int n);
        // public static Decimal32 Compound(Half x, Decimal32 n); (Already approved in API review but not implemented yet) // PLATINUM

        //
        // IHyperbolicFunctions
        //
        public static Decimal32 Acosh(Decimal32 x); // PLATINUM
        public static Decimal32 Asinh(Decimal32 x); // PLATINUM
        public static Decimal32 Atanh(Decimal32 x); // PLATINUM
        public static Decimal32 Cosh(Decimal32 x); // PLATINUM
        public static Decimal32 Sinh(Decimal32 x); // PLATINUM
        public static Decimal32 Tanh(Decimal32 x); // PLATINUM

        //
        // IIncrementOperators
        //
        public static Decimal32 operator ++(Decimal32 value);

        //
        // ILogarithmicFunctions
        //
        public static Decimal32 Log(Decimal32 x); // PLATINUM
        public static Decimal32 Log(Decimal32 x, Decimal32 newBase); // PLATINUM
        public static Decimal32 Log10(Decimal32 x); // PLATINUM
        public static Decimal32 LogP1(Decimal32 x); // PLATINUM
        public static Decimal32 Log2(Decimal32 x); // PLATINUM
        public static Decimal32 Log2P1(Decimal32 x); // PLATINUM
        public static Decimal32 Log10P1(Decimal32 x); // PLATINUM

        //
        // IMinMaxValue
        //
        public static Decimal32 MaxValue { get; }
        public static Decimal32 MinValue { get; }

        //
        // IModulusOperators
        //
        public static Decimal32 operator %(Decimal32 left, Decimal32 right);

        //
        // IMultiplicativeIdentity
        //
        public static Decimal32 MultiplicativeIdentity { get; }

        //
        // IMultiplyOperators
        //
        public static Decimal32 operator *(Decimal32 left, Decimal32 right);

        //
        // INumber
        //
        public static Decimal32 Clamp(Decimal32 value, Decimal32 min, Decimal32 max);
        public static Decimal32 CopySign(Decimal32 value, Decimal32 sign);
        public static Decimal32 Max(Decimal32 x, Decimal32 y);
        public static Decimal32 MaxNumber(Decimal32 x, Decimal32 y);
        public static Decimal32 Min(Decimal32 x, Decimal32 y);
        public static Decimal32 MinNumber(Decimal32 x, Decimal32 y);
        public static int Sign(Decimal32 value);

        //
        // INumberBase (well defined/commonly used values)
        //
        public static Decimal32 One { get; }
        static int INumberBase<Decimal32>.Radix; // Note: this ideally should be exposed implicitly as it is required by IEEE
        public static Decimal32 Zero { get; }
        public static Decimal32 Abs(Decimal32 value);
        public static Decimal32 CreateChecked<TOther>(TOther value);
        public static Decimal32 CreateSaturating<TOther>(TOther value);
        public static Decimal32 CreateTruncating<TOther>(TOther value);
        static bool INumberBase<Decimal32>.IsCanonical(Decimal32 value); // Note: this ideally should be exposed implicitly as it is required by IEEE
        static bool INumberBase<Decimal32>.IsComplexNumber(Decimal32 value);
        public static bool IsEvenInteger(Decimal32 value);
        public static bool IsFinite(Decimal32 value);
        static bool INumberBase<Decimal32>.IsImaginaryNumber(Decimal32 value);
        public static bool IsInfinity(Decimal32 value);
        public static bool IsInteger(Decimal32 value);
        public static bool IsNaN(Decimal32 value);
        public static bool IsNegative(Decimal32 value);
        public static bool IsNegativeInfinity(Decimal32 value);
        public static bool IsNormal(Decimal32 value);
        public static bool IsOddInteger(Decimal32 value);
        public static bool IsPositive(Decimal32 value);
        public static bool IsPositiveInfinity(Decimal32 value);
        public static bool IsRealNumber(Decimal32 value);
        public static bool IsSubnormal(Decimal32 value);
        static bool INumberBase<Decimal32>.IsZero(Decimal32 value); // Note: this ideally should be exposed implicitly as it is required by IEEE
        public static Decimal32 MaxMagnitude(Decimal32 x, Decimal32 y);
        public static Decimal32 MaxMagnitudeNumber(Decimal32 x, Decimal32 y);
        public static Decimal32 MinMagnitude(Decimal32 x, Decimal32 y);
        public static Decimal32 MinMagnitudeNumber(Decimal32 x, Decimal32 y);
        static bool INumberBase<Decimal32>.TryConvertFromChecked<TOther>(TOther value, out Decimal32 result);
        static bool INumberBase<Decimal32>.TryConvertFromSaturating<TOther>(TOther value, out Decimal32 result);
        static bool INumberBase<Decimal32>.TryConvertFromTruncating<TOther>(TOther value, out Decimal32 result);
        static bool INumberBase<Decimal32>.TryConvertToChecked<TOther>(Decimal32 value, [MaybeNullWhen(false)] out TOther result);
        static bool INumberBase<Decimal32>.TryConvertToSaturating<TOther>(Decimal32 value, [MaybeNullWhen(false)] out TOther result);
        static bool INumberBase<Decimal32>.TryConvertToTruncating<TOther>(Decimal32 value, [MaybeNullWhen(false)] out TOther result);

        //
        // IPowerFunctions
        //
        public static Decimal32 Pow(Decimal32 x, Decimal32 y); // PLATINUM

        //
        // IRootFunctions
        //
        public static Decimal32 Cbrt(Decimal32 x); // PLATINUM
        public static Decimal32 Hypot(Decimal32 x, Decimal32 y); // PLATINUM
        public static Decimal32 RootN(Decimal32 x, int n); // PLATINUM
        public static Decimal32 Sqrt(Decimal32 x);

        //
        // ISignedNumber
        //
        public static Decimal32 NegativeOne { get; }

        //
        // ISubtractionOperators
        //
        public static Decimal32 operator -(Decimal32 left, Decimal32 right);

        //
        // ITrigonometricFunctions
        //
        public static Decimal32 Acos(Decimal32 x); // PLATINUM
        public static Decimal32 AcosPi(Decimal32 x); // PLATINUM
        public static Decimal32 Asin(Decimal32 x); // PLATINUM
        public static Decimal32 AsinPi(Decimal32 x); // PLATINUM
        public static Decimal32 Atan(Decimal32 x); // PLATINUM
        public static Decimal32 AtanPi(Decimal32 x); // PLATINUM
        public static Decimal32 Cos(Decimal32 x); // PLATINUM
        public static Decimal32 CosPi(Decimal32 x); // PLATINUM
        public static Decimal32 Sin(Decimal32 x); // PLATINUM
        public static (Decimal32 Sin, Decimal32 Cos) SinCos(Decimal32 x); // PLATINUM
        public static (Decimal32 SinPi, Decimal32 CosPi) SinCosPi(Decimal32 x); // PLATINUM
        public static Decimal32 SinPi(Decimal32 x); // PLATINUM
        public static Decimal32 Tan(Decimal32 x); // PLATINUM
        public static Decimal32 TanPi(Decimal32 x); // PLATINUM

        //
        // IUnaryNegationOperators
        //
        public static Decimal32 operator -(Decimal32 value);

        //
        // IUnaryPlusOperators
        //
        public static Decimal32 operator +(Decimal32 value);
    }
}
ghost commented 1 year ago

Tagging subscribers to this area: @dotnet/area-system-numerics See info in area-owners.md if you want to be subscribed.

Issue Details
### Background and motivation This is a restructuring of the original API proposal here: https://github.com/dotnet/runtime/issues/69777 The existing `System.Decimal` type does not conform to the IEEE standard for decimal floating-point types. We have no plans to rehash `System.Decimal`, but adding `Decimal32`, `Decimal64`, and `Decimal128` in addition would allow users to work within a standard that is being adopted by other languages and frameworks. There is also a future where hardware support for these types is more widely adopted, and having IEEE-conforming types will allow us to users to take advantage of performance gains. ### Alternative Designs - Notably, a few APIs below have been annotated with `PLATINUM`. These APIs represent high-implementation-cost functionality that is only "recommended" by IEEE. These are APIs that we might want eventually, as implementing all of them will allow these types to inherit from `IFloatingPointIeee754` instead of just `IFloatingPoint`. Given the implementation cost, we are targeting shipping a smaller surface for .NET 8 that does not include the `PLATINUM` APIs. - While this proposal covers all three types, there is an argument for focusing on one of them for .NET 8. The implementation cost of adding all three isn't exactly 3x the cost of adding one, but it isn't completely trivial either. ### Risks - There is a potential risk when it comes to code size, especially for `Decimal128`, as the current lack of hardware support will require the below APIs to be implemented in software. - These must be implemented with future hardware support integration in mind. - There is potential for confusion with these coexisting with `System.Decimal`. We are mitigating this by placing these types in `System.Numerics`. Clear documentation will be required. ### API Proposal `Decimal32` is shown, but the API surfaces for `Decimal64` and `Decimal128` are nearly identical, bar some differences in the conversions (which are noted below). This proposal is for all three types. ```C# namespace System.Numerics { /// Defines an IEEE 754 floating-point type that is represented in a base-10 format. /// The type that implements the interface. public interface IDecimalFloatingPointIeee754 // PLATINUM : IFloatingPointIeee754 where TSelf : IDecimalFloatingPointIeee754 { // IEEE Spec 5.3.2 static abstract TSelf Quantize(TSelf x, TSelf y); static abstract TSelf Quantum(TSelf x); // IEEE Spec 5.7.3 static abstract bool SameQuantum(TSelf x, TSelf y); } // // Summary: // Represents a 32-bit IEEE decimal floating-point number [StructLayout(LayoutKind.Sequential)] public readonly struct Decimal32 : IComparable, IComparable, ISpanFormattable, ISpanParsable, IEquatable, IFloatingPoint,/*PLATINUM: Replace with IDecimalFloatingPointIeee754,*/ IMinMaxValue { internal readonly uint _value; // // Parsing (INumberBase, IParsable, ISpanParsable) // public static Decimal32 Parse(string s); public static Decimal32 Parse(string s, NumberStyles style); public static Decimal32 Parse(ReadOnlySpan s, IFormatProvider? provider); public static Decimal32 Parse(string s, IFormatProvider? provider); public static Decimal32 Parse(ReadOnlySpan s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null); public static Decimal32 Parse(string s, NumberStyles style, IFormatProvider? provider); public static bool TryParse([NotNullWhen(true)] string? s, out Decimal32 result); public static bool TryParse(ReadOnlySpan s, out Decimal32 result); public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result); public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result); public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result); public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result); // // Misc. Methods (including IComparable, IEquatable) // public int CompareTo(object? obj); public int CompareTo(Decimal32 other); public override bool Equals([NotNullWhen(true)] object? obj); public bool Equals(Decimal32 other); public override int GetHashCode(); // 5.5.2 of the IEEE Spec public static uint EncodeDecimal(Decimal32 x); public static Decimal32 DecodeDecimal(uint x); public static uint EncodeBinary(Decimal32 x); public static Decimal32 DecodeBinary(uint x); // // Formatting (IFormattable, ISpanFormattable) // public override string ToString(); public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format); public string ToString(IFormatProvider? provider); public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider); public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null); // // Explicit Convert To Decimal32 // (T -> Decimal32 is lossy) // public static explicit operator Decimal32(int value); // NOTE: Decimal64 and Decimal128 will have this as *implicit* public static explicit operator Decimal32(uint value); // NOTE: Decimal64 and Decimal128 will have this as *implicit* public static explicit operator Decimal32(nint value); // NOTE: Decimal128 will have this as *implicit* public static explicit operator Decimal32(nuint value); // NOTE: Decimal128 will have this as *implicit* public static explicit operator Decimal32(long value); // NOTE: Decimal128 will have this as *implicit* public static explicit operator Decimal32(ulong value); // NOTE: Decimal128 will have this as *implicit* public static explicit operator Decimal32(Int128 value); public static explicit operator Decimal32(UInt128 value); public static explicit operator Decimal32(Half value); public static explicit operator Decimal32(float value); public static explicit operator Decimal32(double value); public static explicit operator Decimal32(decimal value); public static explicit operator Decimal32(Decimal64 value); public static explicit operator Decimal32(Decimal128 value); // // Explicit Convert From Decimal32 // (Decimal32 -> T is lossy) // - Includes a "checked" conversion if T cannot represent infinity and NaN // public static explicit operator byte(Decimal32 value); public static explicit operator checked byte(Decimal32 value); public static explicit operator sbyte(Decimal32 value); public static explicit operator checked sbyte(Decimal32 value); public static explicit operator char(Decimal32 value); public static explicit operator checked char(Decimal32 value); public static explicit operator short(Decimal32 value); public static explicit operator checked short(Decimal32 value); public static explicit operator ushort(Decimal32 value); public static explicit operator checked ushort(Decimal32 value); public static explicit operator int(Decimal32 value); public static explicit operator checked int(Decimal32 value); public static explicit operator uint(Decimal32 value); public static explicit operator checked uint(Decimal32 value); public static explicit operator nint(Decimal32 value); public static explicit operator checked nint(Decimal32 value); public static explicit operator nuint(Decimal32 value); public static explicit operator checked nuint(Decimal32 value); public static explicit operator long(Decimal32 value); public static explicit operator checked long(Decimal32 value); public static explicit operator ulong(Decimal32 value); public static explicit operator checked ulong(Decimal32 value); public static explicit operator Int128(Decimal32 value); public static explicit operator checked Int128(Decimal32 value); public static explicit operator UInt128(Decimal32 value); public static explicit operator checked UInt128(Decimal32 value); public static explicit operator Half(Decimal32 value); public static explicit operator float(Decimal32 value); public static explicit operator double(Decimal32 value); public static explicit operator decimal(Decimal32 value); // Doesn't have a "checked" for historical reasons // // Implicit Convert To Decimal32 // (T -> Decimal32 is not lossy) // public static implicit operator Decimal32(byte value); public static implicit operator Decimal32(sbyte value); public static implicit operator Decimal32(char value); public static implicit operator Decimal32(short value); public static implicit operator Decimal32(ushort value); // // Implicit Convert From Decimal32 // (Decimal32 -> T is not lossy) // public static implicit operator Decimal64(Decimal32 value); public static implicit operator Decimal128(Decimal32 value); // // IAdditionOperators // public static Decimal32 operator +(Decimal32 left, Decimal32 right); // // IAdditiveIdentity // static Decimal32 IAdditiveIdentity.AdditiveIdentity; // // IComparisonOperators // public static bool operator <(Decimal32 left, Decimal32 right); public static bool operator >(Decimal32 left, Decimal32 right); public static bool operator <=(Decimal32 left, Decimal32 right); public static bool operator >=(Decimal32 left, Decimal32 right); // // IDecimalFloatingPointIeee754 // public static Decimal32 Quantize(Decimal32 x, Decimal32 y); public static Decimal32 Quantum(Decimal32 x); public static bool SameQuantum(Decimal32 x, Decimal32 y); // // IDecrementOperators // public static Decimal32 operator --(Decimal32 value); // // IDivisionOperators // public static Decimal32 operator /(Decimal32 left, Decimal32 right); // // IEqualityOperators // public static bool operator ==(Decimal32 left, Decimal32 right); public static bool operator !=(Decimal32 left, Decimal32 right); // // IExponentialFunctions // public static Decimal32 Exp(Decimal32 x); // PLATINUM public static Decimal32 ExpM1(Decimal32 x); // PLATINUM public static Decimal32 Exp2(Decimal32 x); // PLATINUM public static Decimal32 Exp2M1(Decimal32 x); // PLATINUM public static Decimal32 Exp10(Decimal32 x); // PLATINUM public static Decimal32 Exp10M1(Decimal32 x); // PLATINUM // // IFloatingPoint // public static Decimal32 Ceiling(Decimal32 x); public static Decimal32 Floor(Decimal32 x); public static Decimal32 Round(Decimal32 x); public static Decimal32 Round(Decimal32 x, int digits); public static Decimal32 Round(Decimal32 x, MidpointRounding mode); public static Decimal32 Round(Decimal32 x, int digits, MidpointRounding mode); public static Decimal32 Truncate(Decimal32 x); int IFloatingPoint.GetExponentByteCount(); int IFloatingPoint.GetExponentShortestBitLength(); int IFloatingPoint.GetSignificandBitLength(); int IFloatingPoint.GetSignificandByteCount(); bool IFloatingPoint.TryWriteExponentBigEndian(Span destination, out int bytesWritten); bool IFloatingPoint.TryWriteExponentLittleEndian(Span destination, out int bytesWritten); bool IFloatingPoint.TryWriteSignificandBigEndian(Span destination, out int bytesWritten); bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destination, out int bytesWritten); // // IFloatingPointConstants // public static Decimal32 E; public static Decimal32 Pi; public static Decimal32 Tau; // // IFloatingPointIeee754 // public static Decimal32 Epsilon; public static Decimal32 NaN; public static Decimal32 NegativeInfinity; public static Decimal32 NegativeZero; public static Decimal32 PositiveInfinity; public static Decimal32 Atan2(Decimal32 y, Decimal32 x); // PLATINUM public static Decimal32 Atan2Pi(Decimal32 y, Decimal32 x); // PLATINUM public static Decimal32 BitDecrement(Decimal32 x); public static Decimal32 BitIncrement(Decimal32 x); public static Decimal32 FusedMultiplyAdd(Decimal32 left, Decimal32 right, Decimal32 addend); public static Decimal32 Ieee754Remainder(Decimal32 left, Decimal32 right); public static int ILogB(Decimal32 x); public static Decimal32 Lerp(Decimal32 value1, Decimal32 value2, Decimal32 amount); public static Decimal32 ReciprocalEstimate(Decimal32 x); public static Decimal32 ReciprocalSqrtEstimate(Decimal32 x); public static Decimal32 ScaleB(Decimal32 x, int n); // public static Decimal32 Compound(Half x, Decimal32 n); (Already approved in API review but not implemented yet) // PLATINUM // // IHyperbolicFunctions // public static Decimal32 Acosh(Decimal32 x); // PLATINUM public static Decimal32 Asinh(Decimal32 x); // PLATINUM public static Decimal32 Atanh(Decimal32 x); // PLATINUM public static Decimal32 Cosh(Decimal32 x); // PLATINUM public static Decimal32 Sinh(Decimal32 x); // PLATINUM public static Decimal32 Tanh(Decimal32 x); // PLATINUM // // IIncrementOperators // public static Decimal32 operator ++(Decimal32 value); // // ILogarithmicFunctions // public static Decimal32 Log(Decimal32 x); // PLATINUM public static Decimal32 Log(Decimal32 x, Decimal32 newBase); // PLATINUM public static Decimal32 Log10(Decimal32 x); // PLATINUM public static Decimal32 LogP1(Decimal32 x); // PLATINUM public static Decimal32 Log2(Decimal32 x); // PLATINUM public static Decimal32 Log2P1(Decimal32 x); // PLATINUM public static Decimal32 Log10P1(Decimal32 x); // PLATINUM // // IMinMaxValue // public static Decimal32 MaxValue; public static Decimal32 MinValue; // // IModulusOperators // public static Decimal32 operator %(Decimal32 left, Decimal32 right); // // IMultiplicativeIdentity // public static Decimal32 MultiplicativeIdentity; // // IMultiplyOperators // public static Decimal32 operator *(Decimal32 left, Decimal32 right); // // INumber // public static Decimal32 Clamp(Decimal32 value, Decimal32 min, Decimal32 max); public static Decimal32 CopySign(Decimal32 value, Decimal32 sign); public static Decimal32 Max(Decimal32 x, Decimal32 y); public static Decimal32 MaxNumber(Decimal32 x, Decimal32 y); public static Decimal32 Min(Decimal32 x, Decimal32 y); public static Decimal32 MinNumber(Decimal32 x, Decimal32 y); public static int Sign(Decimal32 value); // // INumberBase (well defined/commonly used values) // public static Decimal32 One; static int INumberBase.Radix; // Note: this ideally should be exposed implicitly as it is required by IEEE public static Decimal32 Zero; public static Decimal32 Abs(Decimal32 value); public static Decimal32 CreateChecked(TOther value); public static Decimal32 CreateSaturating(TOther value); public static Decimal32 CreateTruncating(TOther value); static bool INumberBase.IsCanonical(Decimal32 value); // Note: this ideally should be exposed implicitly as it is required by IEEE static bool INumberBase.IsComplexNumber(Decimal32 value); public static bool IsEvenInteger(Decimal32 value); public static bool IsFinite(Decimal32 value); static bool INumberBase.IsImaginaryNumber(Decimal32 value); public static bool IsInfinity(Decimal32 value); public static bool IsInteger(Decimal32 value); public static bool IsNaN(Decimal32 value); public static bool IsNegative(Decimal32 value); public static bool IsNegativeInfinity(Decimal32 value); public static bool IsNormal(Decimal32 value); public static bool IsOddInteger(Decimal32 value); public static bool IsPositive(Decimal32 value); public static bool IsPositiveInfinity(Decimal32 value); public static bool IsRealNumber(Decimal32 value); public static bool IsSubnormal(Decimal32 value); static bool INumberBase.IsZero(Decimal32 value); // Note: this ideally should be exposed implicitly as it is required by IEEE public static Decimal32 MaxMagnitude(Decimal32 x, Decimal32 y); public static Decimal32 MaxMagnitudeNumber(Decimal32 x, Decimal32 y); public static Decimal32 MinMagnitude(Decimal32 x, Decimal32 y); public static Decimal32 MinMagnitudeNumber(Decimal32 x, Decimal32 y); static bool INumberBase.TryConvertFromChecked(TOther value, out Decimal32 result); static bool INumberBase.TryConvertFromSaturating(TOther value, out Decimal32 result); static bool INumberBase.TryConvertFromTruncating(TOther value, out Decimal32 result); static bool INumberBase.TryConvertToChecked(Decimal32 value, [MaybeNullWhen(false)] out TOther result); static bool INumberBase.TryConvertToSaturating(Decimal32 value, [MaybeNullWhen(false)] out TOther result); static bool INumberBase.TryConvertToTruncating(Decimal32 value, [MaybeNullWhen(false)] out TOther result); // // IPowerFunctions // public static Decimal32 Pow(Decimal32 x, Decimal32 y); // PLATINUM // // IRootFunctions // public static Decimal32 Cbrt(Decimal32 x); // PLATINUM public static Decimal32 Hypot(Decimal32 x, Decimal32 y); // PLATINUM public static Decimal32 RootN(Decimal32 x, int n); // PLATINUM public static Decimal32 Sqrt(Decimal32 x); // // ISignedNumber // public static Decimal32 NegativeOne; // // ISubtractionOperators // public static Decimal32 operator -(Decimal32 left, Decimal32 right); // // ITrigonometricFunctions // public static Decimal32 Acos(Decimal32 x); // PLATINUM public static Decimal32 AcosPi(Decimal32 x); // PLATINUM public static Decimal32 Asin(Decimal32 x); // PLATINUM public static Decimal32 AsinPi(Decimal32 x); // PLATINUM public static Decimal32 Atan(Decimal32 x); // PLATINUM public static Decimal32 AtanPi(Decimal32 x); // PLATINUM public static Decimal32 Cos(Decimal32 x); // PLATINUM public static Decimal32 CosPi(Decimal32 x); // PLATINUM public static Decimal32 Sin(Decimal32 x); // PLATINUM public static (Decimal32 Sin, Decimal32 Cos) SinCos(Decimal32 x); // PLATINUM public static (Decimal32 SinPi, Decimal32 CosPi) SinCosPi(Decimal32 x); // PLATINUM public static Decimal32 SinPi(Decimal32 x); // PLATINUM public static Decimal32 Tan(Decimal32 x); // PLATINUM public static Decimal32 TanPi(Decimal32 x); // PLATINUM // // IUnaryNegationOperators // public static Decimal32 operator -(Decimal32 value); // // IUnaryPlusOperators // public static Decimal32 operator +(Decimal32 value); } } ```
Author: dakersnar
Assignees: -
Labels: `area-System.Numerics`
Milestone: -
AaronRobinsonMSFT commented 1 year ago

These must be implemented with future hardware support integration in mind.

This is a bit strong. In order for customers to benefit from future hardware these APIs must be implemented. The above implies these APIs should be implemented because of future hardware which doesn't seem correct. This is an increasingly niche API surface area and I think we should determine a customer and if it is a current need for .NET 8 or perhaps a future release.

dakersnar commented 1 year ago

@AaronRobinsonMSFT Let me adjust my wording, what I meant to imply with that bullet point was that if we are going to implement these types, we must implement them with future hardware support in mind. In other words, we should implement them in such a way that, if hardware support is eventually released and widespread, we can easily wire these up to those intrinsics.

tannergooding commented 1 year ago

This is a bit strong. In order for customers to benefit from future hardware these APIs must be implemented.

@AaronRobinsonMSFT, I think you're misunderstanding the statement here.

The statement is one around ensuring we consider that there are two backing encodings for decimal (one oriented towards software and another towards hardware) and that we should ensure our API surface isn't forcing one or the other.

Such hardware supporting the IEEE 754 decimal types already exists and has been in production use at the enterprise level for years (namely IBM PowerPC).

This is an increasingly niche API surface area and I think we should determine a customer and if it is a current need for .NET 8 or perhaps a future release.

I likewise think this is a misunderstanding. We have had decimal since .NET v1.0 and since its introduction developers having been asking for extended functionality that cannot be provided by that type (namely because it has a strict 1-to-1 requirement with the underlying DECIMAL/CY type in Win32).

The IEEE 754 decimal types are an industry standard that has already been proven to be successful, which has been an ABI standard for over 12 years, and which is actively getting first class support in a number of other languages.

This points towards it being a viable answer towards both the perf and extension points customers have already been asking for around System.Decimal which we've not been able to provide.

AaronRobinsonMSFT commented 1 year ago

The statement is one around ensuring we consider that there are two backing encodings for decimal (one oriented towards software and another towards hardware) and that we should ensure our API surface isn't forcing one or the other.

Gotcha. That makes sense. @dakersnar's statement also helped to clarify that.

KTSnowy commented 1 year ago

Copying my comment from #79004 and adding a little more context.

I'm the current lead developer of Otterkit, a free and open source COBOL compiler for dotnet (Implementing the COBOL 2022 standard). We're looking forward to being able to use these types in our compiler.

We are currently PInvoking calls to the mpdecimal library to provide this functionality on dotnet, but that makes the build process much more complicated and hurts portability. Having these types available directly on C# would be awesome for our project.

The reason for this is that COBOL relies heavily on decimal arithmetic, the COBOL standard requires support for the Decimal128 type for its standard-decimal arithmetic mode, and requires a decimal implementation with at least 31 digits of precision for its native arithmetic mode. The decimal type we have in C# right now is not compatible with these requirements.

Our COBOL runtime library only requires the Decimal128 type, both the Decimal32 and Decimal64 use a truncated Decimal128. This works quite well because all of these types' max/min values only contains 9s and an exponent (+-99999...E+-...). There doesn't seem to be a downside to this as long as the Decimal128 implementation is performant enough, and the truncation and overflow checks matches the IEEE requirements for those types' max/min values. The final value is then stored in Decimal32 and Decimal64 formats, but the math itself is done as if it was a Decimal128.


Hi @dakersnar, COBOL provides some of those math functions with its intrinsic functions, and implementing those is required by the COBOL standard. The mpdecimal library that we're using unfortunately does not completely provide that functionality so we had to implement it ourselves in the runtime library (mpdecimal doesn't provide trigonometric functions).

Because of the COBOL standard's requirements we would require support for the IExponentialFunctions, ILogarithmicFunctions, IPowerFunctions, IRootFunctions, ITrigonometricFunctions.

COBOL itself does not provide support for anything from IHyperbolicFunctions so we would not require support for it.

Having those directly in C# would be awesome, we would be able to provide that COBOL functionality in our compiler in a way that is compatible with C#, and might make it easier to implement our COBOL <=> C# Bridge in the future.

I'm not sure if I'm allowed to request any extra functionality for this, but if possible, would there be a way to support UTF-8 encoded ToSpan and FromSpan methods for these types? Otterkit heavily depends on Span<byte> and Memory<byte> for every COBOL data type, every type is implemented using UTF-8 encoded Spans. Using Span<char> would not work due to COBOL's very specific picture formatting rules.

richlander commented 1 year ago

I'm not sure if I'm allowed to request any extra functionality for this

Please consider yourself "allowed". I'm sure the team would appreciate knowing more about your needs, particularly to the end of influencing their design and prioritization.

dakersnar commented 1 year ago

@KTSnowy just so I can fully understand your scenario, let me outline a few possibilities: 

  1. We ship only Decimal64, without the Platinum APIs, in .NET 8.
  2. We ship Decimal128, without the Platinum APIs, in .NET 8.
  3. We ship Decimal128, with the Platinum APIs, in .NET 8. 

At which level does it become feasible for you to plug our types into your project, if only to test the ease of integration and measure performance? Following that, at which level does it become beneficial to plug the types into your project? Obviously, it sounds like level 3 is what you would most prefer. In the event level 3 is not feasible for this release cycle, is there a "half measure" for .NET 8 that you would still be able to use?

Edit: Additionally, I want to note that .NET does not currently support some aspects of the IEEE spec including global rounding modes, floating-point exceptions, and flag setting. Does your project require these features?

KTSnowy commented 1 year ago

Hi @dakersnar, it would become both feasible and beneficial to plug these types into our compiler at level 2, but only if there is support for UTF-8 encoded Spans (from Span and to Span conversions).

The COBOL standard requires support for the Decimal128 type. At level 1 we won't be able to use them due to the Decimal64 format only supporting 16 decimal digits.

At level 2 with UTF-8 Spans support we could move most of our mpdecimal calls to the .NET 8 implementation, we could also temporarily use the Platinum APIs functionality from mpdecimal until there is support in the .NET runtime for them.

This would mostly depend on the UTF-8 Span<byte> support.

Without it, PInvoking into mpdecimal to provide the Platinum APIs functionality would become a lot more expensive. Having to convert from and to a string and passing them through PInvoke would be much more expensive than the byte pointers that we're currently using.

Without conversions for UTF-8 encoded Spans, it would only be feasible at level 3.

Edit: Additionally, I want to note that .NET does not currently support some aspects of the IEEE spec including global rounding modes, floating-point exceptions, and flag setting. Does your project require these features?

Our compiler can generate calls to the local rounding methods to "emulate" the global rounding modes at compile time, so this won't be an issue, but it would be awesome to have those if it's possible to support them.

The exceptions and flags are also not required, COBOL has a very different way of handling exceptions so we won't be able to directly use C# exceptions.

KTSnowy commented 1 year ago

At which level does it become feasible for you to plug our types into your project, if only to test the ease of integration and measure performance?

I'd be happy to help with performance and ease of integration tests. Just let me know if there's any specific way or specific tool that you'd like us to use to measure performance.

dakersnar commented 1 year ago

Having to convert from and to a string and passing them through PInvoke would be much more expensive than the byte pointers that we're currently using.

Would the "EncodeBinary" and/or "EncodeDecimal" APIs proposed above work for your scenario? That would get you the raw bit representation of the Decimal128, either as a UInt128 or two ulongs, depending on what API review decides.

KTSnowy commented 1 year ago

Would the "EncodeBinary" and/or "EncodeDecimal" APIs proposed above work for your scenario?

It wouldn't work completely, we still need a "byte array-like" representation of the Decimal128 to apply formatting to.

Using Span<byte> and Memory<byte> was the most performant way we found to store and format COBOL types with the picture clause requirements, so even if we find a way to convert the raw bits into UTF-8 bytes we'd still need a "Parse" and "TryParse" that can accept the UTF-8 bytes back, or a way to turn those bytes back into the raw bit representation.

Ideally we'd like a "ToSpan", "Parse" and "TryParse" that can return and accept UTF-8 encoded Span<byte>, but we're not sure if we can request this and if this extra functionality would be too much work to implement. Please let us know if this would be feasible.

GrabYourPitchforks commented 1 year ago

@dakersnar Looks like they want Utf8Parser and Utf8Formatter to be updated at the same time that these types come online. That's where all our other UTF-8 parsing / emitting functionality sits. (We don't tend to put these APIs directly on the numeric types themselves.)

tannergooding commented 1 year ago

I think we should consider IUtf8Parsable and IUtf8Formattable instead. That makes it more general purpose and allows any type to opt-in.

Will open a proposal https://github.com/dotnet/runtime/issues/81500

dakersnar commented 1 year ago

Another consideration: reading another recent thread, I was reminded that we expose a public constructor for Int128/UInt128 as they are not primitive types. Internally, while working on the implementation for DecimalXX, I've been taking advantage of an internal constructor: DecimalXX(sign, exp, significand). Should we consider exposing something like this publicly?

Notably, with the current shape, users can only create these types in the following ways:

bartonjs commented 1 year ago

Since we lost quorum before finalizing the ctor-vs-named-static point (and the names of the parameters), and the pattern for the conversion operators, we'll need to discuss this again before approval.

namespace System.Numerics
{

    /// <summary>Defines an IEEE 754 floating-point type that is represented in a base-10 format.</summary>
    /// <typeparam name="TSelf">The type that implements the interface.</typeparam>
    public interface IDecimalFloatingPointIeee754<TSelf> // PLATINUM
        : IFloatingPointIeee754<TSelf>
        where TSelf : IDecimalFloatingPointIeee754<TSelf>
    {
        // IEEE Spec 5.3.2
        static abstract TSelf Quantize(TSelf x, TSelf y);
        static abstract TSelf GetQuantum(TSelf x);

        // IEEE Spec 5.7.3
        static abstract bool HaveSameQuantum(TSelf x, TSelf y);
    }

    //
    // Summary:
    //     Represents a 32-bit IEEE decimal floating-point number
    [StructLayout(LayoutKind.Sequential)]
    public readonly struct Decimal32
        : IComparable<Decimal32>,
          IComparable,
          ISpanFormattable,
          ISpanParsable<Decimal32>,
          IEquatable<Decimal32>,
          IFloatingPoint<Decimal32>,/*PLATINUM: Replace with IDecimalFloatingPointIeee754<Decimal32>,*/
          IMinMaxValue<Decimal32>
    {
        internal readonly uint _value; // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128

        //
        // Parsing (INumberBase, IParsable, ISpanParsable, other)
        //

        public static Decimal32 Parse(string s);
        public static Decimal32 Parse(string s, NumberStyles style);
        public static Decimal32 Parse(ReadOnlySpan<char> s, IFormatProvider? provider);
        public static Decimal32 Parse(string s, IFormatProvider? provider);
        public static Decimal32 Parse(ReadOnlySpan<char> s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null);
        public static Decimal32 Parse(string s, NumberStyles style, IFormatProvider? provider);
        public static bool TryParse([NotNullWhen(true)] string? s, out Decimal32 result);
        public static bool TryParse(ReadOnlySpan<char> s, out Decimal32 result);
        public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);
        public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);
        public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);
        public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);

        //
        // Misc. Methods (including IComparable, IEquatable, other)
        //

        public int CompareTo(object? obj);
        public int CompareTo(Decimal32 other);
        public override bool Equals([NotNullWhen(true)] object? obj);
        public bool Equals(Decimal32 other);
        public override int GetHashCode();
        // 5.5.2 of the IEEE Spec
        public static uint EncodeDecimal(Decimal32 x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128
        public static Decimal32 DecodeDecimal(uint x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128
        public static uint EncodeBinary(Decimal32 x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128
        public static Decimal32 DecodeBinary(uint x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128

        //
        // Formatting (IFormattable, ISpanFormattable, other)
        //

        public override string ToString();
        public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format);
        public string ToString(IFormatProvider? provider);
        public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider);
        public bool TryFormat(Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan<char> format = default, IFormatProvider? provider = null);

        //
        // Explicit Convert To Decimal32
        // (T -> Decimal32 is lossy)
        //

        public static explicit operator Decimal32(int value); // NOTE: Decimal64 and Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(uint value); // NOTE: Decimal64 and Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(nint value);  // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(nuint value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(long value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(ulong value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(Int128 value);
        public static explicit operator Decimal32(UInt128 value);
        public static explicit operator Decimal32(Half value);
        public static explicit operator Decimal32(float value);
        public static explicit operator Decimal32(double value);
        public static explicit operator Decimal32(decimal value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(Decimal64 value);
        public static explicit operator Decimal32(Decimal128 value);

        //
        // Explicit Convert From Decimal32
        // (Decimal32 -> T is lossy)
        // - Includes a "checked" conversion if T cannot represent infinity and NaN
        //
        public static explicit operator byte(Decimal32 value);
        public static explicit operator checked byte(Decimal32 value);
        public static explicit operator sbyte(Decimal32 value);
        public static explicit operator checked sbyte(Decimal32 value);
        public static explicit operator char(Decimal32 value);
        public static explicit operator checked char(Decimal32 value);
        public static explicit operator short(Decimal32 value);
        public static explicit operator checked short(Decimal32 value);
        public static explicit operator ushort(Decimal32 value);
        public static explicit operator checked ushort(Decimal32 value);
        public static explicit operator int(Decimal32 value);
        public static explicit operator checked int(Decimal32 value);
        public static explicit operator uint(Decimal32 value);
        public static explicit operator checked uint(Decimal32 value);
        public static explicit operator nint(Decimal32 value);        
        public static explicit operator checked nint(Decimal32 value);
        public static explicit operator nuint(Decimal32 value);
        public static explicit operator checked nuint(Decimal32 value);
        public static explicit operator long(Decimal32 value);
        public static explicit operator checked long(Decimal32 value);
        public static explicit operator ulong(Decimal32 value);
        public static explicit operator checked ulong(Decimal32 value);
        public static explicit operator Int128(Decimal32 value);       
        public static explicit operator checked Int128(Decimal32 value);
        public static explicit operator UInt128(Decimal32 value);
        public static explicit operator checked UInt128(Decimal32 value);
        public static explicit operator Half(Decimal32 value);
        public static explicit operator float(Decimal32 value);
        public static explicit operator double(Decimal32 value);
        public static explicit operator decimal(Decimal32 value); // Doesn't have a "checked" for historical reasons

        //
        // Implicit Convert To Decimal32
        // (T -> Decimal32 is not lossy)
        //
        public static implicit operator Decimal32(byte value);
        public static implicit operator Decimal32(sbyte value);
        public static implicit operator Decimal32(char value);
        public static implicit operator Decimal32(short value);
        public static implicit operator Decimal32(ushort value);

        //
        // Implicit Convert From Decimal32
        // (Decimal32 -> T is not lossy)
        //
        public static implicit operator Decimal64(Decimal32 value);
        public static implicit operator Decimal128(Decimal32 value);

        //
        // IAdditionOperators
        //
        public static Decimal32 operator +(Decimal32 left, Decimal32 right);

        //
        // IAdditiveIdentity
        //
        static Decimal32 IAdditiveIdentity<Decimal32, Decimal32>.AdditiveIdentity { get; }

        //
        // IComparisonOperators
        //
        public static bool operator <(Decimal32 left, Decimal32 right);
        public static bool operator >(Decimal32 left, Decimal32 right);
        public static bool operator <=(Decimal32 left, Decimal32 right);
        public static bool operator >=(Decimal32 left, Decimal32 right);

        //
        // IDecimalFloatingPointIeee754
        //
        public static Decimal32 Quantize(Decimal32 x, Decimal32 y);
        public static Decimal32 Quantum(Decimal32 x);
        public static bool SameQuantum(Decimal32 x, Decimal32 y);

        //
        // IDecrementOperators
        //
        public static Decimal32 operator --(Decimal32 value);

        //
        // IDivisionOperators
        //
        public static Decimal32 operator /(Decimal32 left, Decimal32 right);

        //
        // IEqualityOperators
        //
        public static bool operator ==(Decimal32 left, Decimal32 right);
        public static bool operator !=(Decimal32 left, Decimal32 right);

        //
        // IExponentialFunctions
        //
        public static Decimal32 Exp(Decimal32 x); // PLATINUM
        public static Decimal32 ExpM1(Decimal32 x); // PLATINUM
        public static Decimal32 Exp2(Decimal32 x); // PLATINUM
        public static Decimal32 Exp2M1(Decimal32 x); // PLATINUM
        public static Decimal32 Exp10(Decimal32 x); // PLATINUM
        public static Decimal32 Exp10M1(Decimal32 x); // PLATINUM

        //
        // IFloatingPoint
        //
        public static Decimal32 Ceiling(Decimal32 x);
        public static Decimal32 Floor(Decimal32 x);
        public static Decimal32 Round(Decimal32 x);
        public static Decimal32 Round(Decimal32 x, int digits);
        public static Decimal32 Round(Decimal32 x, MidpointRounding mode);
        public static Decimal32 Round(Decimal32 x, int digits, MidpointRounding mode);
        public static Decimal32 Truncate(Decimal32 x);
        int IFloatingPoint<Decimal32>.GetExponentByteCount();
        int IFloatingPoint<Decimal32>.GetExponentShortestBitLength();
        int IFloatingPoint<Decimal32>.GetSignificandBitLength();
        int IFloatingPoint<Decimal32>.GetSignificandByteCount();
        bool IFloatingPoint<Decimal32>.TryWriteExponentBigEndian(Span<byte> destination, out int bytesWritten);
        bool IFloatingPoint<Decimal32>.TryWriteExponentLittleEndian(Span<byte> destination, out int bytesWritten);
        bool IFloatingPoint<Decimal32>.TryWriteSignificandBigEndian(Span<byte> destination, out int bytesWritten);
        bool IFloatingPoint<Decimal32>.TryWriteSignificandLittleEndian(Span<byte> destination, out int bytesWritten);

        //
        // IFloatingPointConstants
        //
        public static Decimal32 E { get; }
        public static Decimal32 Pi { get; }
        public static Decimal32 Tau { get; }

        //
        // IFloatingPointIeee754
        //
        public static Decimal32 Epsilon { get; }
        public static Decimal32 NaN { get; }
        public static Decimal32 NegativeInfinity { get; }
        public static Decimal32 NegativeZero { get; }
        public static Decimal32 PositiveInfinity { get; }
        public static Decimal32 Atan2(Decimal32 y, Decimal32 x); // PLATINUM
        public static Decimal32 Atan2Pi(Decimal32 y, Decimal32 x); // PLATINUM
        public static Decimal32 BitDecrement(Decimal32 x);
        public static Decimal32 BitIncrement(Decimal32 x);
        public static Decimal32 FusedMultiplyAdd(Decimal32 left, Decimal32 right, Decimal32 addend);
        public static Decimal32 Ieee754Remainder(Decimal32 left, Decimal32 right);
        public static int ILogB(Decimal32 x);
        public static Decimal32 Lerp(Decimal32 value1, Decimal32 value2, Decimal32 amount);
        public static Decimal32 ReciprocalEstimate(Decimal32 x);
        public static Decimal32 ReciprocalSqrtEstimate(Decimal32 x);
        public static Decimal32 ScaleB(Decimal32 x, int n);
        // public static Decimal32 Compound(Half x, Decimal32 n); (Already approved in API review but not implemented yet) // PLATINUM

        //
        // IHyperbolicFunctions
        //
        public static Decimal32 Acosh(Decimal32 x); // PLATINUM
        public static Decimal32 Asinh(Decimal32 x); // PLATINUM
        public static Decimal32 Atanh(Decimal32 x); // PLATINUM
        public static Decimal32 Cosh(Decimal32 x); // PLATINUM
        public static Decimal32 Sinh(Decimal32 x); // PLATINUM
        public static Decimal32 Tanh(Decimal32 x); // PLATINUM

        //
        // IIncrementOperators
        //
        public static Decimal32 operator ++(Decimal32 value);

        //
        // ILogarithmicFunctions
        //
        public static Decimal32 Log(Decimal32 x); // PLATINUM
        public static Decimal32 Log(Decimal32 x, Decimal32 newBase); // PLATINUM
        public static Decimal32 Log10(Decimal32 x); // PLATINUM
        public static Decimal32 LogP1(Decimal32 x); // PLATINUM
        public static Decimal32 Log2(Decimal32 x); // PLATINUM
        public static Decimal32 Log2P1(Decimal32 x); // PLATINUM
        public static Decimal32 Log10P1(Decimal32 x); // PLATINUM

        //
        // IMinMaxValue
        //
        public static Decimal32 MaxValue { get; }
        public static Decimal32 MinValue { get; }

        //
        // IModulusOperators
        //
        public static Decimal32 operator %(Decimal32 left, Decimal32 right);

        //
        // IMultiplicativeIdentity
        //
        public static Decimal32 MultiplicativeIdentity { get; }

        //
        // IMultiplyOperators
        //
        public static Decimal32 operator *(Decimal32 left, Decimal32 right);

        //
        // INumber
        //
        public static Decimal32 Clamp(Decimal32 value, Decimal32 min, Decimal32 max);
        public static Decimal32 CopySign(Decimal32 value, Decimal32 sign);
        public static Decimal32 Max(Decimal32 x, Decimal32 y);
        public static Decimal32 MaxNumber(Decimal32 x, Decimal32 y);
        public static Decimal32 Min(Decimal32 x, Decimal32 y);
        public static Decimal32 MinNumber(Decimal32 x, Decimal32 y);
        public static int Sign(Decimal32 value);

        //
        // INumberBase (well defined/commonly used values)
        //
        public static Decimal32 One { get; }
        static int INumberBase<Decimal32>.Radix; // Note: this ideally should be exposed implicitly as it is required by IEEE
        public static Decimal32 Zero { get; }
        public static Decimal32 Abs(Decimal32 value);
        public static Decimal32 CreateChecked<TOther>(TOther value);
        public static Decimal32 CreateSaturating<TOther>(TOther value);
        public static Decimal32 CreateTruncating<TOther>(TOther value);
        static bool INumberBase<Decimal32>.IsCanonical(Decimal32 value); // Note: this ideally should be exposed implicitly as it is required by IEEE
        static bool INumberBase<Decimal32>.IsComplexNumber(Decimal32 value);
        public static bool IsEvenInteger(Decimal32 value);
        public static bool IsFinite(Decimal32 value);
        static bool INumberBase<Decimal32>.IsImaginaryNumber(Decimal32 value);
        public static bool IsInfinity(Decimal32 value);
        public static bool IsInteger(Decimal32 value);
        public static bool IsNaN(Decimal32 value);
        public static bool IsNegative(Decimal32 value);
        public static bool IsNegativeInfinity(Decimal32 value);
        public static bool IsNormal(Decimal32 value);
        public static bool IsOddInteger(Decimal32 value);
        public static bool IsPositive(Decimal32 value);
        public static bool IsPositiveInfinity(Decimal32 value);
        public static bool IsRealNumber(Decimal32 value);
        public static bool IsSubnormal(Decimal32 value);
        static bool INumberBase<Decimal32>.IsZero(Decimal32 value); // Note: this ideally should be exposed implicitly as it is required by IEEE
        public static Decimal32 MaxMagnitude(Decimal32 x, Decimal32 y);
        public static Decimal32 MaxMagnitudeNumber(Decimal32 x, Decimal32 y);
        public static Decimal32 MinMagnitude(Decimal32 x, Decimal32 y);
        public static Decimal32 MinMagnitudeNumber(Decimal32 x, Decimal32 y);
        static bool INumberBase<Decimal32>.TryConvertFromChecked<TOther>(TOther value, out Decimal32 result);
        static bool INumberBase<Decimal32>.TryConvertFromSaturating<TOther>(TOther value, out Decimal32 result);
        static bool INumberBase<Decimal32>.TryConvertFromTruncating<TOther>(TOther value, out Decimal32 result);
        static bool INumberBase<Decimal32>.TryConvertToChecked<TOther>(Decimal32 value, [MaybeNullWhen(false)] out TOther result);
        static bool INumberBase<Decimal32>.TryConvertToSaturating<TOther>(Decimal32 value, [MaybeNullWhen(false)] out TOther result);
        static bool INumberBase<Decimal32>.TryConvertToTruncating<TOther>(Decimal32 value, [MaybeNullWhen(false)] out TOther result);

        //
        // IPowerFunctions
        //
        public static Decimal32 Pow(Decimal32 x, Decimal32 y); // PLATINUM

        //
        // IRootFunctions
        //
        public static Decimal32 Cbrt(Decimal32 x); // PLATINUM
        public static Decimal32 Hypot(Decimal32 x, Decimal32 y); // PLATINUM
        public static Decimal32 RootN(Decimal32 x, int n); // PLATINUM
        public static Decimal32 Sqrt(Decimal32 x);

        //
        // ISignedNumber
        //
        public static Decimal32 NegativeOne { get; }

        //
        // ISubtractionOperators
        //
        public static Decimal32 operator -(Decimal32 left, Decimal32 right);

        //
        // ITrigonometricFunctions
        //
        public static Decimal32 Acos(Decimal32 x); // PLATINUM
        public static Decimal32 AcosPi(Decimal32 x); // PLATINUM
        public static Decimal32 Asin(Decimal32 x); // PLATINUM
        public static Decimal32 AsinPi(Decimal32 x); // PLATINUM
        public static Decimal32 Atan(Decimal32 x); // PLATINUM
        public static Decimal32 AtanPi(Decimal32 x); // PLATINUM
        public static Decimal32 Cos(Decimal32 x); // PLATINUM
        public static Decimal32 CosPi(Decimal32 x); // PLATINUM
        public static Decimal32 Sin(Decimal32 x); // PLATINUM
        public static (Decimal32 Sin, Decimal32 Cos) SinCos(Decimal32 x); // PLATINUM
        public static (Decimal32 SinPi, Decimal32 CosPi) SinCosPi(Decimal32 x); // PLATINUM
        public static Decimal32 SinPi(Decimal32 x); // PLATINUM
        public static Decimal32 Tan(Decimal32 x); // PLATINUM
        public static Decimal32 TanPi(Decimal32 x); // PLATINUM

        //
        // IUnaryNegationOperators
        //
        public static Decimal32 operator -(Decimal32 value);

        //
        // IUnaryPlusOperators
        //
        public static Decimal32 operator +(Decimal32 value);
    }
}
bartonjs commented 1 year ago

Since we lost quorum before finalizing the ctor-vs-named-static point (and the names of the parameters), and the pattern for the conversion operators, we'll need to discuss this again before approval.

namespace System.Numerics
{

    /// <summary>Defines an IEEE 754 floating-point type that is represented in a base-10 format.</summary>
    /// <typeparam name="TSelf">The type that implements the interface.</typeparam>
    public interface IDecimalFloatingPointIeee754<TSelf> // PLATINUM
        : IFloatingPointIeee754<TSelf>
        where TSelf : IDecimalFloatingPointIeee754<TSelf>
    {
        // IEEE Spec 5.3.2
        static abstract TSelf Quantize(TSelf x, TSelf y);
        static abstract TSelf GetQuantum(TSelf x);

        // IEEE Spec 5.7.3
        static abstract bool HaveSameQuantum(TSelf x, TSelf y);
    }

    //
    // Summary:
    //     Represents a 32-bit IEEE decimal floating-point number
    [StructLayout(LayoutKind.Sequential)]
    public readonly struct Decimal32
        : IComparable<Decimal32>,
          IComparable,
          ISpanFormattable,
          ISpanParsable<Decimal32>,
          IEquatable<Decimal32>,
          IFloatingPoint<Decimal32>,/*PLATINUM: Replace with IDecimalFloatingPointIeee754<Decimal32>,*/
          IMinMaxValue<Decimal32>
    {
        internal readonly uint _value; // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128

        public Decimal32(int significand, int exponent); // NOTE: params are (long, int) for Decimal64 and (Int128, int) for Decimal128

        //
        // Parsing (INumberBase, IParsable, ISpanParsable, other)
        //

        public static Decimal32 Parse(string s);
        public static Decimal32 Parse(string s, NumberStyles style);
        public static Decimal32 Parse(ReadOnlySpan<char> s, IFormatProvider? provider);
        public static Decimal32 Parse(string s, IFormatProvider? provider);
        public static Decimal32 Parse(ReadOnlySpan<char> s, NumberStyles style = DefaultParseStyle, IFormatProvider? provider = null);
        public static Decimal32 Parse(string s, NumberStyles style, IFormatProvider? provider);
        public static bool TryParse([NotNullWhen(true)] string? s, out Decimal32 result);
        public static bool TryParse(ReadOnlySpan<char> s, out Decimal32 result);
        public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);
        public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);
        public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);
        public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result);

        //
        // Misc. Methods (including IComparable, IEquatable, other)
        //

        public int CompareTo(object? obj);
        public int CompareTo(Decimal32 other);
        public override bool Equals([NotNullWhen(true)] object? obj);
        public bool Equals(Decimal32 other);
        public override int GetHashCode();
        // 5.5.2 of the IEEE Spec
        public static uint EncodeDecimal(Decimal32 x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128
        public static Decimal32 DecodeDecimal(uint x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128
        public static uint EncodeBinary(Decimal32 x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128
        public static Decimal32 DecodeBinary(uint x); // NOTE: this is a ulong for Decimal64, and a UInt128 for Decimal128

        //
        // Formatting (IFormattable, ISpanFormattable, other)
        //

        public override string ToString();
        public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format);
        public string ToString(IFormatProvider? provider);
        public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider);
        public bool TryFormat(Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan<char> format = default, IFormatProvider? provider = null);

        //
        // Explicit Convert To Decimal32
        // (T -> Decimal32 is lossy)
        //

        public static explicit operator Decimal32(int value); // NOTE: Decimal64 and Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(uint value); // NOTE: Decimal64 and Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(nint value);  // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(nuint value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(long value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(ulong value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(Int128 value);
        public static explicit operator Decimal32(UInt128 value);
        public static explicit operator Decimal32(Half value);
        public static explicit operator Decimal32(float value);
        public static explicit operator Decimal32(double value);
        public static explicit operator Decimal32(decimal value); // NOTE: Decimal128 will have this as *implicit*
        public static explicit operator Decimal32(Decimal64 value);
        public static explicit operator Decimal32(Decimal128 value);

        //
        // Explicit Convert From Decimal32
        // (Decimal32 -> T is lossy)
        // - Includes a "checked" conversion if T cannot represent infinity and NaN
        //
        public static explicit operator byte(Decimal32 value);
        public static explicit operator checked byte(Decimal32 value);
        public static explicit operator sbyte(Decimal32 value);
        public static explicit operator checked sbyte(Decimal32 value);
        public static explicit operator char(Decimal32 value);
        public static explicit operator checked char(Decimal32 value);
        public static explicit operator short(Decimal32 value);
        public static explicit operator checked short(Decimal32 value);
        public static explicit operator ushort(Decimal32 value);
        public static explicit operator checked ushort(Decimal32 value);
        public static explicit operator int(Decimal32 value);
        public static explicit operator checked int(Decimal32 value);
        public static explicit operator uint(Decimal32 value);
        public static explicit operator checked uint(Decimal32 value);
        public static explicit operator nint(Decimal32 value);        
        public static explicit operator checked nint(Decimal32 value);
        public static explicit operator nuint(Decimal32 value);
        public static explicit operator checked nuint(Decimal32 value);
        public static explicit operator long(Decimal32 value);
        public static explicit operator checked long(Decimal32 value);
        public static explicit operator ulong(Decimal32 value);
        public static explicit operator checked ulong(Decimal32 value);
        public static explicit operator Int128(Decimal32 value);       
        public static explicit operator checked Int128(Decimal32 value);
        public static explicit operator UInt128(Decimal32 value);
        public static explicit operator checked UInt128(Decimal32 value);
        public static explicit operator Half(Decimal32 value);
        public static explicit operator float(Decimal32 value);
        public static explicit operator double(Decimal32 value);
        public static explicit operator decimal(Decimal32 value); // Doesn't have a "checked" for historical reasons

        //
        // Implicit Convert To Decimal32
        // (T -> Decimal32 is not lossy)
        //
        public static implicit operator Decimal32(byte value);
        public static implicit operator Decimal32(sbyte value);
        public static implicit operator Decimal32(char value);
        public static implicit operator Decimal32(short value);
        public static implicit operator Decimal32(ushort value);

        //
        // Implicit Convert From Decimal32
        // (Decimal32 -> T is not lossy)
        //
        public static implicit operator Decimal64(Decimal32 value);
        public static implicit operator Decimal128(Decimal32 value);

        //
        // IAdditionOperators
        //
        public static Decimal32 operator +(Decimal32 left, Decimal32 right);

        //
        // IAdditiveIdentity
        //
        static Decimal32 IAdditiveIdentity<Decimal32, Decimal32>.AdditiveIdentity { get; }

        //
        // IComparisonOperators
        //
        public static bool operator <(Decimal32 left, Decimal32 right);
        public static bool operator >(Decimal32 left, Decimal32 right);
        public static bool operator <=(Decimal32 left, Decimal32 right);
        public static bool operator >=(Decimal32 left, Decimal32 right);

        //
        // IDecimalFloatingPointIeee754
        //
        public static Decimal32 Quantize(Decimal32 x, Decimal32 y);
        public static Decimal32 Quantum(Decimal32 x);
        public static bool SameQuantum(Decimal32 x, Decimal32 y);

        //
        // IDecrementOperators
        //
        public static Decimal32 operator --(Decimal32 value);

        //
        // IDivisionOperators
        //
        public static Decimal32 operator /(Decimal32 left, Decimal32 right);

        //
        // IEqualityOperators
        //
        public static bool operator ==(Decimal32 left, Decimal32 right);
        public static bool operator !=(Decimal32 left, Decimal32 right);

        //
        // IExponentialFunctions
        //
        public static Decimal32 Exp(Decimal32 x); // PLATINUM
        public static Decimal32 ExpM1(Decimal32 x); // PLATINUM
        public static Decimal32 Exp2(Decimal32 x); // PLATINUM
        public static Decimal32 Exp2M1(Decimal32 x); // PLATINUM
        public static Decimal32 Exp10(Decimal32 x); // PLATINUM
        public static Decimal32 Exp10M1(Decimal32 x); // PLATINUM

        //
        // IFloatingPoint
        //
        public static Decimal32 Ceiling(Decimal32 x);
        public static Decimal32 Floor(Decimal32 x);
        public static Decimal32 Round(Decimal32 x);
        public static Decimal32 Round(Decimal32 x, int digits);
        public static Decimal32 Round(Decimal32 x, MidpointRounding mode);
        public static Decimal32 Round(Decimal32 x, int digits, MidpointRounding mode);
        public static Decimal32 Truncate(Decimal32 x);
        int IFloatingPoint<Decimal32>.GetExponentByteCount();
        int IFloatingPoint<Decimal32>.GetExponentShortestBitLength();
        int IFloatingPoint<Decimal32>.GetSignificandBitLength();
        int IFloatingPoint<Decimal32>.GetSignificandByteCount();
        bool IFloatingPoint<Decimal32>.TryWriteExponentBigEndian(Span<byte> destination, out int bytesWritten);
        bool IFloatingPoint<Decimal32>.TryWriteExponentLittleEndian(Span<byte> destination, out int bytesWritten);
        bool IFloatingPoint<Decimal32>.TryWriteSignificandBigEndian(Span<byte> destination, out int bytesWritten);
        bool IFloatingPoint<Decimal32>.TryWriteSignificandLittleEndian(Span<byte> destination, out int bytesWritten);

        //
        // IFloatingPointConstants
        //
        public static Decimal32 E { get; }
        public static Decimal32 Pi { get; }
        public static Decimal32 Tau { get; }

        //
        // IFloatingPointIeee754
        //
        public static Decimal32 Epsilon { get; }
        public static Decimal32 NaN { get; }
        public static Decimal32 NegativeInfinity { get; }
        public static Decimal32 NegativeZero { get; }
        public static Decimal32 PositiveInfinity { get; }
        public static Decimal32 Atan2(Decimal32 y, Decimal32 x); // PLATINUM
        public static Decimal32 Atan2Pi(Decimal32 y, Decimal32 x); // PLATINUM
        public static Decimal32 BitDecrement(Decimal32 x);
        public static Decimal32 BitIncrement(Decimal32 x);
        public static Decimal32 FusedMultiplyAdd(Decimal32 left, Decimal32 right, Decimal32 addend);
        public static Decimal32 Ieee754Remainder(Decimal32 left, Decimal32 right);
        public static int ILogB(Decimal32 x);
        public static Decimal32 Lerp(Decimal32 value1, Decimal32 value2, Decimal32 amount);
        public static Decimal32 ReciprocalEstimate(Decimal32 x);
        public static Decimal32 ReciprocalSqrtEstimate(Decimal32 x);
        public static Decimal32 ScaleB(Decimal32 x, int n);
        // public static Decimal32 Compound(Half x, Decimal32 n); (Already approved in API review but not implemented yet) // PLATINUM

        //
        // IHyperbolicFunctions
        //
        public static Decimal32 Acosh(Decimal32 x); // PLATINUM
        public static Decimal32 Asinh(Decimal32 x); // PLATINUM
        public static Decimal32 Atanh(Decimal32 x); // PLATINUM
        public static Decimal32 Cosh(Decimal32 x); // PLATINUM
        public static Decimal32 Sinh(Decimal32 x); // PLATINUM
        public static Decimal32 Tanh(Decimal32 x); // PLATINUM

        //
        // IIncrementOperators
        //
        public static Decimal32 operator ++(Decimal32 value);

        //
        // ILogarithmicFunctions
        //
        public static Decimal32 Log(Decimal32 x); // PLATINUM
        public static Decimal32 Log(Decimal32 x, Decimal32 newBase); // PLATINUM
        public static Decimal32 Log10(Decimal32 x); // PLATINUM
        public static Decimal32 LogP1(Decimal32 x); // PLATINUM
        public static Decimal32 Log2(Decimal32 x); // PLATINUM
        public static Decimal32 Log2P1(Decimal32 x); // PLATINUM
        public static Decimal32 Log10P1(Decimal32 x); // PLATINUM

        //
        // IMinMaxValue
        //
        public static Decimal32 MaxValue { get; }
        public static Decimal32 MinValue { get; }

        //
        // IModulusOperators
        //
        public static Decimal32 operator %(Decimal32 left, Decimal32 right);

        //
        // IMultiplicativeIdentity
        //
        public static Decimal32 MultiplicativeIdentity { get; }

        //
        // IMultiplyOperators
        //
        public static Decimal32 operator *(Decimal32 left, Decimal32 right);

        //
        // INumber
        //
        public static Decimal32 Clamp(Decimal32 value, Decimal32 min, Decimal32 max);
        public static Decimal32 CopySign(Decimal32 value, Decimal32 sign);
        public static Decimal32 Max(Decimal32 x, Decimal32 y);
        public static Decimal32 MaxNumber(Decimal32 x, Decimal32 y);
        public static Decimal32 Min(Decimal32 x, Decimal32 y);
        public static Decimal32 MinNumber(Decimal32 x, Decimal32 y);
        public static int Sign(Decimal32 value);

        //
        // INumberBase (well defined/commonly used values)
        //
        public static Decimal32 One { get; }
        static int INumberBase<Decimal32>.Radix; // Note: this ideally should be exposed implicitly as it is required by IEEE
        public static Decimal32 Zero { get; }
        public static Decimal32 Abs(Decimal32 value);
        public static Decimal32 CreateChecked<TOther>(TOther value);
        public static Decimal32 CreateSaturating<TOther>(TOther value);
        public static Decimal32 CreateTruncating<TOther>(TOther value);
        static bool INumberBase<Decimal32>.IsCanonical(Decimal32 value); // Note: this ideally should be exposed implicitly as it is required by IEEE
        static bool INumberBase<Decimal32>.IsComplexNumber(Decimal32 value);
        public static bool IsEvenInteger(Decimal32 value);
        public static bool IsFinite(Decimal32 value);
        static bool INumberBase<Decimal32>.IsImaginaryNumber(Decimal32 value);
        public static bool IsInfinity(Decimal32 value);
        public static bool IsInteger(Decimal32 value);
        public static bool IsNaN(Decimal32 value);
        public static bool IsNegative(Decimal32 value);
        public static bool IsNegativeInfinity(Decimal32 value);
        public static bool IsNormal(Decimal32 value);
        public static bool IsOddInteger(Decimal32 value);
        public static bool IsPositive(Decimal32 value);
        public static bool IsPositiveInfinity(Decimal32 value);
        public static bool IsRealNumber(Decimal32 value);
        public static bool IsSubnormal(Decimal32 value);
        static bool INumberBase<Decimal32>.IsZero(Decimal32 value); // Note: this ideally should be exposed implicitly as it is required by IEEE
        public static Decimal32 MaxMagnitude(Decimal32 x, Decimal32 y);
        public static Decimal32 MaxMagnitudeNumber(Decimal32 x, Decimal32 y);
        public static Decimal32 MinMagnitude(Decimal32 x, Decimal32 y);
        public static Decimal32 MinMagnitudeNumber(Decimal32 x, Decimal32 y);
        static bool INumberBase<Decimal32>.TryConvertFromChecked<TOther>(TOther value, out Decimal32 result);
        static bool INumberBase<Decimal32>.TryConvertFromSaturating<TOther>(TOther value, out Decimal32 result);
        static bool INumberBase<Decimal32>.TryConvertFromTruncating<TOther>(TOther value, out Decimal32 result);
        static bool INumberBase<Decimal32>.TryConvertToChecked<TOther>(Decimal32 value, [MaybeNullWhen(false)] out TOther result);
        static bool INumberBase<Decimal32>.TryConvertToSaturating<TOther>(Decimal32 value, [MaybeNullWhen(false)] out TOther result);
        static bool INumberBase<Decimal32>.TryConvertToTruncating<TOther>(Decimal32 value, [MaybeNullWhen(false)] out TOther result);

        //
        // IPowerFunctions
        //
        public static Decimal32 Pow(Decimal32 x, Decimal32 y); // PLATINUM

        //
        // IRootFunctions
        //
        public static Decimal32 Cbrt(Decimal32 x); // PLATINUM
        public static Decimal32 Hypot(Decimal32 x, Decimal32 y); // PLATINUM
        public static Decimal32 RootN(Decimal32 x, int n); // PLATINUM
        public static Decimal32 Sqrt(Decimal32 x);

        //
        // ISignedNumber
        //
        public static Decimal32 NegativeOne { get; }

        //
        // ISubtractionOperators
        //
        public static Decimal32 operator -(Decimal32 left, Decimal32 right);

        //
        // ITrigonometricFunctions
        //
        public static Decimal32 Acos(Decimal32 x); // PLATINUM
        public static Decimal32 AcosPi(Decimal32 x); // PLATINUM
        public static Decimal32 Asin(Decimal32 x); // PLATINUM
        public static Decimal32 AsinPi(Decimal32 x); // PLATINUM
        public static Decimal32 Atan(Decimal32 x); // PLATINUM
        public static Decimal32 AtanPi(Decimal32 x); // PLATINUM
        public static Decimal32 Cos(Decimal32 x); // PLATINUM
        public static Decimal32 CosPi(Decimal32 x); // PLATINUM
        public static Decimal32 Sin(Decimal32 x); // PLATINUM
        public static (Decimal32 Sin, Decimal32 Cos) SinCos(Decimal32 x); // PLATINUM
        public static (Decimal32 SinPi, Decimal32 CosPi) SinCosPi(Decimal32 x); // PLATINUM
        public static Decimal32 SinPi(Decimal32 x); // PLATINUM
        public static Decimal32 Tan(Decimal32 x); // PLATINUM
        public static Decimal32 TanPi(Decimal32 x); // PLATINUM

        //
        // IUnaryNegationOperators
        //
        public static Decimal32 operator -(Decimal32 value);

        //
        // IUnaryPlusOperators
        //
        public static Decimal32 operator +(Decimal32 value);
    }
}
jeffhandley commented 1 year ago

Although the Decimal32, Decimal64, and Decimal128 APIs have been approved as shown above, we will not be able to finish the implementation, testing, and integration of these types during .NET 8. I'm moving this proposal to Future, and we will consider it during our .NET 9 planning.

Note that this is not being marked as https://github.com/dotnet/runtime/labels/help%20wanted because even with an implementation, the review, testing, and integration work could not be done during this release.

/cc @KTSnowy

KTSnowy commented 1 year ago

Hey @jeffhandley, in the meantime would it be alright if I used the same API described here for our own IEEE decimal library for C#?

Does this API have any license attached to it?

danmoseley commented 1 year ago

You can use the same license the code is under.

KTSnowy commented 1 year ago

@bartonjs I would recommend changing the UInt128 _value to two ulong fields for the Decimal128 API. This would be more consistent with how Int128 and UInt128 are defined (as far as I know).

internal readonly ulong _upper;
internal readonly ulong _lower;

This would allow easier marshalling of the Decimal128 struct to native code, because there's no guarantee that a C compiler has support for a uint128_t or similar. On Windows for example, stdint.h with Microsoft's C compiler does not have support for 128-bit integers.

Using UInt128 directly instead of two ulongs would make marshalling to C/C++ only available on compilers that support 128-bit integers.

bartonjs commented 1 year ago

@KTSnowy I think those are mostly in the proposal to show that the type has internal fields (which makes the type ineligible for "C# Definite Assignment"). The actual implementation will probably take marshalling into account. But, good observation.

eydelrivero commented 7 months ago

Hi!, we're looking forward to being able to use these new decimal data types. We are building a financial application that needs to handle a big numbers (it can be trillions) and also needs to support a high number of decimals, making System.Decimal too small for our purposes. Is there an ETA when we can start seeing this added to the runtime?

MichalPetryka commented 7 months ago

@bartonjs I would recommend changing the UInt128 _value to two ulong fields for the Decimal128 API. This would be more consistent with how Int128 and UInt128 are defined (as far as I know).

internal readonly ulong _upper;
internal readonly ulong _lower;

This would allow easier marshalling of the Decimal128 struct to native code, because there's no guarantee that a C compiler has support for a uint128_t or similar. On Windows for example, stdint.h with Microsoft's C compiler does not have support for 128-bit integers.

Using UInt128 directly instead of two ulongs would make marshalling to C/C++ only available on compilers that support 128-bit integers.

Decimal128 and the others will correspond to the C _Decimal128 family of types in interop scenarios.

RaymondHuy commented 6 months ago

Hi @tannergooding @jeffhandley is it ok for me to work on this, I intend to implement a part of it (without PLATINUM api).

AaronRobinsonMSFT commented 6 months ago

@RaymondHuy You are free to start working on this effort as it has been marked "approved". I would suggest the following to ensure a productive experience.

tannergooding commented 6 months ago

You are free to start working on this effort as it has been marked "approved"

Please note that while this particular issue is fine to pick up, an issue simply being marked "approved" does not mean we would take any PR on the feature. It is at the discretion of the individual area owners and we have explicit guidance specifically targeted towards "big" features like this one asking for coordination with the area owners: https://github.com/dotnet/runtime/blob/main/CONTRIBUTING.md

In an ideal scenario, the process is typically:

  1. An issue exists for a known bug or approved API
  2. An interested user indicates they would like to work on the issue 2A. Issues marked help wanted are particularly applicable here
  3. The area owners indicate what the overall work needed is and provide any guidance as to how it is expected to be implemented, reviewed, etc
  4. The user indicates they are still willing to pick up the issue and it is assigned out to them

In this particular case, there is a known good implementation by Intel that we would like to port to .NET: https://www.netlib.org/misc/intel/ -- It is made available under the BSD 3-Clause License

We can do this work "incrementally" in that there is:

  1. A raw interchange type (simply the type + basic support for IEquatable, IComparable, GetHashCode, ToString, and Parse; similarly to how we exposed System.Half in .NET 5)
  2. Additionally expose barebones arithmetic functionality (add, subtract, multiply, divide, negate, etc)
  3. Additionally expose core math operations (conversions, BitIncrement/Decrement, Abs, Min, Max, etc)
  4. Additionally expose complex math operations (log, exp, sin, cos, tan, etc)

The prioritization of types would be Decimal128, then Decimal64, then Decimal32. The first two are "required" by IEEE 754, the latter is "optional". They are done from largest to smallest because that addresses the most commonly requested user scenario first (give additional math operations on decimal types, which can't be achieved for System.Decimal due to its fixed and limited format) and gives performance as a secondary goal where the smaller types and less precision can be taken advantage of in specialized scenarios.

There are notably two formats that these decimal types can be encoded as:

  1. Decimal Encoding
  2. Binary Encoding

Given we are implementing this in software, following the binary encoding is desirable and is what the Intel based implementation is centered around.

The Intel implementation also uses some fairly large tables in its implementation. Depending on the total impact, we may end up wanting to deviate from that to save space at the cost of some performance. It is something that will need to be measured and decided upon.

If you would still like to pick up the issue, please let me know and I can assign it out. Myself and the other area owners ( @dotnet/area-system-numerics ) will be available to answer questions and help you through the process as needed.

MichalPetryka commented 6 months ago

There are notably two formats that these decimal types can be encoded as:

  1. Decimal Encoding
  2. Binary Encoding

Given we are implementing this in software, following the binary encoding is desirable and is what the Intel based implementation is centered around.

For context, according to LLVMs discourse, GCC and Clang do Binary on XArch and Decimal on other platforms for the C23 _Decimal* types:

There are four modes:

no
DFP support is not enabled.
This is the default for targets other than x86, x86_64, Power, and z/Architecture.

yes
DFP support is enabled, value representation is BID for x86 and x86_64 targets and DPD for all other targets.
This is the default for most x86, x86_64, Power, and z/Architecture targets.

bid
DFP support is enabled, BID is used for value representation.

dpd
DFP support is enabled, DPD is used for value representation.
tannergooding commented 6 months ago

That would be incorrect.

Arm explicitly documents that decimal support is done using the binary based encoding: https://github.com/ARM-software/abi-aa/blob/2982a9f3b512a5bfdc9e3fea5d3b298f9165c36b/aapcs64/aapcs64.rst#decimal-floating-point

The AAPCS permits use of Decimal Floating Point numbers encoded using the BID format as specified in IEEE 754-2008. Unless explicitly noted elsewhere, Decimal floating-point objects should be treated in exactly the same way as (binary) Floating Point objects for the purposes of structure layout, parameter passing, and result return.

Note There is no support in the AArch64 ISA for Decimal Floating Point, so all operations must be emulated in software.

The same goes for the x86-64 SysV ABI specification (used by Linux systems): https://gitlab.com/x86-psABIs/x86-64-ABI

C sizeof alignment (bytes) AMD64 Architecture)
_Decimal32 4 4 32bit BID (IEEE-754R)
_Decimal64 8 8 64bit BID (IEEE-754R)
_Decimal128 16 16 128bit BID (IEEE-754R)

• Arguments of types _Float16, float, double, _Decimal32, _Decimal64 and m64 are in class SSE. • Arguments of types float128, _Decimal128 and __m128 are split into two halves. The least significant ones belong to class SSE, the most significant one to class SSEUP.

The intent of the specification is that any software based implementation is done using the binary encoding where it is more efficient. There is no need for our software based support to then support anything except for the binary based encoding as no platforms should be using it if they provide hardware support, where the decimal based encoding should be used instead and the APIs would be handled instrinsically.

RaymondHuy commented 6 months ago

Thanks for your replies, I have been contributing to System.Text.Json area so the process sounds familiar with me. I intend to start like your suggestion @tannergooding , you can assign me this issue. :wink:

RaymondHuy commented 5 months ago

Hi @tannergooding I wonder about this case:

var number = new Decimal(12345678, 0);

As you see the significand exceeds 7 digits precision. How we should handle it ? throw overflow exception or rounding it to 1234567 * 10^1 like new Decimal(1234567, 1)

tannergooding commented 5 months ago

IEEE 754 requires that inputs are taken as given, computed as if to infinite precision and unbounded range, and then rounded to the nearest representable result.

Thus new Decimal32(12345678, 0) is equivalent to the literal 12345678e0 which is simply 12345678 * 10^0 or just 12345678. Since decimal32 only supports 7 digits, it can only represent 12345670 or 12345680, since the latter is closer to the infinitely precise result, it would be the correct answer. Something like 12345675 which is halfway between both would use tied to even rounding mode and also become 12345680, while 12345685 would become 12345680 as well, not 12345690