Skip to content

Add support for Int128 and UInt128 data types #67151

Closed

Description

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions