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
}
}
}