I have created Int128
which is based off of BigInteger
with operator overloading to handle larger math requirements. Here is a look at my Int128
implementation.
/// <summary>
/// Creates a new <see cref="Int128"/>
/// </summary>
public readonly struct Int128(long high, ulong low) :
IComparable, IComparable<Int128>, IEquatable<Int128>
{
private readonly BigInteger Value = ((BigInteger)high << 64) | low;
/// <summary>
/// Used for our ++ and -- operators
/// </summary>
private static readonly Int128 Increment_Deincrement = new(1);
/// <summary>
/// Creates a new <see cref="Int128"/> with
/// only <paramref name="low"/> bits set
/// </summary>
/// <param name="low"></param>
public Int128(ulong low) : this(0, low)
{
}
/// <summary>
/// Used only for our <see cref="operator"/> overloads.
/// Low and high bits don't matter here as it most
/// likely will exceed what can they can represent.
/// </summary>
private Int128(BigInteger value) : this(0, 0)
{
Value = value;
}
public static Int128 operator ++(Int128 value)
=> value + Increment_Deincrement;
public static Int128 operator --(Int128 value)
=> value - Increment_Deincrement;
public static Int128 operator +(Int128 left, Int128 right)
=> new((BigInteger)(left.Value + right.Value));
public static Int128 operator -(Int128 left, Int128 right)
=> new((BigInteger)(left.Value - right.Value));
public static Int128 operator *(Int128 left, Int128 right)
=> new((BigInteger)(left.Value * right.Value));
public static Int128 operator /(Int128 left, Int128 right)
{
return right.Value == 0 ? default : new((BigInteger)(left.Value / right.Value));
}
public static Int128 operator %(Int128 left, Int128 right)
=> new((BigInteger)(left.Value % right.Value));
public static bool operator ==(Int128 left, Int128 right)
=> left.Value == right.Value;
public static bool operator !=(Int128 left, Int128 right)
=> !(left == right);
public static bool operator <(Int128 left, Int128 right)
=> left.Value < right.Value;
public static bool operator <=(Int128 left, Int128 right)
=> left.Value <= right.Value;
public static bool operator >(Int128 left, Int128 right)
=> left.Value > right.Value;
public static bool operator >=(Int128 left, Int128 right)
=> left.Value >= right.Value;
public override bool Equals(object? obj)
=> obj != null && obj is Int128 other && Equals(other);
public bool Equals(Int128 other)
=> Value.Equals(other.Value);
public override int GetHashCode()
=> Value.GetHashCode();
public int CompareTo(object? obj)
=> obj != null && obj is Int128 other ? CompareTo(other) : 1;
public int CompareTo(Int128 other) => Value.CompareTo(other.Value);
public override string ToString() => Value.ToString();
public string ToString(string format) => Value.ToString(format);
}
1 Answer 1
/// <summary> /// Used only for our <see cref="operator"/> overloads. /// Low and high bits don't matter here as it most /// likely will exceed what can they can represent. /// </summary> private Int128(BigInteger value) : this(0, 0) { Value = value; }
If the value
passed in is outside the range of an Int128
, then this constructor constructs an instance of this struct in an invalid state. The comment even acknowledges that.
Since this is a private constructor, that may be OK as long as every use of it is careful to pass an in-range value. But the arithmetic operators do not restrict the range, so you can very easily leave the range that an Int128
would have without the expected "wrapping" behaviour.
In effect, that makes this struct a BigInteger
again, there's no "Int128-like behaviour" except offering a constructor that takes two 64-bit chunks to build the value out of. In almost every way this struct acts like a BigInteger
.
However, it's a version of BigInteger
that allows division by zero (but not remainder by zero), and it supports fewer operations.
Casts
Casting a BigInteger
to a BigInteger
is not useful. Many of the arithmetic operators do that.
-
\$\begingroup\$ There is no upper bound for BigInteger, it grows as much as your system memory allows. The larger the number the slower it is \$\endgroup\$Short Int– Short Int2025年02月08日 20:39:21 +00:00Commented Feb 8 at 20:39
-
2\$\begingroup\$ @CharlesHenington well yes, I know what a BigInteger is \$\endgroup\$user555045– user5550452025年02月08日 20:41:29 +00:00Commented Feb 8 at 20:41
-
\$\begingroup\$ The comment you mention, that the BigInteger will exceed what the high and low can represent but not what the BigInteger represents. If I'm misunderstanding you please let me know, thanks. \$\endgroup\$Short Int– Short Int2025年02月08日 21:26:17 +00:00Commented Feb 8 at 21:26
-
2\$\begingroup\$ @CharlesHenington that's right but my point is it makes this Int128 struct just act like it is a BigInteger again, which is not how an Int128 would behave \$\endgroup\$user555045– user5550452025年02月08日 21:30:28 +00:00Commented Feb 8 at 21:30
Int128
struct. Why is this insufficient for you? What does your implementation do to justify its existence? \$\endgroup\$