Skip to content

Commit eae2fd5

Browse files
committed
IQuantity with QuantityValue as Value type (PoC)
- QuantityValue implements most of the INumber interface members / operators (operations preserving decimal when possible, failing back to double, clamped to the real values- infinities excluded) - changed the type of IQuantity.Value/As to QuantityValue (explicit cast required) - added back the IEquatable interface: behavior depending on the runtime type of the quantity values (strict equality comparison for double/double) - added back the fix for the non-reflexive CompareTo issue (and the corresponding tests) - added a check preventing the possible stack overflow exception when calling ToUnit() with default(TUnit) (or any other invalid Enum value) - DataContracts changed to reflect the possible use of either double or decimal as ValueType (the Json converters were not changed- but probably should be) - removed the obsolete Json converter tests
1 parent c1a2ba7 commit eae2fd5

File tree

297 files changed

+19705
-13077
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

297 files changed

+19705
-13077
lines changed

CodeGen/CodeGen.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<ItemGroup>
1313
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
14-
<PackageReference Include="NuGet.Protocol" Version="6.2.1" />
14+
<PackageReference Include="NuGet.Protocol" Version="6.3.0" />
1515
<PackageReference Include="Serilog" Version="2.11.0" />
1616
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
1717
<PackageReference Include="System.CommandLine.DragonFruit" Version="0.2.0-alpha.19174.3" />

CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs

Lines changed: 96 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public QuantityGenerator(Quantity quantity)
2525
throw new ArgumentException($"No unit found with SingularName equal to BaseUnit [{_quantity.BaseUnit}]. This unit must be defined.",
2626
nameof(quantity));
2727

28-
_valueType = quantity.ValueType;
28+
_valueType = "QuantityValue";// quantity.ValueType;
2929
_unitEnumName = $"{quantity.Name}Unit";
3030

3131
BaseDimensions baseDimensions = quantity.BaseDimensions;
@@ -77,14 +77,14 @@ public partial struct {_quantity.Name} : IQuantity<{_unitEnumName}>, ");
7777
Writer.W("IDecimalQuantity, ");
7878
}
7979

80-
Writer.WL($"IComparable, IComparable<{_quantity.Name}>, IConvertible, IFormattable");
80+
Writer.WL($"IEquatable<{_quantity.Name}>, IComparable, IComparable<{_quantity.Name}>, IConvertible, IFormattable");
8181
Writer.WL($@"
8282
{{
8383
/// <summary>
8484
/// The numeric value this quantity was constructed with.
8585
/// </summary>
8686
[DataMember(Name = ""Value"", Order = 0)]
87-
private readonly {_quantity.ValueType} _value;
87+
private readonly {_valueType} _value;
8888
8989
/// <summary>
9090
/// The unit this quantity was constructed with.
@@ -176,9 +176,9 @@ private void GenerateInstanceConstructors()
176176
/// <param name=""value"">The numeric value to construct this quantity with.</param>
177177
/// <param name=""unit"">The unit representation to construct this quantity with.</param>
178178
/// <exception cref=""ArgumentException"">If value is NaN or Infinity.</exception>
179-
public {_quantity.Name}({_quantity.ValueType} value, {_unitEnumName} unit)
179+
public {_quantity.Name}({_valueType} value, {_unitEnumName} unit)
180180
{{");
181-
Writer.WL(_quantity.ValueType == "double"
181+
Writer.WL(_valueType == "double"
182182
? @"
183183
_value = Guard.EnsureValidNumber(value, nameof(value));"
184184
: @"
@@ -203,7 +203,7 @@ private void GenerateInstanceConstructors()
203203
var firstUnitInfo = unitInfos.FirstOrDefault();
204204
");
205205

206-
Writer.WL(_quantity.ValueType == "double"
206+
Writer.WL(_valueType == "double"
207207
? @"
208208
_value = Guard.EnsureValidNumber(value, nameof(value));"
209209
: @"
@@ -262,15 +262,15 @@ private void GenerateProperties()
262262
public {_valueType} Value => _value;
263263
");
264264

265-
// Need to provide explicit interface implementation for decimal quantities like Information
266-
if (_quantity.ValueType != "double")
267-
Writer.WL(@"
268-
double IQuantity.Value => (double) _value;
265+
Writer.WL($@"
266+
/// <inheritdoc />
267+
{_valueType} IQuantity.Value => _value;
269268
");
269+
// Need to provide explicit interface implementation for decimal quantities like Information
270270
if (_quantity.ValueType == "decimal")
271271
Writer.WL(@"
272272
/// <inheritdoc cref=""IDecimalQuantity.Value""/>
273-
decimal IDecimalQuantity.Value => _value;
273+
decimal IDecimalQuantity.Value => (decimal)_value;
274274
");
275275

276276
Writer.WL($@"
@@ -306,11 +306,11 @@ private void GenerateConversionProperties()
306306

307307
Writer.WL($@"
308308
/// <summary>
309-
/// Gets a <see cref=""double""/> value of this quantity converted into <see cref=""{_unitEnumName}.{unit.SingularName}""/>
309+
/// Gets the numeric value of this quantity converted into <see cref=""{_unitEnumName}.{unit.SingularName}""/>
310310
/// </summary>");
311311
Writer.WLIfText(2, GetObsoleteAttributeOrNull(unit));
312312
Writer.WL($@"
313-
public double {unit.PluralName} => As({_unitEnumName}.{unit.SingularName});
313+
public {_valueType} {unit.PluralName} => As({_unitEnumName}.{unit.SingularName});
314314
");
315315
}
316316

@@ -426,7 +426,7 @@ private void GenerateStaticFactoryMethods()
426426
/// <exception cref=""ArgumentException"">If value is NaN or Infinity.</exception>");
427427
Writer.WLIfText(2, GetObsoleteAttributeOrNull(unit));
428428
Writer.WL($@"
429-
public static {_quantity.Name} From{unit.PluralName}(QuantityValue {valueParamName})
429+
public static {_quantity.Name} From{unit.PluralName}({_valueType} {valueParamName})
430430
{{
431431
{_valueType} value = ({_valueType}) {valueParamName};
432432
return new {_quantity.Name}(value, {_unitEnumName}.{unit.SingularName});
@@ -651,7 +651,7 @@ private void GenerateArithmeticOperators()
651651
}}
652652
653653
/// <summary>Get ratio value from dividing <see cref=""{_quantity.Name}""/> by <see cref=""{_quantity.Name}""/>.</summary>
654-
public static double operator /({_quantity.Name} left, {_quantity.Name} right)
654+
public static {_valueType} operator /({_quantity.Name} left, {_quantity.Name} right)
655655
{{
656656
return left.{_baseUnit.PluralName} / right.{_baseUnit.PluralName};
657657
}}
@@ -679,15 +679,15 @@ private void GenerateLogarithmicArithmeticOperators()
679679
{{
680680
// Logarithmic addition
681681
// Formula: {x} * log10(10^(x/{x}) + 10^(y/{x}))
682-
return new {_quantity.Name}({x} * Math.Log10(Math.Pow(10, left.Value/{x}) + Math.Pow(10, right.GetValueAs(left.Unit)/{x})), left.Unit);
682+
return new {_quantity.Name}({x} * Math.Log10(Math.Pow(10, (double)left.Value/{x}) + Math.Pow(10, (double)right.GetValueAs(left.Unit)/{x})), left.Unit);
683683
}}
684684
685685
/// <summary>Get <see cref=""{_quantity.Name}""/> from logarithmic subtraction of two <see cref=""{_quantity.Name}""/>.</summary>
686686
public static {_quantity.Name} operator -({_quantity.Name} left, {_quantity.Name} right)
687687
{{
688688
// Logarithmic subtraction
689689
// Formula: {x} * log10(10^(x/{x}) - 10^(y/{x}))
690-
return new {_quantity.Name}({x} * Math.Log10(Math.Pow(10, left.Value/{x}) - Math.Pow(10, right.GetValueAs(left.Unit)/{x})), left.Unit);
690+
return new {_quantity.Name}({x} * Math.Log10(Math.Pow(10, (double)left.Value/{x}) - Math.Pow(10, (double)right.GetValueAs(left.Unit)/{x})), left.Unit);
691691
}}
692692
693693
/// <summary>Get <see cref=""{_quantity.Name}""/> from logarithmic multiplication of value and <see cref=""{_quantity.Name}""/>.</summary>
@@ -751,6 +751,19 @@ private void GenerateEqualityAndComparison()
751751
return left.Value > right.GetValueAs(left.Unit);
752752
}}
753753
754+
/// <summary>Returns true if exactly equal.</summary>
755+
/// <remarks>Consider using <see cref=""Equals({_quantity.Name}, {_valueType}, ComparisonType)""/> for safely comparing floating point values.</remarks>
756+
public static bool operator ==({_quantity.Name} left, {_quantity.Name} right)
757+
{{
758+
return left.Equals(right);
759+
}}
760+
/// <summary>Returns true if not exactly equal.</summary>
761+
/// <remarks>Consider using <see cref=""Equals({_quantity.Name}, {_valueType}, ComparisonType)""/> for safely comparing floating point values.</remarks>
762+
public static bool operator !=({_quantity.Name} left, {_quantity.Name} right)
763+
{{
764+
return !(left == right);
765+
}}
766+
754767
/// <inheritdoc />
755768
public int CompareTo(object obj)
756769
{{
@@ -763,7 +776,29 @@ public int CompareTo(object obj)
763776
/// <inheritdoc />
764777
public int CompareTo({_quantity.Name} other)
765778
{{
766-
return _value.CompareTo(other.GetValueAs(this.Unit));
779+
var asFirstUnit = other.GetValueAs(this.Unit);
780+
var asSecondUnit = GetValueAs(other.Unit);
781+
return (_value.CompareTo(asFirstUnit) - other.Value.CompareTo(asSecondUnit)) / 2;
782+
}}
783+
784+
/// <inheritdoc />
785+
/// <remarks>Consider using <see cref=""Equals({_quantity.Name}, {_valueType}, ComparisonType)""/> for safely comparing floating point values.</remarks>
786+
public override bool Equals(object obj)
787+
{{
788+
if (obj is null || !(obj is {_quantity.Name} obj{_quantity.Name}))
789+
return false;
790+
return Equals(obj{_quantity.Name});
791+
}}
792+
793+
/// <inheritdoc />
794+
/// <remarks>Consider using <see cref=""Equals({_quantity.Name}, {_valueType}, ComparisonType)""/> for safely comparing floating point values.</remarks>
795+
public bool Equals({_quantity.Name} other)
796+
{{
797+
if (Value.IsDecimal)
798+
return other.Value.Equals(this.GetValueAs(other.Unit));
799+
if (other.Value.IsDecimal)
800+
return Value.Equals(other.GetValueAs(this.Unit));
801+
return this.Unit == other.Unit && this.Value.Equals(other.Value);
767802
}}
768803
769804
/// <summary>
@@ -806,13 +841,13 @@ public int CompareTo({_quantity.Name} other)
806841
/// <param name=""tolerance"">The absolute or relative tolerance value. Must be greater than or equal to 0.</param>
807842
/// <param name=""comparisonType"">The comparison type: either relative or absolute.</param>
808843
/// <returns>True if the absolute difference between the two values is not greater than the specified relative or absolute tolerance.</returns>
809-
public bool Equals({_quantity.Name} other, double tolerance, ComparisonType comparisonType)
844+
public bool Equals({_quantity.Name} other, {_valueType} tolerance, ComparisonType comparisonType)
810845
{{
811846
if (tolerance < 0)
812847
throw new ArgumentOutOfRangeException(""tolerance"", ""Tolerance must be greater than or equal to 0."");
813848
814-
double thisValue = (double)this.Value;
815-
double otherValueInThisUnits = other.As(this.Unit);
849+
{_valueType} thisValue = this.Value;
850+
{_valueType} otherValueInThisUnits = other.As(this.Unit);
816851
817852
return UnitsNet.Comparison.Equals(thisValue, otherValueInThisUnits, tolerance, comparisonType);
818853
}}
@@ -823,7 +858,7 @@ public bool Equals({_quantity.Name} other, double tolerance, ComparisonType comp
823858
/// <returns>A hash code for the current {_quantity.Name}.</returns>
824859
public override int GetHashCode()
825860
{{
826-
return new {{ Info.Name, Value, Unit }}.GetHashCode();
861+
return Info.Name.GetHashCode();
827862
}}
828863
829864
#endregion
@@ -839,17 +874,30 @@ private void GenerateConversionMethods()
839874
/// Convert to the unit representation <paramref name=""unit"" />.
840875
/// </summary>
841876
/// <returns>Value converted to the specified unit.</returns>
842-
public double As({_unitEnumName} unit)
877+
public {_valueType} As({_unitEnumName} unit)
843878
{{
844-
if (Unit == unit)
845-
return Convert.ToDouble(Value);
879+
if(Unit == unit)
880+
return Value;
846881
847-
var converted = GetValueAs(unit);
848-
return Convert.ToDouble(converted);
882+
return GetValueAs(unit);
849883
}}
884+
");
885+
886+
if (_valueType == "decimal")
887+
{
888+
Writer.WL($@"
889+
890+
{_valueType} IQuantity<{_unitEnumName}>.As({_unitEnumName} unit)
891+
{{
892+
return ({_valueType})As(unit);
893+
}}
894+
");
895+
}
896+
897+
Writer.WL($@"
850898
851899
/// <inheritdoc cref=""IQuantity.As(UnitSystem)""/>
852-
public double As(UnitSystem unitSystem)
900+
public {_valueType} As(UnitSystem unitSystem)
853901
{{
854902
if (unitSystem is null)
855903
throw new ArgumentNullException(nameof(unitSystem));
@@ -862,14 +910,27 @@ public double As(UnitSystem unitSystem)
862910
863911
return As(firstUnitInfo.Value);
864912
}}
913+
");
914+
915+
if (_valueType == "decimal")
916+
{
917+
Writer.WL($@"
918+
/// <inheritdoc cref=""IQuantity.As(UnitSystem)""/>
919+
{_valueType} IQuantity.As(UnitSystem unitSystem)
920+
{{
921+
return ({_valueType})As(unitSystem);
922+
}}
923+
");
924+
}
865925

926+
Writer.WL($@"
866927
/// <inheritdoc />
867-
double IQuantity.As(Enum unit)
928+
{_valueType} IQuantity.As(Enum unit)
868929
{{
869-
if (!(unit is {_unitEnumName} unitAs{_unitEnumName}))
930+
if (!(unit is {_unitEnumName} typedUnit))
870931
throw new ArgumentException($""The given unit is of type {{unit.GetType()}}. Only {{typeof({_unitEnumName})}} is supported."", nameof(unit));
871932
872-
return As(unitAs{_unitEnumName});
933+
return ({_valueType})As(typedUnit);
873934
}}
874935
875936
/// <summary>
@@ -901,25 +962,25 @@ double IQuantity.As(Enum unit)
901962
var converted = conversionFunction(this);
902963
return ({_quantity.Name})converted;
903964
}}
904-
else if (Unit != BaseUnit)
965+
else if (Enum.IsDefined(typeof({_unitEnumName}), unit))
905966
{{
906967
// Direct conversion to requested unit NOT found. Convert to BaseUnit, and then from BaseUnit to requested unit.
907968
var inBaseUnits = ToUnit(BaseUnit);
908969
return inBaseUnits.ToUnit(unit);
909970
}}
910971
else
911972
{{
912-
throw new NotImplementedException($""Can not convert {{Unit}} to {{unit}}."");
973+
throw new NotSupportedException($""Can not convert {{Unit}} to {{unit}}."");
913974
}}
914975
}}
915976
916977
/// <inheritdoc />
917978
IQuantity IQuantity.ToUnit(Enum unit)
918979
{{
919-
if (!(unit is {_unitEnumName} unitAs{_unitEnumName}))
980+
if (!(unit is {_unitEnumName} typedUnit))
920981
throw new ArgumentException($""The given unit is of type {{unit.GetType()}}. Only {{typeof({_unitEnumName})}} is supported."", nameof(unit));
921982
922-
return ToUnit(unitAs{_unitEnumName}, DefaultConversionFunctions);
983+
return ToUnit(typedUnit, DefaultConversionFunctions);
923984
}}
924985
925986
/// <inheritdoc cref=""IQuantity.ToUnit(UnitSystem)""/>

0 commit comments

Comments
 (0)