1
1
// Licensed under MIT No Attribution, see LICENSE file at the root.
2
2
// Copyright 2013 Andreas Gullberg Larsen ([email protected] ). Maintained at https://github.com/angularsen/UnitsNet.
3
3
4
-
4
+ using System ;
5
+ using System . Diagnostics ;
6
+ using System . Runtime . InteropServices ;
7
+ using System . Text ;
5
8
using UnitsNet . InternalHelpers ;
6
9
7
10
namespace UnitsNet
@@ -16,35 +19,49 @@ namespace UnitsNet
16
19
/// </list>
17
20
/// </summary>
18
21
/// <remarks>
19
- /// At the time of this writing, this reduces the number of From(value, unit) overloads to 1/4th:
22
+ /// <para> At the time of this writing, this reduces the number of From(value, unit) overloads to 1/4th:
20
23
/// From 8 (int, long, double, decimal + each nullable) down to 2 (QuantityValue and QuantityValue?).
21
- /// This also adds more numeric types with no extra overhead, such as float, short and byte.
24
+ /// This also adds more numeric types with no extra overhead, such as float, short and byte.</para>
25
+ /// <para>So far, the internal representation can be either <see cref="double"/> or <see cref="decimal"/>,
26
+ /// 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.
27
+ /// This allows for adding support for smaller data types without increasing the overall size.</para>
22
28
/// </remarks>
23
- public struct QuantityValue
29
+ [ StructLayout ( LayoutKind . Explicit ) ]
30
+ [ DebuggerDisplay ( "{GetDebugRepresentation()}" ) ]
31
+ public readonly struct QuantityValue
24
32
{
25
33
/// <summary>
26
34
/// Value assigned when implicitly casting from all numeric types except <see cref="decimal" />, since
27
- /// <see cref="double" /> has the greatest range and is 64 bits versus 128 bits for <see cref="decimal"/> .
35
+ /// <see cref="double" /> has the greatest range.
28
36
/// </summary>
29
- private readonly double ? _value ;
37
+ [ FieldOffset ( 8 ) ] // so that it does not interfere with the Type field
38
+ private readonly double _doubleValue ;
30
39
31
40
/// <summary>
32
41
/// Value assigned when implicitly casting from <see cref="decimal" /> type, since it has a greater precision than
33
42
/// <see cref="double"/> and we want to preserve that when constructing quantities that use <see cref="decimal"/>
34
43
/// as their value type.
35
44
/// </summary>
36
- private readonly decimal ? _valueDecimal ;
45
+ [ FieldOffset ( 0 ) ]
46
+ // bytes layout: 0-1 unused, 2 exponent, 3 sign (only highest bit), 4-15 number
47
+ private readonly decimal _decimalValue ;
48
+
49
+ /// <summary>
50
+ /// Determines the underlying type of this <see cref="QuantityValue"/>.
51
+ /// </summary>
52
+ [ FieldOffset ( 0 ) ] // using unused byte for storing type
53
+ public readonly UnderlyingDataType Type ;
37
54
38
- private QuantityValue ( double val )
55
+ private QuantityValue ( double val ) : this ( )
39
56
{
40
- _value = Guard . EnsureValidNumber ( val , nameof ( val ) ) ;
41
- _valueDecimal = null ;
57
+ _doubleValue = Guard . EnsureValidNumber ( val , nameof ( val ) ) ;
58
+ Type = UnderlyingDataType . Double ;
42
59
}
43
60
44
- private QuantityValue ( decimal val )
61
+ private QuantityValue ( decimal val ) : this ( )
45
62
{
46
- _valueDecimal = val ;
47
- _value = null ;
63
+ _decimalValue = val ;
64
+ Type = UnderlyingDataType . Decimal ;
48
65
}
49
66
50
67
#region To QuantityValue
@@ -72,28 +89,59 @@ private QuantityValue(decimal val)
72
89
73
90
/// <summary>Explicit cast from <see cref="QuantityValue"/> to <see cref="double"/>.</summary>
74
91
public static explicit operator double ( QuantityValue number )
75
- {
76
- // double -> decimal -> zero (since we can't implement the default struct ctor)
77
- return number . _value ?? ( double ) number . _valueDecimal . GetValueOrDefault ( ) ;
78
- }
92
+ => number . Type switch
93
+ {
94
+ UnderlyingDataType . Decimal => ( double ) number . _decimalValue ,
95
+ UnderlyingDataType . Double => number . _doubleValue ,
96
+ _ => throw new NotImplementedException ( )
97
+ } ;
79
98
80
99
#endregion
81
100
82
101
#region To decimal
83
102
84
103
/// <summary>Explicit cast from <see cref="QuantityValue"/> to <see cref="decimal"/>.</summary>
85
104
public static explicit operator decimal ( QuantityValue number )
86
- {
87
- // decimal -> double -> zero (since we can't implement the default struct ctor)
88
- return number . _valueDecimal ?? ( decimal ) number . _value . GetValueOrDefault ( ) ;
89
- }
105
+ => number . Type switch
106
+ {
107
+ UnderlyingDataType . Decimal => number . _decimalValue ,
108
+ UnderlyingDataType . Double => ( decimal ) number . _doubleValue ,
109
+ _ => throw new NotImplementedException ( )
110
+ } ;
90
111
91
112
#endregion
92
113
93
114
/// <summary>Returns the string representation of the numeric value.</summary>
94
115
public override string ToString ( )
116
+ => Type switch
117
+ {
118
+ UnderlyingDataType . Decimal => _decimalValue . ToString ( ) ,
119
+ UnderlyingDataType . Double => _doubleValue . ToString ( ) ,
120
+ _ => throw new NotImplementedException ( )
121
+ } ;
122
+
123
+ private string GetDebugRepresentation ( )
124
+ {
125
+ StringBuilder builder = new ( $ "{ Type } { ToString ( ) } Hex:") ;
126
+
127
+ byte [ ] bytes = BytesUtility . GetBytes ( this ) ;
128
+ for ( int i = bytes . Length - 1 ; i >= 0 ; i -- )
129
+ {
130
+ builder . Append ( $ " { bytes [ i ] : X2} ") ;
131
+ }
132
+
133
+ return builder . ToString ( ) ;
134
+ }
135
+
136
+ /// <summary>
137
+ /// Describes the underlying type of a <see cref="QuantityValue"/>.
138
+ /// </summary>
139
+ public enum UnderlyingDataType : byte
95
140
{
96
- return _value . HasValue ? _value . ToString ( ) : _valueDecimal . ToString ( ) ;
141
+ /// <summary><see cref="Decimal"/> must have the value 0 due to the bit structure of <see cref="decimal"/>.</summary>
142
+ Decimal = 0 ,
143
+ /// <inheritdoc cref="double"/>
144
+ Double = 1
97
145
}
98
146
}
99
147
}
0 commit comments