[API Proposal]: Add System.Numerics.BigNumber
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Background and motivation
Interchange formats can carry finite base-10 values outside the ranges of the primitive numeric types. Callers currently preserve these values as text or maintain an ad hoc `BigInteger` significand and scale; neither provides shared parsing, conversion, numeric equality, or ordering.
`BigNumber` is an exchange type only. Arithmetic and generic-math number contracts are explicitly out of scope, leaving `BigDecimal` and `BigFloat` available for future arithmetic types. Related: #125611, #20681, #83907, #118134.
### API Proposal
Parsing and formatting are invariant, and provider arguments are ignored. Numerically equivalent representations such as `1`, `1.0`, and `10e-1` compare equal and produce the same hash code. Signed zero preserves its sign but compares equal to zero.
```csharp
namespace System.Numerics;
public readonly struct BigNumber :
IComparable,
IComparable,
IEquatable,
IFormattable
#if NET
, ISpanFormattable
, IParsable
, ISpanParsable
, IUtf8SpanFormattable
, IUtf8SpanParsable
, IComparisonOperators
, IEqualityOperators
#endif
{
public static BigNumber Zero { get; }
public bool IsZero { get; }
public bool IsNegative { get; }
public bool IsInteger { get; }
public int CompareTo(BigNumber other);
public int CompareTo(object? obj);
public bool Equals(BigNumber other);
public override bool Equals(object? obj);
public override int GetHashCode();
public static bool operator ==(BigNumber left, BigNumber right);
public static bool operator !=(BigNumber left, BigNumber right);
public static bool operator <(BigNumber left, BigNumber right);
public static bool operator <=(BigNumber left, BigNumber right);
public static bool operator >(BigNumber left, BigNumber right);
public static bool operator >=(BigNumber left, BigNumber right);
public static implicit operator BigNumber(byte value);
[CLSCompliant(false)] public static implicit operator BigNumber(sbyte value);
public static implicit operator BigNumber(short value);
[CLSCompliant(false)] public static implicit operator BigNumber(ushort value);
public static implicit operator BigNumber(int value);
[CLSCompliant(false)] public static implicit operator BigNumber(uint value);
public static implicit operator BigNumber(long value);
[CLSCompliant(false)] public static implicit operator BigNumber(ulong value);
public static implicit operator BigNumber(decimal value);
public static explicit operator BigNumber(float value);
public static explicit operator BigNumber(double value);
#if NET
public static explicit operator BigNumber(Half value);
public static implicit operator BigNumber(Int128 value);
[CLSCompliant(false)] public static implicit operator BigNumber(UInt128 value);
#endif
public static explicit operator byte(BigNumber value);
[CLSCompliant(false)] public static explicit operator sbyte(BigNumber value);
public static explicit operator short(BigNumber value);
[CLSCompliant(false)] public static explicit operator ushort(BigNumber value);
public static explicit operator int(BigNumber value);
[CLSCompliant(false)] public static explicit operator uint(BigNumber value);
public static explicit operator long(BigNumber value);
[CLSCompliant(false)] public static explicit operator ulong(BigNumber value);
public static explicit operator decimal(BigNumber value);
public static explicit operator float(BigNumber value);
public static explicit operator double(BigNumber value);
#if NET
public static explicit operator Half(BigNumber value);
public static explicit operator Int128(BigNumber value);
[CLSCompliant(false)] public static explicit operator UInt128(BigNumber value);
#endif
public bool TryGetByte(out byte value);
[CLSCompliant(false)] public bool TryGetSByte(out sbyte value);
public bool TryGetInt16(out short value);
[CLSCompliant(false)] public bool TryGetUInt16(out ushort value);
public bool TryGetInt32(out int value);
[CLSCompliant(false)] public bool TryGetUInt32(out uint value);
public bool TryGetInt64(out long value);
[CLSCompliant(false)] public bool TryGetUInt64(out ulong value);
public bool TryGetDecimal(out decimal value);
public bool TryGetSingle(out float value);
public bool TryGetDouble(out double value);
#if NET
public bool TryGetHalf(out Half value);
public bool TryGetInt128(out Int128 value);
[CLSCompliant(false)] public bool TryGetUInt128(out UInt128 value);
#endif
public static BigNumber Parse(string text);
public static BigNumber Parse(string text, IFormatProvider? provider);
public static BigNumber Parse(ReadOnlySpan text);
public static BigNumber Parse(ReadOnlySpan text, IFormatProvider? provider);
public static BigNumber Parse(ReadOnlySpan utf8Text);
public static BigNumber Parse(ReadOnlySpan utf8Text, IFormatProvider? provider);
public static bool TryParse([NotNullWhen(true)] string? text, out BigNumber result);
public static bool TryParse([NotNullWhen(true)] string? text, IFormatProvider? provider, out BigNumber result);
public static bool TryParse(ReadOnlySpan text, out BigNumber result);
public static bool TryParse(ReadOnlySpan text, IFormatProvider? provider, out BigNumber result);
public static bool TryParse(ReadOnlySpan utf8Text, out BigNumber result);
public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out BigNumber result);
public override string ToString();
public string ToString(string? format, IFormatProvider? formatProvider);
public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null);
public bool TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format = default, IFormatProvider? provider = null);
}
```
`Half`, `Int128`, `UInt128`, and the generic parsing, formatting, and operator interfaces are available on current .NET only. The remaining surface is provided to downlevel targets, including `netstandard2.0`, through `Microsoft.Bcl.Numerics`.
Prototype: https://github.com/eiriktsarpalis/runtime/commit/9599cd11e6ae811605e8ae49481588244cb72140
### API Usage
```csharp
BigNumber value = BigNumber.Parse("100000000000000000000000000000.25");
BigNumber limit = BigNumber.Parse("1e29");
bool exceedsLimit = value > limit;
bool fitsInDecimal = value.TryGetDecimal(out decimal decimalValue);
string roundTrip = value.ToString("R", null);
```
### Alternative Designs
- Raw text preserves the wire representation but provides no numeric equality, ordering, or checked conversion.
- A `BigInteger` plus scale requires every consumer to implement parsing and normalization.
- `BigDecimal` or `BigFloat` would imply arithmetic semantics that this type deliberately does not provide.
### Risks
The significand is arbitrary precision, but the prototype bounds the base-10 exponent to `±decimal.MaxValue`. The downlevel implementation and current-.NET type forwarding through `Microsoft.Bcl.Numerics` also become part of the compatibility contract.
### Usage in dotnet/runtime
No runtime consumers are changed by the prototype. Sharing parts of `NumberBuffer` or adding CBOR integration may be evaluated separately; primitive numeric parsing remains bespoke.
> [!NOTE]
> This proposal was drafted with GitHub Copilot.
Contributor guide
Research direction
Start by reviewing the linked prototype commit and the related issues, then examine the proposed compatibility split between current .NET targets and Microsoft.Bcl.Numerics. Assess the full BigNumber API, exponent limits, normalization, conversions, and type-forwarding implications; done means the proposal's design and compatibility contract are resolved before implementation begins.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100