-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Comments
Tagging subscribers to this area: @dotnet/area-system-numerics Issue DetailsSummarySince 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 Many C/C++ compilers provide an 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 API Proposalnamespace 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
}
}
|
-- Need to add the relevant constructors/conversion operators, and wait for minor initial feedback, before this can be marked as |
Note: there is an missing |
I was just looking for an Int128 to use in an implementation of 64 bit decimal math. |
Fixed. |
Unary Negation would also need a checked variant to match current integer types. |
Updated to include the constructors, checked unary negation, and relevant conversion APIs. |
What will be it's ABI? Treat it as a normal struct, or spill into two integer registers like some C++ compilers? |
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);
}
} |
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. |
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. |
Ok no problem:) I'm looking forward to having it. BTW unrelated to uint128, but has a 64bit decimal ever been discussed? |
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? |
considerable improvement I think. |
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. |
We already have MinusOne on BigInteger and Decimal. Why come up with a public NegativeOne now? @tannergooding |
@LEI-Hongfaan,
|
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 officialint128_t
type. Rust providesi128
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 foruser-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 asSystem.Half
where previous review had determined that the support forliterals
,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
Additional Notes
The integer primitive types have
implicit
conversions todouble
,decimal
, andfloat
. However, these conversions are "unsafe" and potentially lossy for values greater than2^24
(float
),2^53
(double
) and10^28
(decimal
). As such they are explicit forInt128
andUInt128
. 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 beulong lower, long upper
onInt128
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 toInt32/Int64
(forInt128
) andUInt32/UInt64
(forUInt128
). Any inconstencies here are unintentional and likely just due to the timing between when this proposal was written and when various modifications to thegeneric math
proposal were made.The text was updated successfully, but these errors were encountered: