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.