Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Int128 and UInt128 data types #67151

Closed
tannergooding opened this issue Mar 25, 2022 · 17 comments
Closed

Add support for Int128 and UInt128 data types #67151

tannergooding opened this issue Mar 25, 2022 · 17 comments
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Numerics
Milestone

Comments

@tannergooding
Copy link
Member

tannergooding commented Mar 25, 2022

Summary

Since the 64-bit computers started becoming prevalent over 20 years ago, many languages and runtimes have opted to expose 128-bit integer types that provide additional range over the 64-bit integer types and additional performance over BigInteger or similar types. These 128-bit integers can often be partially accelerated by the underlying hardware rather than having to fallback against the more complex logic required to support arbitrary precision integers.

Many C/C++ compilers provide an __int128 type as an implementation specific extension. C allows for an official int128_t type. Rust provides i128 as a built in. Various other ecosystems provide their "own" 128-bit library types as well. Potentially most importantly, Int128 is an ABI primitive type that requires runtime support and so it is not sufficient for it to be provided by the community.

Previously it was not possible for the framework to provide such types without simultaneous language support as it would have been a "non-trivial" breaking change for checked operator support to be added later. With C# 11 (which will ship alongside .NET 7) the language is adding support for user-defined checked operators in order to better support generic math and while doing this won't get rid of all the potential breaking changes if the language adds support later, it puts it on the same level as System.Half where previous review had determined that the support for literals, constant folding, and the minor changes in how operators are interpreted/handled would be overall acceptable if the language decided to add support in the future.

API Proposal

namespace System
{
    public readonly struct Int128
        : IComparable,
          IComparable<Int128>,
          IEquatable<Int128>,
          IBinaryInteger<Int128>,
          IMinMaxValue<Int128>,
          ISpanFormattable,
          ISignedNumber<Int128>
    {
        // Constructors

        public Int128(ulong lower, ulong upper);

        // Properties

        public static Int128 AdditiveIdentity { get; }

        public static Int128 MaxValue { get; }
        public static Int128 MinValue { get; }

        public static Int128 MultiplicativeIdentity { get; }

        public static Int128 NegativeOne { get; }
        public static Int128 One { get; }
        public static Int128 Zero { get; }

        // Addition Operators

        public static Int128 operator +(Int128 left, Int128 right);
        public static Int128 operator checked +(Int128 left, Int128 right);

        // Bitwise Operators

        public static Int128 operator &(Int128 left, Int128 right);
        public static Int128 operator |(Int128 left, Int128 right);
        public static Int128 operator ^(Int128 left, Int128 right);
        public static Int128 operator ~(Int128 left, Int128 right);

        // Comparison Operators

        public static bool operator <(Int128 left, Int128 right);
        public static bool operator <=(Int128 left, Int128 right);
        public static bool operator >(Int128 left, Int128 right);
        public static bool operator >=(Int128 left, Int128 right);

        // Conversion From Operators

        public static implicit operator Int128(byte value);
        public static implicit operator Int128(char value);
        public static implicit operator Int128(short value);
        public static implicit operator Int128(int value);
        public static implicit operator Int128(long value);
        public static implicit operator Int128(nint value);
        public static implicit operator Int128(sbyte value);
        public static implicit operator Int128(ushort value);
        public static implicit operator Int128(uint value);
        public static implicit operator Int128(ulong value);
        public static implicit operator Int128(nuint value);

        public static explicit operator Int128(double value);
        public static explicit operator Int128(decimal value);
        public static explicit operator Int128(Half value);
        public static explicit operator Int128(float value);

        public static explicit operator Int128(UInt128 value);

        // Conversion To Operators

        public static explicit operator byte(Int128 value);
        public static explicit operator char(Int128 value);
        public static explicit operator short(Int128 value);
        public static explicit operator int(Int128 value);
        public static explicit operator long(Int128 value);
        public static explicit operator nint(Int128 value);
        public static explicit operator sbyte(Int128 value);
        public static explicit operator ushort(Int128 value);
        public static explicit operator uint(Int128 value);
        public static explicit operator ulong(Int128 value);
        public static explicit operator nuint(Int128 value);

        public static explicit operator double(Int128 value);
        public static explicit operator decimal(Int128 value);
        public static explicit operator Half(Int128 value);
        public static explicit operator float(Int128 value);

        // Decrement Operators

        public static Int128 operator --(Int128 value);
        public static Int128 operator checked --(Int128 value);

        // Division Operators

        public static Int128 operator /(Int128 left, Int128 right);
        public static Int128 operator checked /(Int128 left, Int128 right);

        // Equality Operators

        public static bool operator ==(Int128 left, Int128 right);
        public static bool operator !=(Int128 left, Int128 right);

        // Increment Operators

        public static Int128 operator ++(Int128 value);
        public static Int128 operator checked ++(Int128 value);

        // Modulus Operators

        public static Int128 operator %(Int128 left, Int128 right);

        // Multiply Operators

        public static Int128 operator *(Int128 left, Int128 right);
        public static Int128 operator checked *(Int128 left, Int128 right);

        // Shift Operators

        public static Int128 operator <<(Int128 value, int shiftAmount);
        public static Int128 operator >>(Int128 value, int shiftAmount);
        public static Int128 operator >>>(Int128 value, int shiftAmount);

        // Subtraction Operators

        public static Int128 operator -(Int128 left, Int128 right);
        public static Int128 operator checked -(Int128 left, Int128 right);

        // Unary Negation/Plus Operators

        public static Int128 operator +(Int128 value);
        public static Int128 operator -(Int128 value);
        public static Int128 operator checked -(Int128 value);

        // Comparison Methods

        public int CompareTo(object? obj);
        public int CompareTo(Int128 other);

        // Equality Methods

        public override bool Equals([NotNullWhen(true)] object? obj);
        public bool Equals(Int128 other);

        // Hashing Methods

        public override int GetHashCode();

        // Parsing Methods

        public static Int128 Parse(string s);
        public static Int128 Parse(string s, NumberStyles style);
        public static Int128 Parse(string s, IFormatProvider? provider);
        public static Int128 Parse(string s, NumberStyles style, IFormatProvider? provider);
        public static Int128 Parse(ReadOnlySpan<char> s, IFormatProvider? provider);
        public static Int128 Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null);

        public static bool TryParse([NotNullWhen(true)] string? s, out Int128 result);
        public static bool TryParse(ReadOnlySpan<char> s, out Int128 result);
        public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out long result);
        public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Int128 result);
        public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Int128 result);
        public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out Int128 result);

        // Formatting Methods

        public override string ToString();
        public string ToString(IFormatProvider? provider);
        public string ToString(string? format);
        public string ToString(string? format, IFormatProvider? provider);

        public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null);

        // Binary Integer Methods

        public static Int128 LeadingZeroCount(Int128 value);
        public static Int128 PopCount(Int128 value);
        public static Int128 RotateLeft(Int128 value);
        public static Int128 RotateRight(Int128 value);
        public static Int128 TrailingZeroCount(Int128 value);

        // Binary Number Methods

        public static bool IsPow2(Int128 value);
        public static Int128 Log2(Int128 value);

        // Number Methods

        public static Int128 Abs(Int128 value);
        public static Int128 Clamp(Int128 value, Int128 min, Int128 max);
        public static Int128 CreateChecked<TOther>(TOther value) where TOther : INumber<TOther>;
        public static Int128 CreateSaturating<TOther>(TOther value) where TOther : INumber<TOther>;
        public static Int128 CreateTruncating<TOther>(TOther value) where TOther : INumber<TOther>;
        public static (Int128 Quotient, Int128 Remainder) DivRem(Int128 left, Int128 right);
        public static Int128 Max(Int128 x, Int128 y);
        public static Int128 Min(Int128 x, Int128 y);
        public static Int128 Sign(Int128 value);
        public static bool TryCreate<TOther>(TOther value, out Int128 result) where TOther : INumber<TOther>;
    }

    public readonly struct UInt128
        : IComparable,
          IComparable<UInt128>,
          IEquatable<UInt128>,
          IBinaryInteger<UInt128>,
          IMinMaxValue<UInt128>,
          ISpanFormattable,
          IUnsignedNumber<UInt128>
    {
        // Same members as Int128, but taking/returning UInt128
        // NegativeOne is not defined/exposed
        // Conversion From/To operators differ as per below

        // Conversion From Operators

        public static implicit operator UInt128(byte value);
        public static implicit operator UInt128(char value);
        public static implicit operator UInt128(ushort value);
        public static implicit operator UInt128(uint value);
        public static implicit operator UInt128(ulong value);
        public static implicit operator UInt128(nuint value);

        public static explicit operator UInt128(short value);
        public static explicit operator UInt128(int value);
        public static explicit operator UInt128(long value);
        public static explicit operator UInt128(nint value);
        public static explicit operator UInt128(sbyte value);

        public static explicit operator UInt128(double value);
        public static explicit operator UInt128(decimal value);
        public static explicit operator UInt128(Half value);
        public static explicit operator UInt128(float value);

        public static explicit operator UInt128(Int128 value);

        // Conversion To Operators

        public static explicit operator byte(UInt128 value);
        public static explicit operator char(UInt128 value);
        public static explicit operator short(UInt128 value);
        public static explicit operator int(UInt128 value);
        public static explicit operator long(UInt128 value);
        public static explicit operator nint(UInt128 value);
        public static explicit operator sbyte(UInt128 value);
        public static explicit operator ushort(UInt128 value);
        public static explicit operator uint(UInt128 value);
        public static explicit operator ulong(UInt128 value);
        public static explicit operator nuint(UInt128 value);

        public static explicit operator double(UInt128 value);
        public static explicit operator decimal(UInt128 value);
        public static explicit operator Half(UInt128 value);
        public static explicit operator float(UInt128 value);
    }
}

Additional Notes

The integer primitive types have implicit conversions to double, decimal, and float. However, these conversions are "unsafe" and potentially lossy for values greater than 2^24 (float), 2^53 (double) and 10^28 (decimal). As such they are explicit for Int128 and UInt128. Conversions are therefore implicit where the conversion is lossless and explicit otherwise.

The constructor takes ulong lower, ulong upper. It might be concievable for this to be ulong lower, long upper on Int128 since the upper value carries the sign.

The general INumber<T> interface support should match any design decisions made for https://github.com/dotnet/designs/tree/main/accepted/2021/statics-in-interfaces and be consistent with regards to Int32/Int64 (for Int128) and UInt32/UInt64 (for UInt128). Any inconstencies here are unintentional and likely just due to the timing between when this proposal was written and when various modifications to the generic math proposal were made.

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Numerics untriaged New issue has not been triaged by the area owner labels Mar 25, 2022
@ghost
Copy link

ghost commented Mar 25, 2022

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

Issue Details

Summary

Since the 64-bit computers started becoming prevalent over 20 years ago, many languages and runtimes have opted to expose 128-bit integer types that provide additional range over the 64-bit integer types and additional performance over BigInteger or similar types. These 128-bit integers can often be partially accelerated by the underlying hardware rather than having to fallback against the more complex logic required to support arbitrary precision integers.

Many C/C++ compilers provide an __int128 type as an implementation specific extension. C allows for an official int128_t type. Rust provides i128 as a built in. Various other ecosystems provide their "own" 128-bit library types as well. Potentially most importantly, Int128 is an ABI primitive type that requires runtime support and so it is not sufficient for it to be provided by the community.

Previously it was not possible for the framework to provide such types without simultaneous language support as it would have been a "non-trivial" breaking change for checked operator support to be added later. With C# 11 (which will ship alongside .NET 7) the language is adding support for user-defined checked operators in order to better support generic math and while doing this won't get rid of all the potential breaking changes if the language adds support later, it puts it on the same level as System.Half where previous review had determined that the support for literals, constant folding, and the minor changes in how operators are interpreted/handled would be overall acceptable if the language decided to add support in the future.

API Proposal

namespace System
{
    public readonly struct Int128
        : IComparable,
          IComparable<Int128>,
          IEquatable<Int128>,
          IBinaryInteger<Int128>,
          IMinMaxValue<Int128>,
          ISpanFormattable,
          ISignedNumber<Int128>
    {
        // Properties
    
        public static Int128 AdditiveIdentity { get; }
    
        public static Int128 MaxValue { get; }
        public static Int128 MinValue { get; }
    
        public static Int128 MultiplicativeIdentity { get; }
    
        public static Int128 NegativeOne { get; }
        public static Int128 One { get; }
        public static Int128 Zero { get; }
    
        // Addition Operators
    
        public static Int128 operator +(Int128 left, Int128 right);
        public static Int128 operator checked +(Int128 left, Int128 right);
    
        // Bitwise Operators
    
        public static Int128 operator &(Int128 left, Int128 right);
        public static Int128 operator |(Int128 left, Int128 right);
        public static Int128 operator ^(Int128 left, Int128 right);
        public static Int128 operator ~(Int128 left, Int128 right);
    
        // Comparison Operators
    
        public static bool operator <(Int128 left, Int128 right);
        public static bool operator <=(Int128 left, Int128 right);
        public static bool operator >(Int128 left, Int128 right);
        public static bool operator >=(Int128 left, Int128 right);
    
        // Decrement Operators
    
        public static Int128 operator --(Int128 value);
        public static Int128 operator checked --(Int128 value);
    
        // Division Operators
    
        public static Int128 operator /(Int128 left, Int128 right);
        public static Int128 operator checked /(Int128 left, Int128 right);
    
        // Equality Operators
    
        public static bool operator ==(Int128 left, Int128 right);
        public static bool operator !=(Int128 left, Int128 right);
    
        // Increment Operators
    
        public static Int128 operator ++(Int128 value);
        public static Int128 operator checked ++(Int128 value);
    
        // Modulus Operators
    
        public static Int128 operator %(Int128 left, Int128 right);
    
        // Multiply Operators
    
        public static Int128 operator *(Int128 left, Int128 right);
        public static Int128 operator checked *(Int128 left, Int128 right);
    
        // Shift Operators
    
        public static Int128 operator <<(Int128 value, int shiftAmount);
        public static Int128 operator >>(Int128 value, int shiftAmount);
        public static Int128 operator >>>(Int128 value, int shiftAmount);
    
        // Subtraction Operators
    
        public static Int128 operator -(Int128 left, Int128 right);
        public static Int128 operator checked -(Int128 left, Int128 right);
    
        // Unary Negation/Plus Operators
    
        public static Int128 operator +(Int128 value);
        public static Int128 operator -(Int128 value);
    
        // Comparison Methods
    
        public int CompareTo(object? obj);
        public int CompareTo(Int128 other);
    
        // Equality Methods
    
        public override bool Equals([NotNullWhen(true)] object? obj);
        public bool Equals(Int128 other);
    
        // Hashing Methods
    
        public override int GetHashCode();
    
        // Parsing Methods
    
        public static Int128 Parse(string s);
        public static Int128 Parse(string s, NumberStyles style);
        public static Int128 Parse(string s, IFormatProvider? provider);
        public static Int128 Parse(string s, NumberStyles style, IFormatProvider? provider);
        public static Int128 Parse(ReadOnlySpan<char> s, IFormatProvider? provider);
        public static Int128 Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null);
    
        public static bool TryParse([NotNullWhen(true)] string? s, out Int128 result);
        public static bool TryParse(ReadOnlySpan<char> s, out Int128 result);
        public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out long result);
        public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Int128 result);
        public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Int128 result)
        public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out Int128 result);
    
        // Formatting Methods
    
        public override string ToString();
        public string ToString(IFormatProvider? provider);
        public string ToString(string? format);
        public string ToString(string? format, IFormatProvider? provider);
    
        public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null);
    
        // Binary Integer Methods
    
        public static Int128 LeadingZeroCount(Int128 value);
        public static Int128 PopCount(Int128 value);
        public static Int128 RotateLeft(Int128 value);
        public static Int128 RotateRight(Int128 value);
        public static Int128 TrailingZeroCount(Int128 value);
    
        // Binary Number Methods
    
        public static bool IsPow2(Int128 value);
        public static Int128 Log2(Int128 value);
    
        // Number Methods
    
        public static Int128 Abs(Int128 value);
        public static Int128 Clamp(Int128 value, Int128 min, Int128 max);
        public static Int128 Create<TOther>(TOther value) where TOther : INumber<TOther>;
        public static Int128 CreateSaturating<TOther>(TOther value) where TOther : INumber<TOther>;
        public static Int128 CreateTruncating<TOther>(TOther value) where TOther : INumber<TOther>;
        public static (Int128 Quotient, Int128 Remainder) DivRem(Int128 left, Int128 right);
        public static Int128 Max(Int128 x, Int128 y);
        public static Int128 Min(Int128 x, Int128 y);
        public static Int128 Sign(Int128 value);
        public static bool TryCreate<TOther>(TOther value, out Int128 result) where TOther : INumber<TOTher>;
    }
    
    public readonly struct UInt128
        : IComparable,
          IComparable<UInt128>,
          IEquatable<UInt128>,
          IBinaryInteger<UInt128>,
          IMinMaxValue<UInt128>,
          ISpanFormattable,
          IUnsignedNumber<UInt128>
    {
        // Same members as Int128, but taking/returning UInt128
        // NegativeOne is not defined/exposed
    }
}
Author: tannergooding
Assignees: -
Labels:

area-System.Numerics, untriaged

Milestone: -

@tannergooding tannergooding added api-suggestion Early API idea and discussion, it is NOT ready for implementation and removed untriaged New issue has not been triaged by the area owner labels Mar 25, 2022
@tannergooding
Copy link
Member Author

tannergooding commented Mar 25, 2022

-- Need to add the relevant constructors/conversion operators, and wait for minor initial feedback, before this can be marked as ready-for-review

@tannergooding tannergooding self-assigned this Mar 25, 2022
@tannergooding tannergooding added this to the Future milestone Mar 25, 2022
@AraHaan
Copy link
Member

AraHaan commented Mar 25, 2022

Note: there is an missing ; in the proposed code sample above.

@JakeSays
Copy link

I was just looking for an Int128 to use in an implementation of 64 bit decimal math.

@danmoseley
Copy link
Member

Note: there is an missing ; in the proposed code sample above.

Fixed.

@pentp
Copy link
Contributor

pentp commented Mar 30, 2022

Unary Negation would also need a checked variant to match current integer types.

@tannergooding tannergooding added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Apr 4, 2022
@tannergooding
Copy link
Member Author

Updated to include the constructors, checked unary negation, and relevant conversion APIs.

@huoyaoyuan
Copy link
Member

What will be it's ABI? Treat it as a normal struct, or spill into two integer registers like some C++ compilers?

@terrajobst
Copy link
Member

  • We should swap the constructor parameter
    • This way a logical 128 bit literal like 0x8000_0000_0000_0000_0000_0000_0000_0000 would be written like new Int128(0x8000_0000_0000_0000, 0x0000_0000_0000_0000)
  • We probably want to file bugs for serializers to consider supporting this type
namespace System
{
    public readonly struct Int128
        : IComparable,
          IComparable<Int128>,
          IEquatable<Int128>,
          IBinaryInteger<Int128>,
          IMinMaxValue<Int128>,
          ISpanFormattable,
          ISignedNumber<Int128>
    {
        // Constructors

        public Int128(ulong upper, ulong lower);

        // Properties

        public static Int128 AdditiveIdentity { get; }

        public static Int128 MaxValue { get; }
        public static Int128 MinValue { get; }

        public static Int128 MultiplicativeIdentity { get; }

        public static Int128 NegativeOne { get; }
        public static Int128 One { get; }
        public static Int128 Zero { get; }

        // Addition Operators

        public static Int128 operator +(Int128 left, Int128 right);
        public static Int128 operator checked +(Int128 left, Int128 right);

        // Bitwise Operators

        public static Int128 operator &(Int128 left, Int128 right);
        public static Int128 operator |(Int128 left, Int128 right);
        public static Int128 operator ^(Int128 left, Int128 right);
        public static Int128 operator ~(Int128 left, Int128 right);

        // Comparison Operators

        public static bool operator <(Int128 left, Int128 right);
        public static bool operator <=(Int128 left, Int128 right);
        public static bool operator >(Int128 left, Int128 right);
        public static bool operator >=(Int128 left, Int128 right);

        // Conversion From Operators

        public static implicit operator Int128(byte value);
        public static implicit operator Int128(char value);
        public static implicit operator Int128(short value);
        public static implicit operator Int128(int value);
        public static implicit operator Int128(long value);
        public static implicit operator Int128(nint value);
        public static implicit operator Int128(sbyte value);
        public static implicit operator Int128(ushort value);
        public static implicit operator Int128(uint value);
        public static implicit operator Int128(ulong value);
        public static implicit operator Int128(nuint value);

        public static explicit operator Int128(double value);
        public static explicit operator Int128(decimal value);
        public static explicit operator Int128(Half value);
        public static explicit operator Int128(float value);

        public static explicit operator Int128(UInt128 value);

        // Conversion To Operators

        public static explicit operator byte(Int128 value);
        public static explicit operator char(Int128 value);
        public static explicit operator short(Int128 value);
        public static explicit operator int(Int128 value);
        public static explicit operator long(Int128 value);
        public static explicit operator nint(Int128 value);
        public static explicit operator sbyte(Int128 value);
        public static explicit operator ushort(Int128 value);
        public static explicit operator uint(Int128 value);
        public static explicit operator ulong(Int128 value);
        public static explicit operator nuint(Int128 value);

        public static explicit operator double(Int128 value);
        public static explicit operator decimal(Int128 value);
        public static explicit operator Half(Int128 value);
        public static explicit operator float(Int128 value);

        // Decrement Operators

        public static Int128 operator --(Int128 value);
        public static Int128 operator checked --(Int128 value);

        // Division Operators

        public static Int128 operator /(Int128 left, Int128 right);
        public static Int128 operator checked /(Int128 left, Int128 right);

        // Equality Operators

        public static bool operator ==(Int128 left, Int128 right);
        public static bool operator !=(Int128 left, Int128 right);

        // Increment Operators

        public static Int128 operator ++(Int128 value);
        public static Int128 operator checked ++(Int128 value);

        // Modulus Operators

        public static Int128 operator %(Int128 left, Int128 right);

        // Multiply Operators

        public static Int128 operator *(Int128 left, Int128 right);
        public static Int128 operator checked *(Int128 left, Int128 right);

        // Shift Operators

        public static Int128 operator <<(Int128 value, int shiftAmount);
        public static Int128 operator >>(Int128 value, int shiftAmount);
        public static Int128 operator >>>(Int128 value, int shiftAmount);

        // Subtraction Operators

        public static Int128 operator -(Int128 left, Int128 right);
        public static Int128 operator checked -(Int128 left, Int128 right);

        // Unary Negation/Plus Operators

        public static Int128 operator +(Int128 value);
        public static Int128 operator -(Int128 value);
        public static Int128 operator checked -(Int128 value);

        // Comparison Methods

        public int CompareTo(object? obj);
        public int CompareTo(Int128 other);

        // Equality Methods

        public override bool Equals([NotNullWhen(true)] object? obj);
        public bool Equals(Int128 other);

        // Hashing Methods

        public override int GetHashCode();

        // Parsing Methods

        public static Int128 Parse(string s);
        public static Int128 Parse(string s, NumberStyles style);
        public static Int128 Parse(string s, IFormatProvider? provider);
        public static Int128 Parse(string s, NumberStyles style, IFormatProvider? provider);
        public static Int128 Parse(ReadOnlySpan<char> s, IFormatProvider? provider);
        public static Int128 Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null);

        public static bool TryParse([NotNullWhen(true)] string? s, out Int128 result);
        public static bool TryParse(ReadOnlySpan<char> s, out Int128 result);
        public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out long result);
        public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Int128 result);
        public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Int128 result);
        public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out Int128 result);

        // Formatting Methods

        public override string ToString();
        public string ToString(IFormatProvider? provider);
        public string ToString(string? format);
        public string ToString(string? format, IFormatProvider? provider);

        public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null);

        // Binary Integer Methods

        public static Int128 LeadingZeroCount(Int128 value);
        public static Int128 PopCount(Int128 value);
        public static Int128 RotateLeft(Int128 value);
        public static Int128 RotateRight(Int128 value);
        public static Int128 TrailingZeroCount(Int128 value);

        // Binary Number Methods

        public static bool IsPow2(Int128 value);
        public static Int128 Log2(Int128 value);

        // Number Methods

        public static Int128 Abs(Int128 value);
        public static Int128 Clamp(Int128 value, Int128 min, Int128 max);
        public static Int128 CreateChecked<TOther>(TOther value) where TOther : INumber<TOther>;
        public static Int128 CreateSaturating<TOther>(TOther value) where TOther : INumber<TOther>;
        public static Int128 CreateTruncating<TOther>(TOther value) where TOther : INumber<TOther>;
        public static (Int128 Quotient, Int128 Remainder) DivRem(Int128 left, Int128 right);
        public static Int128 Max(Int128 x, Int128 y);
        public static Int128 Min(Int128 x, Int128 y);
        public static Int128 Sign(Int128 value);
        public static bool TryCreate<TOther>(TOther value, out Int128 result) where TOther : INumber<TOther>;
    }

    public readonly struct UInt128
        : IComparable,
          IComparable<UInt128>,
          IEquatable<UInt128>,
          IBinaryInteger<UInt128>,
          IMinMaxValue<UInt128>,
          ISpanFormattable,
          IUnsignedNumber<UInt128>
    {
        // Same members as Int128, but taking/returning UInt128
        // NegativeOne is not defined/exposed
        // Conversion From/To operators differ as per below

        // Conversion From Operators

        public static implicit operator UInt128(byte value);
        public static implicit operator UInt128(char value);
        public static implicit operator UInt128(ushort value);
        public static implicit operator UInt128(uint value);
        public static implicit operator UInt128(ulong value);
        public static implicit operator UInt128(nuint value);

        public static explicit operator UInt128(short value);
        public static explicit operator UInt128(int value);
        public static explicit operator UInt128(long value);
        public static explicit operator UInt128(nint value);
        public static explicit operator UInt128(sbyte value);

        public static explicit operator UInt128(double value);
        public static explicit operator UInt128(decimal value);
        public static explicit operator UInt128(Half value);
        public static explicit operator UInt128(float value);

        public static explicit operator UInt128(Int128 value);

        // Conversion To Operators

        public static explicit operator byte(UInt128 value);
        public static explicit operator char(UInt128 value);
        public static explicit operator short(UInt128 value);
        public static explicit operator int(UInt128 value);
        public static explicit operator long(UInt128 value);
        public static explicit operator nint(UInt128 value);
        public static explicit operator sbyte(UInt128 value);
        public static explicit operator ushort(UInt128 value);
        public static explicit operator uint(UInt128 value);
        public static explicit operator ulong(UInt128 value);
        public static explicit operator nuint(UInt128 value);

        public static explicit operator double(UInt128 value);
        public static explicit operator decimal(UInt128 value);
        public static explicit operator Half(UInt128 value);
        public static explicit operator float(UInt128 value);
    }
}

@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Apr 14, 2022
@JakeSays
Copy link

JakeSays commented Apr 15, 2022

Is there an implementation of this or is it still in the planning stages? If there isn't I'd be interested in working on one.

@tannergooding
Copy link
Member Author

Thanks for the offer. However, this is one I've already gotten a good bit of implementation done on locally and which will require some special support in the VM and potentially the JIT; so I'll be driving it alongside the rest of the generic math related work.

@JakeSays
Copy link

Ok no problem:) I'm looking forward to having it. BTW unrelated to uint128, but has a 64bit decimal ever been discussed?

@LukaszRozmej
Copy link

We are looking forward to this to speed up our Int/UInt256 implementation used on Ethereum: https://github.com/NethermindEth/int256 , what kind of speed-up could we expect compared to using 2xlong fields?

@AraHaan
Copy link
Member

AraHaan commented May 10, 2022

considerable improvement I think.

@tannergooding
Copy link
Member Author

This was done in #69204

It currently just has a general software implementation. Additional acceleration will come with time but may be .NET 8 depending on timing.

@LEI-Hongfaan
Copy link
Contributor

We already have MinusOne on BigInteger and Decimal. Why come up with a public NegativeOne now? @tannergooding

@tannergooding
Copy link
Member Author

@LEI-Hongfaan, MinusOne was a name decided on 20 years ago in .NET Framework 1.0 that is inconsistent, overall, with the naming and terminology used elsewhere.

NegativeOne (and Negative in general) was the name decided on for Generic Math and the standardized name to use moving forward when referring to negative numbers.

@ghost ghost locked as resolved and limited conversation to collaborators Jul 17, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Numerics
Projects
None yet
Development

No branches or pull requests

9 participants