Skip to content

Commit 7fbc8b1

Browse files
committed
QuantityValue: Preserve precision of decimal values
Store decimal values internally and use that when explicitly casting to decimal, to preserve its precision when constructing quantities such as Power, Information and BitRate.
1 parent 0837623 commit 7fbc8b1

File tree

1 file changed

+58
-18
lines changed

1 file changed

+58
-18
lines changed

UnitsNet/QuantityValue.cs

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
// Copyright © 2007 Andreas Gullberg Larsen (angularsen@gmail.com).
1+
// Copyright (c) 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com).
22
// https://github.com/angularsen/UnitsNet
3-
//
3+
//
44
// Permission is hereby granted, free of charge, to any person obtaining a copy
55
// of this software and associated documentation files (the "Software"), to deal
66
// in the Software without restriction, including without limitation the rights
77
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
88
// copies of the Software, and to permit persons to whom the Software is
99
// furnished to do so, subject to the following conditions:
10-
//
10+
//
1111
// The above copyright notice and this permission notice shall be included in
1212
// all copies or substantial portions of the Software.
13-
//
13+
//
1414
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1515
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1616
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -20,47 +20,87 @@
2020
// THE SOFTWARE.
2121

2222
// Operator overloads not supported in Windows Runtime Components, we use 'double' type instead
23+
2324
#if !WINDOWS_UWP
2425
using System;
2526

2627
namespace UnitsNet
2728
{
2829
/// <summary>
29-
/// Pass it any numeric value (int, double, decimal, float..) and it will be implicitly converted to a <see cref="double" />, the quantity value representation used in UnitsNet.
30-
/// This is used to avoid an explosion of overloads for methods taking N numeric types for all our 500+ units.
30+
/// A type that supports implicit cast from all .NET numeric types, in order to avoid a large number of overloads
31+
/// and binary size for all From(value, unit) factory methods, for each of the 700+ units in the library.
32+
/// <see cref="QuantityValue"/> stores the value internally with the proper type to preserve the range or precision of the original value:
33+
/// <list type="bullet">
34+
/// <item><description><see cref="double"/> for [byte, short, int, long, float, double]</description></item>
35+
/// <item><description><see cref="decimal"/> for [decimal] to preserve the 128-bit precision</description></item>
36+
/// </list>
3137
/// </summary>
3238
/// <remarks>
33-
/// At the time of this writing, this reduces the number of From() overloads to 1/4th:
39+
/// At the time of this writing, this reduces the number of From(value, unit) overloads to 1/4th:
3440
/// From 8 (int, long, double, decimal + each nullable) down to 2 (QuantityValue and QuantityValue?).
3541
/// This also adds more numeric types with no extra overhead, such as float, short and byte.
3642
/// </remarks>
3743
public struct QuantityValue
3844
{
39-
private readonly double _value;
45+
/// <summary>
46+
/// Value assigned when implicitly casting from all numeric types except <see cref="decimal" />, since
47+
/// <see cref="double" /> has the greatest range and is 64 bits versus 128 bits for <see cref="decimal"/>.
48+
/// </summary>
49+
private readonly double? _value;
50+
51+
/// <summary>
52+
/// Value assigned when implicitly casting from <see cref="decimal" /> type, since it has a greater precision than
53+
/// <see cref="double"/> and we want to preserve that when constructing quantities that use <see cref="decimal"/>
54+
/// as their value type.
55+
/// </summary>
56+
private readonly decimal? _valueDecimal;
4057

41-
// Obsolete is used to communicate how they should use this type, instead of making the constructor private and have them figure it out
42-
[Obsolete("Do not use this constructor. Instead pass any numeric value such as int, long, float, double, decimal, short or byte directly and it will be implicitly casted to double.")]
4358
private QuantityValue(double val)
4459
{
4560
_value = val;
61+
_valueDecimal = null;
62+
}
63+
64+
private QuantityValue(decimal val)
65+
{
66+
_valueDecimal = val;
67+
_value = null;
4668
}
4769

4870
#region To QuantityValue
4971

5072
#pragma warning disable 618
51-
public static implicit operator QuantityValue(double val) => new QuantityValue(val);
52-
public static implicit operator QuantityValue(float val) => new QuantityValue(val);
53-
public static implicit operator QuantityValue(long val) => new QuantityValue(val);
54-
public static implicit operator QuantityValue(decimal val) => new QuantityValue(Convert.ToDouble(val));
55-
public static implicit operator QuantityValue(short val) => new QuantityValue(val);
56-
public static implicit operator QuantityValue(byte val) => new QuantityValue(val);
73+
// Prefer double for integer types, since most quantities use that type as of now and
74+
// that avoids unnecessary casts back and forth.
75+
// If we later change to use decimal more, we should revisit this.
76+
public static implicit operator QuantityValue(byte val) => new QuantityValue((double) val);
77+
public static implicit operator QuantityValue(short val) => new QuantityValue((double) val);
78+
public static implicit operator QuantityValue(int val) => new QuantityValue((double) val);
79+
public static implicit operator QuantityValue(long val) => new QuantityValue((double) val);
80+
public static implicit operator QuantityValue(float val) => new QuantityValue(val); // double
81+
public static implicit operator QuantityValue(double val) => new QuantityValue(val); // double
82+
public static implicit operator QuantityValue(decimal val) => new QuantityValue(val); // decimal
5783
#pragma warning restore 618
58-
84+
5985
#endregion
6086

6187
#region To double
6288

63-
public static explicit operator double(QuantityValue number) => Convert.ToDouble(number._value);
89+
public static explicit operator double(QuantityValue number)
90+
{
91+
// double -> decimal -> zero (since we can't implement the default struct ctor)
92+
return number._value.GetValueOrDefault((double) number._valueDecimal.GetValueOrDefault());
93+
}
94+
95+
#endregion
96+
97+
#region To decimal
98+
99+
public static explicit operator decimal(QuantityValue number)
100+
{
101+
// decimal -> double -> zero (since we can't implement the default struct ctor)
102+
return number._valueDecimal.GetValueOrDefault((decimal) number._value.GetValueOrDefault());
103+
}
64104

65105
#endregion
66106
}

0 commit comments

Comments
 (0)