Microsoft.Azure.Cosmos/src/UInt128.cs (311 lines of code) (raw):
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Linq;
using System.Runtime.InteropServices;
/// <summary>
/// Struct that represents a 128 bit unsigned integer.
/// </summary>
internal readonly struct UInt128 : IComparable, IComparable<UInt128>, IEquatable<UInt128>
{
/// <summary>
/// The length of this struct in bytes.
/// </summary>
public const int Length = 16;
/// <summary>
/// Maximum UInt128.
/// </summary>
public static readonly UInt128 MaxValue = new UInt128(ulong.MaxValue, ulong.MaxValue);
/// <summary>
/// Maximum UInt128.
/// </summary>
public static readonly UInt128 MinValue = 0;
/// <summary>
/// The lowest 64 bits of the UInt128.
/// </summary>
private readonly ulong low;
/// <summary>
/// The highest 64 bits of the UInt128.
/// </summary>
private readonly ulong high;
/// <summary>
/// Initializes a new instance of the <see cref="UInt128"/> struct.
/// </summary>
/// <param name="low">The lowest 64 bits of the UInt128.</param>
/// <param name="high">The highest 64 bits of the UInt128.</param>
private UInt128(ulong low, ulong high)
{
this.low = low;
this.high = high;
}
/// <summary>
/// Implicitly converts an int to UInt128.
/// </summary>
/// <param name="value">The int to convert.</param>
public static implicit operator UInt128(int value)
{
return new UInt128((ulong)value, 0);
}
/// <summary>
/// Implicitly converts a long to UInt128.
/// </summary>
/// <param name="value">The int to convert.</param>
public static implicit operator UInt128(long value)
{
return new UInt128((ulong)value, 0);
}
/// <summary>
/// Implicitly converts an unsigned int to UInt128.
/// </summary>
/// <param name="value">The unsigned int to convert.</param>
public static implicit operator UInt128(uint value)
{
return new UInt128((ulong)value, 0);
}
/// <summary>
/// Implicitly converts an unsigned long to UInt128.
/// </summary>
/// <param name="value">The unsigned long to convert.</param>
public static implicit operator UInt128(ulong value)
{
return new UInt128(value, 0);
}
/// <summary>
/// Adds two instances of UInt128 together.
/// </summary>
/// <param name="augend">The augend.</param>
/// <param name="addend">The addend.</param>
/// <returns>The augend + addend.</returns>
public static UInt128 operator +(UInt128 augend, UInt128 addend)
{
ulong low = augend.low + addend.low;
ulong high = augend.high + addend.high;
if (low < augend.low)
{
high++;
}
return new UInt128(low, high);
}
/// <summary>
/// Takes the difference between two UInt128.
/// </summary>
/// <param name="minuend">The minuend.</param>
/// <param name="subtrahend">The subtrahend.</param>
/// <returns>minuend - subtrahend.</returns>
public static UInt128 operator -(UInt128 minuend, UInt128 subtrahend)
{
ulong low = minuend.low - subtrahend.low;
ulong high = minuend.high - subtrahend.high;
if (low > minuend.low)
{
high--;
}
return new UInt128(low, high);
}
/// <summary>
/// Multiplies two UInt128s together.
/// </summary>
/// <param name="multiplicand">The multiplicand.</param>
/// <param name="multiplier">The multiplier</param>
/// <returns>The multiplication of the two UInt128s.</returns>
public static UInt128 operator *(UInt128 multiplicand, UInt128 multiplier)
{
(UInt128 high, UInt128 low) = UInt128.Mult128To256(multiplicand, multiplier);
if (high != 0)
{
throw new OverflowException();
}
return low;
}
private static (ulong h, ulong l) Mult64To128(ulong u, ulong v)
{
//https://www.codeproject.com/Tips/618570/UInt-Multiplication-Squaring
ulong u1 = u & 0xffffffff;
ulong v1 = v & 0xffffffff;
ulong t = u1 * v1;
ulong w3 = t & 0xffffffff;
ulong k = t >> 32;
u >>= 32;
t = (u * v1) + k;
k = t & 0xffffffff;
ulong w1 = t >> 32;
v >>= 32;
t = (u1 * v) + k;
k = t >> 32;
ulong h = (u * v) + w1 + k;
ulong l = (t << 32) + w3;
return (h, l);
}
private static (UInt128 h, UInt128 l) Mult128To256(UInt128 n, UInt128 m)
{
//https://www.codeproject.com/Tips/618570/UInt-Multiplication-Squaring
// Step 1
(ulong hh, ulong hl) = Mult64To128(n.high, m.high);
(ulong lh, ulong ll) = Mult64To128(n.low, m.low);
// Step 2
{
(ulong th, ulong tl) = Mult64To128(n.high, m.low);
lh += tl;
if (lh < tl)
{
// lh overflowed;
UInt128 hInc = UInt128.Create(hl, hh) + 1;
hh = hInc.high;
hl = hInc.low;
}
hl += th;
if (hl < th)
{
// hl overflowed
hh++;
}
}
// Step 3
{
(ulong th, ulong tl) = Mult64To128(n.low, m.high);
lh += tl;
if (lh < tl)
{
// lh overflowed;
UInt128 hInc = UInt128.Create(hl, hh) + 1;
hh = hInc.high;
hl = hInc.low;
}
hl += th;
if (hl < th)
{
// hl overflowed
hh++;
}
}
return (UInt128.Create(hl, hh), UInt128.Create(ll, lh));
}
/// <summary>
/// Divides one UInt128 by another UInt128
/// </summary>
/// <param name="dividend">The dividend.</param>
/// <param name="divisor">The divisor</param>
/// <returns>The multiplication of the two UInt128s.</returns>
public static UInt128 operator /(UInt128 dividend, UInt128 divisor)
{
if (divisor == 0)
{
throw new DivideByZeroException();
}
if (divisor > uint.MaxValue)
{
throw new ArgumentOutOfRangeException($"{divisor} must be less than 32 bits.");
}
uint divisor32 = (uint)divisor.low;
// let high be represented as 2 32 bit numbers, h1h2
// let low be represented as 2 32 bit numbers, l1l2
if (dividend == 0)
{
return UInt128.Create(0, 0);
}
ulong h1 = dividend.high >> 32;
ulong h2 = dividend.high & 0xffffffff;
ulong l1 = dividend.low >> 32;
ulong l2 = dividend.low & 0xffffffff;
ulong result = h1;
h1 = (result / divisor32) & 0xffffffff;
result = ((result % divisor32) << 32) + h2;
h2 = (result / divisor32) & 0xffffffff;
result = ((result % divisor32) << 32) + l1;
l1 = (result / divisor32) & 0xffffffff;
result = ((result % divisor32) << 32) + l2;
l2 = (result / divisor32) & 0xffffffff;
ulong nhigh = (h1 << 32) + h2;
ulong nlow = (l1 << 32) + l2;
return UInt128.Create(nlow, nhigh);
}
/// <summary>
/// Returns if one UInt128 is less than another UInt128.
/// </summary>
/// <param name="left">The left hand side of the operator.</param>
/// <param name="right">The right hand side of the operator.</param>
/// <returns>Whether left is less than right.</returns>
public static bool operator <(UInt128 left, UInt128 right)
{
return (left.high < right.high)
|| ((left.high == right.high) && (left.low < right.low));
}
/// <summary>
/// Returns if one UInt128 is greater than another UInt128.
/// </summary>
/// <param name="left">The left hand side of the operator.</param>
/// <param name="right">The right hand side of the operator.</param>
/// <returns>Whether left is greater than right.</returns>
public static bool operator >(UInt128 left, UInt128 right)
{
return right < left;
}
/// <summary>
/// Returns if one UInt128 is less than or equal to another UInt128.
/// </summary>
/// <param name="left">The left hand side of the operator.</param>
/// <param name="right">The right hand side of the operator.</param>
/// <returns>Whether left is less than or equal to the right.</returns>
public static bool operator <=(UInt128 left, UInt128 right)
{
return !(right < left);
}
/// <summary>
/// Returns if one UInt128 is greater than or equal to another UInt128.
/// </summary>
/// <param name="left">The left hand side of the operator.</param>
/// <param name="right">The right hand side of the operator.</param>
/// <returns>Whether left is greater than or equal to the right.</returns>
public static bool operator >=(UInt128 left, UInt128 right)
{
return !(left < right);
}
/// <summary>
/// Returns if two UInt128 are equal.
/// </summary>
/// <param name="left">The left hand side of the operator.</param>
/// <param name="right">The right hand side of the operator.</param>
/// <returns>Whether the left is equal to the right.</returns>
public static bool operator ==(UInt128 left, UInt128 right)
{
return (left.high == right.high) && (left.low == right.low);
}
/// <summary>
/// Returns if two UInt128 are not equal.
/// </summary>
/// <param name="left">The left hand side of the operator.</param>
/// <param name="right">The right hand side of the operator.</param>
/// <returns>Whether the left is not equal to the right.</returns>
public static bool operator !=(UInt128 left, UInt128 right)
{
return !(left == right);
}
/// <summary>
/// Takes the bitwise and of two instance of UInt128.
/// </summary>
/// <param name="left">The left hand side of the operator.</param>
/// <param name="right">The right hand side of the operator.</param>
/// <returns>The bitwise and of two instance of UInt128..</returns>
public static UInt128 operator &(UInt128 left, UInt128 right)
{
return new UInt128(left.low & right.low, left.high & right.high);
}
/// <summary>
/// Takes the bitwise or of two instance of UInt128.
/// </summary>
/// <param name="left">The left hand side of the operator.</param>
/// <param name="right">The right hand side of the operator.</param>
/// <returns>The bitwise or of two instance of UInt128..</returns>
public static UInt128 operator |(UInt128 left, UInt128 right)
{
return new UInt128(left.low | right.low, left.high | right.high);
}
/// <summary>
/// Takes the bitwise x or of two instance of UInt128.
/// </summary>
/// <param name="left">The left hand side of the operator.</param>
/// <param name="right">The right hand side of the operator.</param>
/// <returns>The bitwise x or of two instance of UInt128..</returns>
public static UInt128 operator ^(UInt128 left, UInt128 right)
{
return new UInt128(left.low ^ right.low, left.high ^ right.high);
}
public static UInt128 operator ++(UInt128 value)
{
return value + 1;
}
public static UInt128 operator --(UInt128 value)
{
return value - 1;
}
/// <summary>
/// Creates a UInt128 from two ulong.
/// </summary>
/// <param name="low">The lower 64 bits of the UInt128.</param>
/// <param name="high">The upper 64 bits of the UInt128.</param>
/// <returns>A UInt128 from the two ulong.</returns>
public static UInt128 Create(ulong low, ulong high)
{
return new UInt128(low, high);
}
public static UInt128 FromByteArray(ReadOnlySpan<byte> buffer)
{
if (!UInt128.TryCreateFromByteArray(buffer, out UInt128 value))
{
throw new FormatException($"Malformed buffer");
}
return value;
}
/// <summary>
/// Converts the UInt128 to a byte array.
/// </summary>
/// <param name="uint128">The UInt128 to convert.</param>
/// <returns>The byte array representation of this UInt128.</returns>
public static byte[] ToByteArray(UInt128 uint128)
{
byte[] bytes = new byte[UInt128.Length];
byte[] lowBytes = BitConverter.GetBytes(uint128.low);
byte[] highBytes = BitConverter.GetBytes(uint128.high);
lowBytes.CopyTo(bytes, 0);
highBytes.CopyTo(bytes, 8);
return bytes;
}
/// <summary>
/// Compares this value to an object.
/// </summary>
/// <param name="value">The value to compare to.</param>
/// <returns>The comparison.</returns>
public int CompareTo(object value)
{
if (value == null)
{
return 1;
}
if (!(value is UInt128 uint128))
{
throw new ArgumentException("Value must be a UInt128.");
}
return this.CompareTo(uint128);
}
/// <summary>
/// Compares this UInt128 to another instance of the UInt128 type.
/// </summary>
/// <param name="other">The other instance to compare to.</param>
/// <returns>
/// A negative number if this instance is less than the other instance.
/// Zero if they are the same.
/// A positive number if this instance is greater than the other instance.
/// </returns>
public int CompareTo(UInt128 other)
{
if (this < other)
{
return -1;
}
if (this > other)
{
return 1;
}
return 0;
}
/// <summary>
/// Returns whether this instance equals another object.
/// </summary>
/// <param name="obj">The object to compare to.</param>
/// <returns>Whether this instance equals another object.</returns>
public override bool Equals(object obj)
{
if (object.ReferenceEquals(this, obj))
{
return true;
}
if (!(obj is UInt128 uint128))
{
return false;
}
return this.Equals(uint128);
}
/// <summary>
/// Returns whether this UInt128 equals another UInt128.
/// </summary>
/// <param name="other">The UInt128 to compare to.</param>
/// <returns>Whether this UInt128 equals another UInt128.</returns>
public bool Equals(UInt128 other)
{
return this == other;
}
/// <summary>
/// Gets a hash code for this instance.
/// </summary>
/// <returns>The hash code for this instance.</returns>
public override int GetHashCode()
{
return (int)(this.low.GetHashCode() ^ this.high.GetHashCode());
}
/// <summary>
/// Gets the string representation of a UInt128 as a hex dump.
/// </summary>
/// <returns>The string representation of a UInt128 as a hex dump.</returns>
public override string ToString()
{
byte[] bytes = UInt128.ToByteArray(this);
// Reverse the bytes and make it big endian so that the lex sort is equivalent to the numeric sort.
Array.Reverse(bytes, 0, bytes.Length);
return BitConverter.ToString(bytes);
}
/// <summary>
/// Returns the high 64 bits of the UInt128.cs.
/// </summary>
/// <returns>The high 64 bits of the UInt128.cs.</returns>
public ulong GetHigh()
{
return this.high;
}
/// <summary>
/// Returns the low 64 bits of the UInt128.cs.
/// </summary>
/// <returns>The low 64 bits of the UInt128.cs.</returns>
public ulong GetLow()
{
return this.low;
}
public static bool TryParse(string value, out UInt128 uInt128)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("value can not be null or empty.");
}
string[] hexPairs = value.Split('-').Take(UInt128.Length).ToArray();
if (hexPairs.Length != UInt128.Length)
{
uInt128 = default;
return false;
}
Span<byte> bytes = stackalloc byte[UInt128.Length];
for (int index = 0; index < UInt128.Length; index++)
{
if (!byte.TryParse(hexPairs[index], System.Globalization.NumberStyles.HexNumber, null, out byte parsedBytes))
{
uInt128 = default;
return false;
}
bytes[index] = parsedBytes;
}
bytes.Reverse();
uInt128 = UInt128.FromByteArray(bytes);
return true;
}
public static bool TryCreateFromByteArray(ReadOnlySpan<byte> buffer, out UInt128 value)
{
if (buffer.Length < UInt128.Length)
{
value = default;
return false;
}
ReadOnlySpan<ulong> bufferAsULongs = MemoryMarshal.Cast<byte, ulong>(buffer);
value = new UInt128(low: bufferAsULongs[0], high: bufferAsULongs[1]);
return true;
}
}
}