using System.Buffers.Binary;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
public static void Main()
var serializer = new JsonSerializer();
var latitude = new Latitude(45.1234m);
var json = JsonConvert.SerializeObject(latitude, new LatitudeConverter());
public readonly struct Latitude : IConvertible, ISerializable, IDeserializationCallback, IFloatingPoint<Latitude>, IMinMaxValue<Latitude>
public static Latitude MaxValue => new(MaxDecimalValue);
public static Latitude MinValue => new(MinDecimalValue);
public decimal Value { get; }
private sbyte Exponent => (sbyte) (95 - Value.Scale);
private const decimal MaxDecimalValue = +90m;
private const decimal MinDecimalValue = -90m;
private const int MaxDecimalScale = 6;
private readonly uint _hi32;
private readonly ulong _lo64;
public Latitude(decimal value)
if (value is < MinDecimalValue or > MaxDecimalValue)
throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(Latitude)} must be between {MinValue.Value} and {MaxValue.Value}.");
if (value.Scale > MaxDecimalScale)
value = Math.Round(value, MaxDecimalScale, MidpointRounding.ToZero);
var bits = decimal.GetBits(value);
_lo64 = (ulong) bits[2] << 32 | (uint) bits[0];
private Latitude(SerializationInfo info, StreamingContext context)
Value = info.GetDecimal(nameof(Value).ToLower(CultureInfo.InvariantCulture));
public static Latitude Parse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider)
var value = decimal.Parse(s, style, provider);
return new Latitude(value);
public static Latitude Parse(ReadOnlySpan<char> s, IFormatProvider? provider)
return Parse(s, NumberStyles.Number, provider);
public static Latitude Parse(string s, NumberStyles style, IFormatProvider? provider)
return Parse(s.AsSpan(), style, provider);
public static Latitude Parse(string s, IFormatProvider? provider)
return Parse(s.AsSpan(), NumberStyles.Number, provider);
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Latitude result)
return TryParse(s, NumberStyles.Number, provider, out result);
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out Latitude result)
if (decimal.TryParse(s, style, provider, out var value) && value >= MinValue.Value && value <= MaxValue.Value)
result = new Latitude(value);
public static bool TryParse(string? s, IFormatProvider? provider, out Latitude result)
return TryParse(s.AsSpan(), NumberStyles.Number, provider, out result);
public static bool TryParse(string? s, NumberStyles style, IFormatProvider? provider, out Latitude result)
return TryParse(s.AsSpan(), style, provider, out result);
public static bool operator !=(Latitude left, Latitude right)
public static Latitude operator %(Latitude left, Latitude right)
public static Latitude Mod(Latitude left, Latitude right)
var value = left.Value % right.Value;
return new Latitude(value);
public static Latitude operator *(Latitude left, Latitude right)
return Multiply(left, right);
public static Latitude Multiply(Latitude left, Latitude right)
var value = left.Value * right.Value;
return new Latitude(value);
public static Latitude operator +(Latitude item)
public static Latitude Plus(Latitude item)
return new Latitude(item.Value);
public static Latitude operator +(Latitude left, Latitude right)
public static Latitude Add(Latitude left, Latitude right)
var value = left.Value + right.Value;
return new Latitude(value);
public static Latitude operator ++(Latitude item)
public static Latitude Increment(Latitude item)
var value = item.Value + decimal.One;
return new Latitude(value);
public static Latitude operator -(Latitude item)
public static Latitude Negate(Latitude item)
var value = item.Value * decimal.MinusOne;
return new Latitude(value);
public static Latitude operator -(Latitude left, Latitude right)
return Subtract(left, right);
public static Latitude Subtract(Latitude left, Latitude right)
var value = left.Value - right.Value;
return new Latitude(value);
public static Latitude operator --(Latitude item)
public static Latitude Decrement(Latitude item)
var value = item.Value - decimal.One;
return new Latitude(value);
public static Latitude operator /(Latitude left, Latitude right)
return Divide(left, right);
public static Latitude Divide(Latitude left, Latitude right)
var value = left.Value / right.Value;
return new Latitude(value);
public static bool operator <(Latitude left, Latitude right)
return left.CompareTo(right) < 0;
public static bool operator <=(Latitude left, Latitude right)
return left.CompareTo(right) <= 0;
public static bool operator ==(Latitude left, Latitude right)
return left.Equals(right);
public static bool operator >(Latitude left, Latitude right)
return left.CompareTo(right) > 0;
public static bool operator >=(Latitude left, Latitude right)
return left.CompareTo(right) >= 0;
public int CompareTo(Latitude other)
return Value.CompareTo(other.Value);
public int CompareTo(object? obj)
return Value.CompareTo(obj);
public bool Equals(Latitude other)
return Value == other.Value;
public override bool Equals(object? obj)
return obj is Latitude other && Equals(other);
public override int GetHashCode()
return Value.GetHashCode();
public static implicit operator decimal(Latitude latitude)
return latitude.ToDecimal();
public decimal ToDecimal()
public static explicit operator Latitude(decimal value)
return ToLatitude(value);
public static Latitude ToLatitude(decimal value)
return new Latitude(value);
public override string ToString()
return ToString(CultureInfo.InvariantCulture);
public string ToString(IFormatProvider? provider)
return Value.ToString(provider);
public string ToString(string? format, IFormatProvider? formatProvider)
return Value.ToString(format, formatProvider);
static Latitude IAdditiveIdentity<Latitude, Latitude>.AdditiveIdentity => new(decimal.Zero);
static Latitude IFloatingPointConstants<Latitude>.E => new(2.7182818284590452353602874714m);
static Latitude IMultiplicativeIdentity<Latitude, Latitude>.MultiplicativeIdentity => new(decimal.One);
static Latitude ISignedNumber<Latitude>.NegativeOne => new(decimal.MinusOne);
static Latitude INumberBase<Latitude>.One => new(decimal.One);
static Latitude IFloatingPointConstants<Latitude>.Pi => new(3.1415926535897932384626433833m);
static int INumberBase<Latitude>.Radix => 10;
static Latitude IFloatingPointConstants<Latitude>.Tau => new(6.2831853071795864769252867666m);
public static Latitude Zero => new(decimal.Zero);
static Latitude INumberBase<Latitude>.Abs(Latitude value)
var absValue = decimal.Abs(value.Value);
return new Latitude(absValue);
int IFloatingPoint<Latitude>.GetExponentByteCount()
int IFloatingPoint<Latitude>.GetExponentShortestBitLength()
return sizeof(sbyte) * 8 - sbyte.LeadingZeroCount(exponent);
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
info.AddValue(nameof(Value).ToLower(CultureInfo.InvariantCulture), Value, typeof(decimal));
int IFloatingPoint<Latitude>.GetSignificandBitLength()
int IFloatingPoint<Latitude>.GetSignificandByteCount()
return sizeof(ulong) + sizeof(uint);
TypeCode IConvertible.GetTypeCode()
return Value.GetTypeCode();
static bool INumberBase<Latitude>.IsCanonical(Latitude value)
return decimal.IsCanonical(value.Value);
static bool INumberBase<Latitude>.IsComplexNumber(Latitude value)
static bool INumberBase<Latitude>.IsEvenInteger(Latitude value)
return decimal.IsEvenInteger(value.Value);
static bool INumberBase<Latitude>.IsFinite(Latitude value)
static bool INumberBase<Latitude>.IsImaginaryNumber(Latitude value)
static bool INumberBase<Latitude>.IsInfinity(Latitude value)
static bool INumberBase<Latitude>.IsInteger(Latitude value)
return decimal.IsInteger(value.Value);
static bool INumberBase<Latitude>.IsNaN(Latitude value)
static bool INumberBase<Latitude>.IsNegative(Latitude value)
return decimal.IsNegative(value.Value);
static bool INumberBase<Latitude>.IsNegativeInfinity(Latitude value)
static bool INumberBase<Latitude>.IsNormal(Latitude value)
return value.Value != decimal.Zero;
static bool INumberBase<Latitude>.IsOddInteger(Latitude value)
var intValue = decimal.ToInt32(value.Value);
return intValue % 2 != 0;
static bool INumberBase<Latitude>.IsPositive(Latitude value)
return decimal.IsPositive(value.Value);
static bool INumberBase<Latitude>.IsPositiveInfinity(Latitude value)
static bool INumberBase<Latitude>.IsRealNumber(Latitude value)
static bool INumberBase<Latitude>.IsSubnormal(Latitude value)
static bool INumberBase<Latitude>.IsZero(Latitude value)
return value.Value == decimal.Zero;
static Latitude INumberBase<Latitude>.MaxMagnitude(Latitude x, Latitude y)
var value = decimal.MaxMagnitude(x.Value, y.Value);
return new Latitude(value);
static Latitude INumberBase<Latitude>.MaxMagnitudeNumber(Latitude x, Latitude y)
var value = decimal.MaxMagnitude(x.Value, y.Value);
return new Latitude(value);
static Latitude INumberBase<Latitude>.MinMagnitude(Latitude x, Latitude y)
var value = decimal.MinMagnitude(x.Value, y.Value);
return new Latitude(value);
static Latitude INumberBase<Latitude>.MinMagnitudeNumber(Latitude x, Latitude y)
var value = decimal.MinMagnitude(x.Value, y.Value);
return new Latitude(value);
void IDeserializationCallback.OnDeserialization(object? sender)
((IDeserializationCallback) Value).OnDeserialization(sender);
static Latitude IFloatingPoint<Latitude>.Round(Latitude x, int digits, MidpointRounding mode)
var value = decimal.Round(x.Value, digits, mode);
return new Latitude(value);
bool IConvertible.ToBoolean(IFormatProvider? provider)
return Convert.ToBoolean(Value, provider);
byte IConvertible.ToByte(IFormatProvider? provider)
return Convert.ToByte(Value, provider);
char IConvertible.ToChar(IFormatProvider? provider)
return Convert.ToChar(Value, provider);
DateTime IConvertible.ToDateTime(IFormatProvider? provider)
return Convert.ToDateTime(Value, provider);
decimal IConvertible.ToDecimal(IFormatProvider? provider)
return Convert.ToDecimal(Value, provider);
double IConvertible.ToDouble(IFormatProvider? provider)
return Convert.ToDouble(Value, provider);
short IConvertible.ToInt16(IFormatProvider? provider)
return Convert.ToInt16(Value, provider);
int IConvertible.ToInt32(IFormatProvider? provider)
return Convert.ToInt32(Value, provider);
long IConvertible.ToInt64(IFormatProvider? provider)
return Convert.ToInt64(Value, provider);
sbyte IConvertible.ToSByte(IFormatProvider? provider)
return Convert.ToSByte(Value, provider);
float IConvertible.ToSingle(IFormatProvider? provider)
return Convert.ToSingle(Value, provider);
object IConvertible.ToType(Type conversionType, IFormatProvider? provider)
return ((IConvertible) Value).ToType(conversionType, provider);
ushort IConvertible.ToUInt16(IFormatProvider? provider)
return Convert.ToUInt16(Value, provider);
uint IConvertible.ToUInt32(IFormatProvider? provider)
return Convert.ToUInt32(Value, provider);
ulong IConvertible.ToUInt64(IFormatProvider? provider)
return Convert.ToUInt64(Value, provider);
static bool INumberBase<Latitude>.TryConvertFromChecked<TOther>(TOther value, out Latitude result)
if (typeof(TOther) == typeof(byte))
var actualValue = (byte) (object) value;
result = new Latitude(actualValue);
if (typeof(TOther) == typeof(char))
var actualValue = (char) (object) value;
result = new Latitude(actualValue);
if (typeof(TOther) == typeof(ushort))
var actualValue = (ushort) (object) value;
result = new Latitude(actualValue);
if (typeof(TOther) == typeof(uint))
var actualValue = (uint) (object) value;
result = new Latitude(actualValue);
if (typeof(TOther) == typeof(ulong))
var actualValue = (ulong) (object) value;
result = new Latitude(actualValue);
if (typeof(TOther) == typeof(UInt128))
var actualValue = (UInt128) (object) value;
result = new Latitude((decimal) actualValue);
if (typeof(TOther) == typeof(nuint))
var actualValue = (nuint) (object) value;
result = new Latitude(actualValue);
static bool INumberBase<Latitude>.TryConvertFromSaturating<TOther>(TOther value, out Latitude result)
return TryConvertFrom(value, out result);
static bool INumberBase<Latitude>.TryConvertFromTruncating<TOther>(TOther value, out Latitude result)
return TryConvertFrom(value, out result);
static bool INumberBase<Latitude>.TryConvertToChecked<TOther>(Latitude value, out TOther result)
if (typeof(TOther) == typeof(double))
var actualResult = (double) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(Half))
var actualResult = (Half) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(short))
var actualResult = (short) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(int))
var actualResult = (int) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(long))
var actualResult = (long) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(Int128))
var actualResult = (Int128) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(nint))
var actualResult = (nint) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(sbyte))
var actualResult = (sbyte) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(float))
var actualResult = (float) value.Value;
result = (TOther) (object) actualResult;
static bool INumberBase<Latitude>.TryConvertToSaturating<TOther>(Latitude value, out TOther result)
return TryConvertTo(value, out result);
static bool INumberBase<Latitude>.TryConvertToTruncating<TOther>(Latitude value, out TOther result)
return TryConvertTo(value, out result);
bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
return Value.TryFormat(destination, out charsWritten, format, provider);
bool IFloatingPoint<Latitude>.TryWriteExponentBigEndian(Span<byte> destination, out int bytesWritten)
if (destination.Length >= sizeof(sbyte))
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), exponent);
bytesWritten = sizeof(sbyte);
bool IFloatingPoint<Latitude>.TryWriteExponentLittleEndian(Span<byte> destination, out int bytesWritten)
if (destination.Length >= sizeof(sbyte))
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), exponent);
bytesWritten = sizeof(sbyte);
bool IFloatingPoint<Latitude>.TryWriteSignificandBigEndian(Span<byte> destination, out int bytesWritten)
if (destination.Length >= sizeof(uint) + sizeof(ulong))
if (BitConverter.IsLittleEndian)
hi32 = BinaryPrimitives.ReverseEndianness(hi32);
lo64 = BinaryPrimitives.ReverseEndianness(lo64);
ref var address = ref MemoryMarshal.GetReference(destination);
Unsafe.WriteUnaligned(ref address, hi32);
Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref address, sizeof(uint)), lo64);
bytesWritten = sizeof(uint) + sizeof(ulong);
bool IFloatingPoint<Latitude>.TryWriteSignificandLittleEndian(Span<byte> destination, out int bytesWritten)
if (destination.Length >= sizeof(ulong) + sizeof(uint))
if (!BitConverter.IsLittleEndian)
lo64 = BinaryPrimitives.ReverseEndianness(lo64);
hi32 = BinaryPrimitives.ReverseEndianness(hi32);
ref var address = ref MemoryMarshal.GetReference(destination);
Unsafe.WriteUnaligned(ref address, lo64);
Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref address, sizeof(ulong)), hi32);
bytesWritten = sizeof(ulong) + sizeof(uint);
private static bool TryConvertFrom<TOther>(TOther value, out Latitude result)
where TOther : INumberBase<TOther>
if (typeof(TOther) == typeof(byte))
var actualValue = (byte) (object) value;
result = new Latitude(actualValue);
if (typeof(TOther) == typeof(char))
var actualValue = (char) (object) value;
result = new Latitude(actualValue);
if (typeof(TOther) == typeof(ushort))
var actualValue = (ushort) (object) value;
result = new Latitude(actualValue);
if (typeof(TOther) == typeof(uint))
var actualValue = (uint) (object) value;
result = new Latitude(actualValue);
if (typeof(TOther) == typeof(ulong))
var actualValue = (ulong) (object) value;
result = new Latitude(actualValue);
if (typeof(TOther) == typeof(UInt128))
var actualValue = (UInt128) (object) value;
result = actualValue >= new UInt128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF) ? MaxValue : new Latitude((decimal) actualValue);
if (typeof(TOther) == typeof(nuint))
var actualValue = (nuint) (object) value;
result = new Latitude(actualValue);
private static bool TryConvertTo<TOther>(Latitude value, out TOther result)
where TOther : INumberBase<TOther>
if (typeof(TOther) == typeof(double))
var actualResult = (double) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(Half))
var actualResult = (Half) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(short))
var actualResult = value.Value >= short.MaxValue ? short.MaxValue : value.Value <= short.MinValue ? short.MinValue : (short) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(int))
var actualResult = value.Value >= int.MaxValue ? int.MaxValue : value.Value <= int.MinValue ? int.MinValue : (int) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(long))
var actualResult = value.Value >= long.MaxValue ? long.MaxValue : value.Value <= long.MinValue ? long.MinValue : (long) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(Int128))
var actualResult = (Int128) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(nint))
var actualResult = value.Value >= nint.MaxValue ? nint.MaxValue : value.Value <= nint.MinValue ? nint.MinValue : (nint) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(sbyte))
var actualResult = value.Value >= sbyte.MaxValue ? sbyte.MaxValue : value.Value <= sbyte.MinValue ? sbyte.MinValue : (sbyte) value.Value;
result = (TOther) (object) actualResult;
if (typeof(TOther) == typeof(float))
var actualResult = (float) value.Value;
result = (TOther) (object) actualResult;
public class LatitudeConverter : JsonConverter
public override bool CanRead => true;
public override bool CanConvert(Type objectType)
return objectType == typeof(Latitude);
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
switch (reader.TokenType)
return reader.Value is not null
? (object) new Latitude((decimal) reader.Value)
: throw new JsonException($"Could not deserialize {nameof(Latitude)} object, because the JSON structure was unexpected.");
throw new JsonException($"Could not deserialize {nameof(Latitude)} object, because the JSON structure was unexpected.");
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
writer.WriteValue((Latitude) value);
throw new JsonException($"Could not serialize {nameof(Latitude)} object, because the object was null.");