diff --git a/UnitsNet/InternalHelpers/BytesUtility.cs b/UnitsNet/InternalHelpers/BytesUtility.cs new file mode 100644 index 0000000000..e75e467773 --- /dev/null +++ b/UnitsNet/InternalHelpers/BytesUtility.cs @@ -0,0 +1,40 @@ +// Licensed under MIT No Attribution, see LICENSE file at the root. +// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. + +using System; +using System.Runtime.InteropServices; + +namespace UnitsNet.InternalHelpers +{ + /// + /// Utility methods for working with the byte representation of structs. + /// + internal static class BytesUtility + { + /// + /// Converts the given to an array of its underlying bytes. + /// + /// The struct type. + /// The struct value to convert. + /// A byte array representing a copy of s bytes. + internal static byte[] GetBytes(T value) where T : struct + { + int size = Marshal.SizeOf(value); + byte[] array = new byte[size]; + + IntPtr ptr = Marshal.AllocHGlobal(size); + + try + { + Marshal.StructureToPtr(value, ptr, true); + Marshal.Copy(ptr, array, 0, size); + } + finally + { + Marshal.FreeHGlobal(ptr); + } + + return array; + } + } +} diff --git a/UnitsNet/QuantityValue.cs b/UnitsNet/QuantityValue.cs index 98cb83b493..d25d184e98 100644 --- a/UnitsNet/QuantityValue.cs +++ b/UnitsNet/QuantityValue.cs @@ -1,7 +1,10 @@ // Licensed under MIT No Attribution, see LICENSE file at the root. // Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. - +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; using UnitsNet.InternalHelpers; namespace UnitsNet @@ -16,35 +19,49 @@ namespace UnitsNet /// /// /// - /// At the time of this writing, this reduces the number of From(value, unit) overloads to 1/4th: + /// At the time of this writing, this reduces the number of From(value, unit) overloads to 1/4th: /// From 8 (int, long, double, decimal + each nullable) down to 2 (QuantityValue and QuantityValue?). - /// This also adds more numeric types with no extra overhead, such as float, short and byte. + /// This also adds more numeric types with no extra overhead, such as float, short and byte. + /// So far, the internal representation can be either or , + /// but as this struct is realized as a union struct with overlapping fields, only the amount of memory of the largest data type is used. + /// This allows for adding support for smaller data types without increasing the overall size. /// - public struct QuantityValue + [StructLayout(LayoutKind.Explicit)] + [DebuggerDisplay("{GetDebugRepresentation()}")] + public readonly struct QuantityValue { /// /// Value assigned when implicitly casting from all numeric types except , since - /// has the greatest range and is 64 bits versus 128 bits for . + /// has the greatest range. /// - private readonly double? _value; + [FieldOffset(8)] // so that it does not interfere with the Type field + private readonly double _doubleValue; /// /// Value assigned when implicitly casting from type, since it has a greater precision than /// and we want to preserve that when constructing quantities that use /// as their value type. /// - private readonly decimal? _valueDecimal; + [FieldOffset(0)] + // bytes layout: 0-1 unused, 2 exponent, 3 sign (only highest bit), 4-15 number + private readonly decimal _decimalValue; + + /// + /// Determines the underlying type of this . + /// + [FieldOffset(0)] // using unused byte for storing type + public readonly UnderlyingDataType Type; - private QuantityValue(double val) + private QuantityValue(double val) : this() { - _value = Guard.EnsureValidNumber(val, nameof(val)); - _valueDecimal = null; + _doubleValue = Guard.EnsureValidNumber(val, nameof(val)); + Type = UnderlyingDataType.Double; } - private QuantityValue(decimal val) + private QuantityValue(decimal val) : this() { - _valueDecimal = val; - _value = null; + _decimalValue = val; + Type = UnderlyingDataType.Decimal; } #region To QuantityValue @@ -72,10 +89,12 @@ private QuantityValue(decimal val) /// Explicit cast from to . public static explicit operator double(QuantityValue number) - { - // double -> decimal -> zero (since we can't implement the default struct ctor) - return number._value ?? (double) number._valueDecimal.GetValueOrDefault(); - } + => number.Type switch + { + UnderlyingDataType.Decimal => (double)number._decimalValue, + UnderlyingDataType.Double => number._doubleValue, + _ => throw new NotImplementedException() + }; #endregion @@ -83,17 +102,46 @@ public static explicit operator double(QuantityValue number) /// Explicit cast from to . public static explicit operator decimal(QuantityValue number) - { - // decimal -> double -> zero (since we can't implement the default struct ctor) - return number._valueDecimal ?? (decimal) number._value.GetValueOrDefault(); - } + => number.Type switch + { + UnderlyingDataType.Decimal => number._decimalValue, + UnderlyingDataType.Double => (decimal)number._doubleValue, + _ => throw new NotImplementedException() + }; #endregion /// Returns the string representation of the numeric value. public override string ToString() + => Type switch + { + UnderlyingDataType.Decimal => _decimalValue.ToString(), + UnderlyingDataType.Double => _doubleValue.ToString(), + _ => throw new NotImplementedException() + }; + + private string GetDebugRepresentation() + { + StringBuilder builder = new($"{Type} {ToString()} Hex:"); + + byte[] bytes = BytesUtility.GetBytes(this); + for (int i = bytes.Length - 1; i >= 0; i--) + { + builder.Append($" {bytes[i]:X2}"); + } + + return builder.ToString(); + } + + /// + /// Describes the underlying type of a . + /// + public enum UnderlyingDataType : byte { - return _value.HasValue ? _value.ToString() : _valueDecimal.ToString(); + /// must have the value 0 due to the bit structure of . + Decimal = 0, + /// + Double = 1 } } }