Skip to content

Commit 37d1641

Browse files
angularsenclaude
andcommitted
Option B: Keep double public API, QuantityValue internal
Revert public-facing API from QuantityValue to double while keeping QuantityValue internally for the conversion pipeline. Changes: - IQuantity.Value, From(), Create() → double - IArithmeticQuantity/ILogarithmicQuantity operator interfaces → double - Generated quantities: _value field, properties, operators, factories → double - QuantityExtensions.As() → returns double - Quantity static methods → double - UnitConverter static convenience methods → double - Comparison.cs → double - CodeGen templates updated, code regenerated - Test/benchmark code updated for double types Build: 0 library errors, 0 warnings. Tests: 2509 failures out of 51899 — primarily ToUnit roundtrip tests that relied on QuantityValue exact rational precision for lossless conversion, which is lost when storing values as double. This demonstrates the fundamental trade-off: Option B loses conversion roundtrip precision that was a key benefit of the QuantityValue PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 528a7a8 commit 37d1641

608 files changed

Lines changed: 17574 additions & 17576 deletions

File tree

Some content is hidden

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

CodeGen/Generators/QuantityRelationsParser.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static void ParseAndApplyRelations(string rootDir, Quantity[] quantities)
4646

4747
// Add QuantityValue and 1 as pseudo-quantities to validate relations that use them.
4848
var pseudoQuantity = new Quantity { Name = null!, Units = [new Unit { SingularName = null! }] };
49-
quantityDictionary["QuantityValue"] = pseudoQuantity with { Name = "QuantityValue" };
49+
quantityDictionary["double"] = pseudoQuantity with { Name = "double" };
5050
quantityDictionary["1"] = pseudoQuantity with { Name = "1" };
5151

5252
var relations = ParseRelations(rootDir, quantityDictionary);
@@ -116,7 +116,7 @@ public static void ParseAndApplyRelations(string rootDir, Quantity[] quantities)
116116
// The left operand of a relation is responsible for generating the operator.
117117
quantityRelations.Add(relation);
118118
}
119-
else if (relation.RightQuantity == quantity && relation.LeftQuantity.Name is "QuantityValue")
119+
else if (relation.RightQuantity == quantity && relation.LeftQuantity.Name is "double")
120120
{
121121
// Because we cannot add operators to QuantityValue we make the right operand responsible in this case.
122122
quantityRelations.Add(relation);

CodeGen/Generators/UnitsNetGen/NumberExtensionsCS14Generator.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,16 @@ public static class NumberTo{_quantityName}Extensions
5353
continue;
5454

5555
Writer.WL(3, $@"
56-
/// <inheritdoc cref=""{_quantityName}.From{unit.PluralName}(QuantityValue)"" />");
56+
/// <inheritdoc cref=""{_quantityName}.From{unit.PluralName}(double)"" />");
5757

5858
// Include obsolete text from the quantity per extension method, to make it visible when the class is not explicitly referenced in code.
5959
Writer.WLIfText(3, GetObsoleteAttributeOrNull(unit.ObsoleteText ?? _quantity.ObsoleteText));
6060

6161
Writer.WL(3, $@"public {_quantityName} {unit.PluralName}
6262
#if NET7_0_OR_GREATER
63-
=> {_quantityName}.From{unit.PluralName}(QuantityValue.CreateChecked(value));
63+
=> {_quantityName}.From{unit.PluralName}(double.CreateChecked(value));
6464
#else
65-
=> {_quantityName}.From{unit.PluralName}(value.ToQuantityValue());
65+
=> {_quantityName}.From{unit.PluralName}(value.ToDouble(System.Globalization.CultureInfo.InvariantCulture));
6666
#endif
6767
");
6868
}

CodeGen/Generators/UnitsNetGen/NumberExtensionsGenerator.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static class NumberTo{_quantityName}Extensions
4040
continue;
4141

4242
Writer.WL(2, $@"
43-
/// <inheritdoc cref=""{_quantityName}.From{unit.PluralName}(QuantityValue)"" />");
43+
/// <inheritdoc cref=""{_quantityName}.From{unit.PluralName}(double)"" />");
4444

4545
// Include obsolete text from the quantity per extension method, to make it visible when the class is not explicitly referenced in code.
4646
Writer.WLIfText(2, GetObsoleteAttributeOrNull(unit.ObsoleteText ?? _quantity.ObsoleteText));
@@ -49,10 +49,10 @@ public static class NumberTo{_quantityName}Extensions
4949
where T : notnull
5050
#if NET7_0_OR_GREATER
5151
, INumber<T>
52-
=> {_quantityName}.From{unit.PluralName}(QuantityValue.CreateChecked(value));
52+
=> {_quantityName}.From{unit.PluralName}(double.CreateChecked(value));
5353
#else
5454
, IConvertible
55-
=> {_quantityName}.From{unit.PluralName}(value.ToQuantityValue());
55+
=> {_quantityName}.From{unit.PluralName}(value.ToDouble(System.Globalization.CultureInfo.InvariantCulture));
5656
#endif
5757
");
5858
}

CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ namespace UnitsNet
7373
/// The numeric value this quantity was constructed with.
7474
/// </summary>
7575
[DataMember(Name = ""Value"", Order = 1, EmitDefaultValue = false)]
76-
private readonly QuantityValue _value;
76+
private readonly double _value;
7777
7878
/// <summary>
7979
/// The unit this quantity was constructed with.
@@ -125,7 +125,7 @@ private void GenerateInterfaceExtensions()
125125
if (!_quantity.IsAffine)
126126
{
127127
Writer.WL($@"
128-
IDivisionOperators<{_quantity.Name}, {_quantity.Name}, QuantityValue>,");
128+
IDivisionOperators<{_quantity.Name}, {_quantity.Name}, double>,");
129129
}
130130

131131
if (_quantity.Relations.Any(r => r.Operator is "*" or "/"))
@@ -308,7 +308,7 @@ private void GenerateInstanceConstructors()
308308
/// </summary>
309309
/// <param name=""value"">The numeric value to construct this quantity with.</param>
310310
/// <param name=""unit"">The unit representation to construct this quantity with.</param>
311-
public {_quantity.Name}(QuantityValue value, {_unitEnumName} unit)
311+
public {_quantity.Name}(double value, {_unitEnumName} unit)
312312
{{");
313313
Writer.WL(@"
314314
_value = value;");
@@ -327,7 +327,7 @@ private void GenerateInstanceConstructors()
327327
/// <param name=""unitSystem"">The unit system to create the quantity with.</param>
328328
/// <exception cref=""ArgumentNullException"">The given <see cref=""UnitSystem""/> is null.</exception>
329329
/// <exception cref=""ArgumentException"">No unit was found for the given <see cref=""UnitSystem""/>.</exception>
330-
public {_quantity.Name}(QuantityValue value, UnitSystem unitSystem)
330+
public {_quantity.Name}(double value, UnitSystem unitSystem)
331331
{{
332332
_value = value;
333333
_unit = Info.GetDefaultUnit(unitSystem);
@@ -375,7 +375,7 @@ private void GenerateStaticProperties()
375375
{
376376
Writer.WL($@"
377377
/// <inheritdoc />
378-
public static QuantityValue LogarithmicScalingFactor {{get;}} = {10 * _quantity.LogarithmicScalingFactor};
378+
public static double LogarithmicScalingFactor {{get;}} = {10 * _quantity.LogarithmicScalingFactor};
379379
");
380380
}
381381

@@ -390,7 +390,7 @@ private void GenerateProperties()
390390
#region Properties
391391
392392
/// <inheritdoc />
393-
public QuantityValue Value => _value;
393+
public double Value => _value;
394394
395395
/// <inheritdoc />
396396
public {_unitEnumName} Unit => _unit.GetValueOrDefault(BaseUnit);
@@ -421,7 +421,7 @@ private void GenerateProperties()
421421
{
422422
Writer.WL($@"
423423
#if NETSTANDARD2_0
424-
QuantityValue ILogarithmicQuantity<{_quantity.Name}>.LogarithmicScalingFactor => LogarithmicScalingFactor;
424+
double ILogarithmicQuantity<{_quantity.Name}>.LogarithmicScalingFactor => LogarithmicScalingFactor;
425425
#endif
426426
");
427427
}
@@ -444,11 +444,11 @@ private void GenerateConversionProperties()
444444

445445
Writer.WL($@"
446446
/// <summary>
447-
/// Gets a <see cref=""QuantityValue""/> value of this quantity converted into <see cref=""{_unitEnumName}.{unit.SingularName}""/>
447+
/// Gets a <see cref=""double""/> value of this quantity converted into <see cref=""{_unitEnumName}.{unit.SingularName}""/>
448448
/// </summary>");
449449
Writer.WLIfText(2, GetObsoleteAttributeOrNull(unit));
450450
Writer.WL($@"
451-
public QuantityValue {unit.PluralName} => this.As({_unitEnumName}.{unit.SingularName});
451+
public double {unit.PluralName} => this.As({_unitEnumName}.{unit.SingularName});
452452
");
453453
}
454454

@@ -504,7 +504,7 @@ private void GenerateStaticFactoryMethods()
504504
/// </summary>");
505505
Writer.WLIfText(2, GetObsoleteAttributeOrNull(unit));
506506
Writer.WL($@"
507-
public static {_quantity.Name} From{unit.PluralName}(QuantityValue value)
507+
public static {_quantity.Name} From{unit.PluralName}(double value)
508508
{{
509509
return new {_quantity.Name}(value, {_unitEnumName}.{unit.SingularName});
510510
}}
@@ -518,7 +518,7 @@ private void GenerateStaticFactoryMethods()
518518
/// <param name=""value"">Value to convert from.</param>
519519
/// <param name=""fromUnit"">Unit to convert from.</param>
520520
/// <returns>{_quantity.Name} unit value.</returns>
521-
public static {_quantity.Name} From(QuantityValue value, {_unitEnumName} fromUnit)
521+
public static {_quantity.Name} From(double value, {_unitEnumName} fromUnit)
522522
{{
523523
return new {_quantity.Name}(value, fromUnit);
524524
}}
@@ -707,25 +707,25 @@ private void GenerateArithmeticOperators()
707707
}}
708708
709709
/// <summary>Get <see cref=""{_quantity.Name}""/> from multiplying value and <see cref=""{_quantity.Name}""/>.</summary>
710-
public static {_quantity.Name} operator *(QuantityValue left, {_quantity.Name} right)
710+
public static {_quantity.Name} operator *(double left, {_quantity.Name} right)
711711
{{
712712
return new {_quantity.Name}(left * right.Value, right.Unit);
713713
}}
714714
715715
/// <summary>Get <see cref=""{_quantity.Name}""/> from multiplying value and <see cref=""{_quantity.Name}""/>.</summary>
716-
public static {_quantity.Name} operator *({_quantity.Name} left, QuantityValue right)
716+
public static {_quantity.Name} operator *({_quantity.Name} left, double right)
717717
{{
718718
return new {_quantity.Name}(left.Value * right, left.Unit);
719719
}}
720720
721721
/// <summary>Get <see cref=""{_quantity.Name}""/> from dividing <see cref=""{_quantity.Name}""/> by value.</summary>
722-
public static {_quantity.Name} operator /({_quantity.Name} left, QuantityValue right)
722+
public static {_quantity.Name} operator /({_quantity.Name} left, double right)
723723
{{
724724
return new {_quantity.Name}(left.Value / right, left.Unit);
725725
}}
726726
727727
/// <summary>Get ratio value from dividing <see cref=""{_quantity.Name}""/> by <see cref=""{_quantity.Name}""/>.</summary>
728-
public static QuantityValue operator /({_quantity.Name} left, {_quantity.Name} right)
728+
public static double operator /({_quantity.Name} left, {_quantity.Name} right)
729729
{{
730730
return left.{_baseUnit.PluralName} / right.{_baseUnit.PluralName};
731731
}}
@@ -752,7 +752,7 @@ private void GenerateLogarithmicArithmeticOperators()
752752
/// </remarks>
753753
public static {_quantity.Name} operator +({_quantity.Name} left, {_quantity.Name} right)
754754
{{
755-
return new {_quantity.Name}(QuantityValueExtensions.AddWithLogScaling(left.Value, right.As(left.Unit), LogarithmicScalingFactor), left.Unit);
755+
return new {_quantity.Name}((double)QuantityValueExtensions.AddWithLogScaling((QuantityValue)left.Value, (QuantityValue)right.As(left.Unit), (QuantityValue)LogarithmicScalingFactor), left.Unit);
756756
}}
757757
758758
/// <summary>Get <see cref=""{_quantity.Name}""/> from logarithmic subtraction of two <see cref=""{_quantity.Name}""/>.</summary>
@@ -761,29 +761,29 @@ private void GenerateLogarithmicArithmeticOperators()
761761
/// </remarks>
762762
public static {_quantity.Name} operator -({_quantity.Name} left, {_quantity.Name} right)
763763
{{
764-
return new {_quantity.Name}(QuantityValueExtensions.SubtractWithLogScaling(left.Value, right.As(left.Unit), LogarithmicScalingFactor), left.Unit);
764+
return new {_quantity.Name}((double)QuantityValueExtensions.SubtractWithLogScaling((QuantityValue)left.Value, (QuantityValue)right.As(left.Unit), (QuantityValue)LogarithmicScalingFactor), left.Unit);
765765
}}
766766
767767
/// <summary>Get <see cref=""{_quantity.Name}""/> from logarithmic multiplication of value and <see cref=""{_quantity.Name}""/>.</summary>
768-
public static {_quantity.Name} operator *(QuantityValue left, {_quantity.Name} right)
768+
public static {_quantity.Name} operator *(double left, {_quantity.Name} right)
769769
{{
770770
return new {_quantity.Name}(left + right.Value, right.Unit);
771771
}}
772772
773773
/// <summary>Get <see cref=""{_quantity.Name}""/> from logarithmic multiplication of value and <see cref=""{_quantity.Name}""/>.</summary>
774-
public static {_quantity.Name} operator *({_quantity.Name} left, QuantityValue right)
774+
public static {_quantity.Name} operator *({_quantity.Name} left, double right)
775775
{{
776776
return new {_quantity.Name}(left.Value + right, left.Unit);
777777
}}
778778
779779
/// <summary>Get <see cref=""{_quantity.Name}""/> from logarithmic division of <see cref=""{_quantity.Name}""/> by value.</summary>
780-
public static {_quantity.Name} operator /({_quantity.Name} left, QuantityValue right)
780+
public static {_quantity.Name} operator /({_quantity.Name} left, double right)
781781
{{
782782
return new {_quantity.Name}(left.Value - right, left.Unit);
783783
}}
784784
785785
/// <summary>Get ratio value from logarithmic division of <see cref=""{_quantity.Name}""/> by <see cref=""{_quantity.Name}""/>.</summary>
786-
public static QuantityValue operator /({_quantity.Name} left, {_quantity.Name} right)
786+
public static double operator /({_quantity.Name} left, {_quantity.Name} right)
787787
{{
788788
return left.Value - right.As(left.Unit);
789789
}}
@@ -839,7 +839,7 @@ private void GenerateRelationalOperators(bool inverseWithFixedUnit = false)
839839
}
840840
else
841841
{
842-
const string valueType = "QuantityValue";
842+
const string valueType = "double";
843843
var leftParameterType = relation.LeftQuantity.Name;
844844
var leftConversionProperty = relation.LeftUnit.PluralName;
845845
var rightParameterType = relation.RightQuantity.Name;

CodeGen/Generators/UnitsNetGen/UnitTestBaseClassGenerator.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ public void ToUnit_UnitSystem_ThrowsArgumentExceptionIfNotSupported()
599599
}
600600

601601
Writer.WL($@"
602-
public void Parse(string culture, string quantityString, {_unitEnumName} expectedUnit, decimal expectedValue)
602+
public void Parse(string culture, string quantityString, {_unitEnumName} expectedUnit, double expectedValue)
603603
{{
604604
using var _ = new CultureScope(culture);
605605
var parsed = {_quantity.Name}.Parse(quantityString);
@@ -644,7 +644,7 @@ public void ParseWithAmbiguousAbbreviation(string culture, string quantityString
644644
}
645645

646646
Writer.WL($@"
647-
public void TryParse(string culture, string quantityString, {_unitEnumName} expectedUnit, decimal expectedValue)
647+
public void TryParse(string culture, string quantityString, {_unitEnumName} expectedUnit, double expectedValue)
648648
{{
649649
using var _ = new CultureScope(culture);
650650
Assert.True({_quantity.Name}.TryParse(quantityString, out {_quantity.Name} parsed));
@@ -1129,8 +1129,8 @@ public void Equals_Logarithmic_WithTolerance(double firstValue, double secondVal
11291129
var quantity = {_quantity.Name}.From{_baseUnit.PluralName}(firstValue);
11301130
var otherQuantity = {_quantity.Name}.From{_baseUnit.PluralName}(secondValue);
11311131
{differenceResultType} maxTolerance = quantity > otherQuantity ? quantity - otherQuantity : otherQuantity - quantity;
1132-
var largerTolerance = maxTolerance * 1.1m;
1133-
var smallerTolerance = maxTolerance / 1.1m;
1132+
var largerTolerance = maxTolerance * 1.1;
1133+
var smallerTolerance = maxTolerance / 1.1;
11341134
Assert.True(quantity.Equals(quantity, {differenceResultType}.Zero));
11351135
Assert.True(quantity.Equals(quantity, maxTolerance));
11361136
Assert.True(quantity.Equals(otherQuantity, largerTolerance));
@@ -1162,8 +1162,8 @@ public void Equals_WithTolerance(double firstValue, double secondValue)
11621162
var quantity = {_quantity.Name}.From{_baseUnit.PluralName}(firstValue);
11631163
var otherQuantity = {_quantity.Name}.From{_baseUnit.PluralName}(secondValue);
11641164
{differenceResultType} maxTolerance = quantity > otherQuantity ? quantity - otherQuantity : otherQuantity - quantity;
1165-
var largerTolerance = maxTolerance * 1.1m;
1166-
var smallerTolerance = maxTolerance / 1.1m;
1165+
var largerTolerance = maxTolerance * 1.1;
1166+
var smallerTolerance = maxTolerance / 1.1;
11671167
Assert.True(quantity.Equals(quantity, {differenceResultType}.Zero));
11681168
Assert.True(quantity.Equals(quantity, maxTolerance));
11691169
Assert.True(quantity.Equals(otherQuantity, maxTolerance));

Common/UnitRelations.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"Area.SquareMeter = Length.Meter * Length.Meter",
1212
"Area.SquareMeter = Volume.CubicMeter * ReciprocalLength.InverseMeter",
1313
"AreaMomentOfInertia.MeterToTheFourth = Volume.CubicMeter * Length.Meter",
14+
"double = SpecificEnergy.JoulePerKilogram * BrakeSpecificFuelConsumption.KilogramPerJoule",
1415
"DynamicViscosity.NewtonSecondPerMeterSquared = Density.KilogramPerCubicMeter * KinematicViscosity.SquareMeterPerSecond",
1516
"ElectricCharge.AmpereHour = ElectricCurrent.Ampere * Duration.Hour",
1617
"ElectricCurrent.Ampere = ElectricCurrentGradient.AmperePerSecond * Duration.Second",
@@ -62,7 +63,6 @@
6263
"Pressure.NewtonPerSquareMeter = ForcePerLength.NewtonPerMeter * ReciprocalLength.InverseMeter",
6364
"Pressure.Pascal = PressureChangeRate.PascalPerSecond * Duration.Second",
6465
"Pressure.Pascal = SpecificWeight.NewtonPerCubicMeter * Length.Meter",
65-
"QuantityValue = SpecificEnergy.JoulePerKilogram * BrakeSpecificFuelConsumption.KilogramPerJoule",
6666
"RadiationEquivalentDose.Sievert = RadiationEquivalentDoseRate.SievertPerHour * Duration.Hour",
6767
"Ratio.DecimalFraction = Area.SquareMeter * ReciprocalArea.InverseSquareMeter -- NoInferredDivision",
6868
"Ratio.DecimalFraction = TemperatureDelta.Kelvin * CoefficientOfThermalExpansion.PerKelvin -- NoInferredDivision",

UnitsNet.Benchmark/BenchmarkHelpers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static (TQuantity Quantity, TUnit Unit)[] GetRandomConversions<TQuantity,
2828
return GetRandomConversions<TQuantity, TUnit>(random, value, options.ToArray(), nbConversions);
2929
}
3030

31-
public static (TQuantity Quantity, TUnit Unit)[] GetRandomConversions<TQuantity, TUnit>(this Random random, QuantityValue value, TUnit[] options,
31+
public static (TQuantity Quantity, TUnit Unit)[] GetRandomConversions<TQuantity, TUnit>(this Random random, double value, TUnit[] options,
3232
int nbConversions)
3333
where TQuantity : IQuantity<TUnit>
3434
where TUnit : struct, Enum
@@ -38,7 +38,7 @@ public static (TQuantity Quantity, TUnit Unit)[] GetRandomConversions<TQuantity,
3838
return quantities.Zip(units, (quantity, unit) => (quantity, unit)).ToArray();
3939
}
4040

41-
public static IEnumerable<TQuantity> GetRandomQuantities<TQuantity, TUnit>(this Random random, QuantityValue value, TUnit[] units, int nbQuantities)
41+
public static IEnumerable<TQuantity> GetRandomQuantities<TQuantity, TUnit>(this Random random, double value, TUnit[] units, int nbQuantities)
4242
where TQuantity : IQuantity<TUnit>
4343
where TUnit : struct, Enum
4444
{

UnitsNet.Benchmark/Comparisons/ComparisonBenchmarks.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ public class ComparisonBenchmarks
3131
public MassUnit RightUnit { get; set; }
3232

3333
[ParamsSource(nameof(LeftValues))]
34-
public QuantityValue LeftValue { get; set; }
34+
public double LeftValue { get; set; }
35+
36+
public double[] LeftValues => [95];
37+
3538

36-
public QuantityValue[] LeftValues => [95];
37-
38-
3939
[ParamsSource(nameof(RightValues))]
40-
public QuantityValue RightValue { get; set; }
40+
public double RightValue { get; set; }
4141

42-
public QuantityValue[] RightValues => [95];
42+
public double[] RightValues => [95];
4343

4444
private Mass _leftQuantity, _rightQuantity;
4545

@@ -51,7 +51,7 @@ public void GlobalSetup()
5151
.WithQuantities([Mass.Info])
5252
.WithConverterOptions(new QuantityConverterBuildOptions(Frozen, CachingMode)));
5353
Console.Out.WriteLine("Default configuration set.");
54-
Quantity.From(QuantityValue.Zero, MassUnit.Kilogram);
54+
Quantity.From(0, MassUnit.Kilogram);
5555

5656
_leftQuantity = new Mass(LeftValue, LeftUnit);
5757
_rightQuantity = new Mass(RightValue, RightUnit);

0 commit comments

Comments
 (0)