diff --git a/source/Quantities.Benchmark/Compare/Creation.cs b/source/Quantities.Benchmark/Compare/Creation.cs new file mode 100644 index 00000000..9fe7232b --- /dev/null +++ b/source/Quantities.Benchmark/Compare/Creation.cs @@ -0,0 +1,56 @@ +using BenchmarkDotNet.Configs; +using Quantities.Prefixes; +using Quantities.Units.Si; +using Quantities.Units.Si.Metric; + +namespace Quantities.Benchmark.Compare; + +[MemoryDiagnoser] +[CategoriesColumn] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +public class Creation +{ + private const String Scalar = nameof(Scalar); + private const String Cubed = nameof(Cubed); + private const String Aliasing = nameof(Aliasing); + private static readonly Double value = Math.E; + + [BenchmarkCategory(Scalar), Benchmark(Baseline = true)] + public Length CreateScalarQuantity() => Length.Of(value, Si()); + + [BenchmarkCategory(Scalar), Benchmark] + public UnitsNet.Length CreateScalarUnitsNet() => UnitsNet.Length.FromCentimeters(value); + + [BenchmarkCategory(Cubed), Benchmark(Baseline = true)] + public Volume CreateCubedQuantity() => Volume.Of(value, Cubic(Si())); + + [BenchmarkCategory(Cubed), Benchmark] + public UnitsNet.Volume CreateCubedUnitsNet() => UnitsNet.Volume.FromCubicCentimeters(value); + + [BenchmarkCategory(Aliasing), Benchmark(Baseline = true)] + public Volume CreateAliasedQuantity() => Volume.Of(value, Metric()); + + [BenchmarkCategory(Aliasing), Benchmark] + public UnitsNet.Volume CreateAliasedUnitsNet() => UnitsNet.Volume.FromCentiliters(value); +} + +/* Summary * + +BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3007/23H2/2023Update/SunValley3) +12th Gen Intel Core i7-1260P, 1 CPU, 16 logical and 12 physical cores +.NET SDK 8.0.101 + [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 + + +| Method | Categories | Mean | Error | Ratio | Allocated | Alloc Ratio | +|---------------------- |----------- |----------:|----------:|------:|----------:|------------:| +| CreateAliasedQuantity | Aliasing | 11.847 ns | 0.1295 ns | 1.00 | - | NA | +| CreateAliasedUnitsNet | Aliasing | 3.543 ns | 0.0291 ns | 0.30 | - | NA | +| | | | | | | | +| CreateCubedQuantity | Cubed | 1.576 ns | 0.0293 ns | 1.00 | - | NA | +| CreateCubedUnitsNet | Cubed | 3.562 ns | 0.0355 ns | 2.26 | - | NA | +| | | | | | | | +| CreateScalarQuantity | Scalar | 1.349 ns | 0.0181 ns | 1.00 | - | NA | +| CreateScalarUnitsNet | Scalar | 2.943 ns | 0.0353 ns | 2.18 | - | NA | +*/ diff --git a/source/Quantities.Benchmark/Compare/Division.cs b/source/Quantities.Benchmark/Compare/Division.cs new file mode 100644 index 00000000..f75b7d6d --- /dev/null +++ b/source/Quantities.Benchmark/Compare/Division.cs @@ -0,0 +1,37 @@ +using Quantities.Prefixes; +using Quantities.Units.Imperial.Length; +using Quantities.Units.Si.Metric; +using nLength = UnitsNet.Length; +using nVolume = UnitsNet.Volume; + +namespace Quantities.Benchmark.Compare; + +[MemoryDiagnoser] +public class Division +{ + private static readonly Volume left = Volume.Of(32, Metric()); + private static readonly Length right = Length.Of(4, Imperial()); + private static readonly nVolume nLeft = nVolume.FromCentiliters(32); + private static readonly nLength nRight = nLength.FromFeet(4); + + [Benchmark(Baseline = true)] + public Area Quantity() => left / right; + + [Benchmark] + public UnitsNet.Area UnitsNet() => nLeft / nRight; +} + +/* Summary * + +BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3007/23H2/2023Update/SunValley3) +12th Gen Intel Core i7-1260P, 1 CPU, 16 logical and 12 physical cores +.NET SDK 8.0.101 + [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 + + +| Method | Mean | Error | Ratio | Allocated | Alloc Ratio | +|--------- |----------:|----------:|------:|----------:|------------:| +| Quantity | 6.624 ns | 0.0736 ns | 1.00 | - | NA | +| UnitsNet | 24.402 ns | 0.1962 ns | 3.68 | - | NA | +*/ diff --git a/source/Quantities.Benchmark/Compare/Multiplication.cs b/source/Quantities.Benchmark/Compare/Multiplication.cs new file mode 100644 index 00000000..aef1dbe3 --- /dev/null +++ b/source/Quantities.Benchmark/Compare/Multiplication.cs @@ -0,0 +1,37 @@ +using Quantities.Prefixes; +using Quantities.Units.Imperial.Length; +using Quantities.Units.Si; + +using nLength = UnitsNet.Length; + +namespace Quantities.Benchmark.Compare; + +[MemoryDiagnoser] +public class Multiplication +{ + private static readonly Length left = Length.Of(3, Si()); + private static readonly Length right = Length.Of(4, Imperial()); + private static readonly nLength nLeft = nLength.FromMillimeters(3); + private static readonly nLength nRight = nLength.FromMillimeters(4); + + [Benchmark(Baseline = true)] + public Area Quantity() => left * right; + + [Benchmark] + public UnitsNet.Area UnitsNet() => nLeft * nRight; +} + +/* Summary * + +BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3007/23H2/2023Update/SunValley3) +12th Gen Intel Core i7-1260P, 1 CPU, 16 logical and 12 physical cores +.NET SDK 8.0.101 + [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 + + +| Method | Mean | Error | Ratio | Allocated | Alloc Ratio | +|--------- |----------:|----------:|------:|----------:|------------:| +| Quantity | 6.646 ns | 0.0605 ns | 1.00 | - | NA | +| UnitsNet | 21.431 ns | 0.1119 ns | 3.22 | - | NA | +*/ diff --git a/source/Quantities.Benchmark/Compare/QuantityConversionComparison.cs b/source/Quantities.Benchmark/Compare/QuantityConversionComparison.cs new file mode 100644 index 00000000..9fae4354 --- /dev/null +++ b/source/Quantities.Benchmark/Compare/QuantityConversionComparison.cs @@ -0,0 +1,57 @@ +using BenchmarkDotNet.Configs; +using Quantities.Prefixes; +using Quantities.Units.Imperial.Length; +using Quantities.Units.Si; +using UnitsNet.Units; +using nLength = UnitsNet.Length; + +namespace Quantities.Benchmark.Compare; + +[MemoryDiagnoser(displayGenColumns: false)] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +public class QuantityConversionComparison +{ + private const String ToSi = nameof(ToSi); + private const String ToImperial = nameof(ToImperial); + private const String ToSame = nameof(ToSame); + private static readonly Length si = Length.Of(3, Si()); + private static readonly Length imperial = Length.Of(4, Imperial()); + private static readonly nLength nSi = nLength.FromMillimeters(3); + private static readonly nLength nImperial = nLength.FromFeet(4); + + [BenchmarkCategory(ToSi), Benchmark(Baseline = true)] + public Length QuantityToSi() => imperial.To(Si()); + [BenchmarkCategory(ToSi), Benchmark] + public nLength UnitsNetToSi() => nImperial.ToUnit(LengthUnit.Millimeter); + + [BenchmarkCategory(ToImperial), Benchmark(Baseline = true)] + public Length QuantityToImperial() => si.To(Imperial()); + [BenchmarkCategory(ToImperial), Benchmark] + public nLength UnitsNetToImperial() => nSi.ToUnit(LengthUnit.Foot); + + [BenchmarkCategory(ToSame), Benchmark(Baseline = true)] + public Length QuantityToSame() => imperial.To(Imperial()); + [BenchmarkCategory(ToSame), Benchmark] + public nLength UnitsNetToSame() => nImperial.ToUnit(LengthUnit.Foot); +} + +/* Summary * + +BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3007/23H2/2023Update/SunValley3) +12th Gen Intel Core i7-1260P, 1 CPU, 16 logical and 12 physical cores +.NET SDK 8.0.101 + [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 + + +| Method | Mean | Error | Ratio | Allocated | Alloc Ratio | +|------------------- |----------:|----------:|------:|----------:|------------:| +| QuantityToImperial | 1.925 ns | 0.0554 ns | 1.00 | - | NA | +| UnitsNetToImperial | 36.503 ns | 0.5333 ns | 18.94 | 48 B | NA | +| | | | | | | +| QuantityToSame | 1.924 ns | 0.0620 ns | 1.00 | - | NA | +| UnitsNetToSame | 11.712 ns | 0.0609 ns | 5.76 | - | NA | +| | | | | | | +| QuantityToSi | 1.946 ns | 0.0489 ns | 1.00 | - | NA | +| UnitsNetToSi | 38.461 ns | 0.4820 ns | 19.77 | 48 B | NA | +*/ diff --git a/source/Quantities.Benchmark/Compare/ValueConversionComparison.cs b/source/Quantities.Benchmark/Compare/ValueConversionComparison.cs new file mode 100644 index 00000000..86c4d483 --- /dev/null +++ b/source/Quantities.Benchmark/Compare/ValueConversionComparison.cs @@ -0,0 +1,56 @@ +using BenchmarkDotNet.Configs; +using Quantities.Prefixes; +using Quantities.Units.Imperial.Length; +using Quantities.Units.Si; +using nLength = UnitsNet.Length; + +namespace Quantities.Benchmark.Compare; + +[MemoryDiagnoser(displayGenColumns: false)] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +public class ValueConversionComparison +{ + private const String ToSi = nameof(ToSi); + private const String ToImperial = nameof(ToImperial); + private const String ToSame = nameof(ToSame); + private static readonly Length si = Length.Of(3, Si()); + private static readonly Length imperial = Length.Of(4, Imperial()); + private static readonly nLength nSi = nLength.FromMillimeters(3); + private static readonly nLength nImperial = nLength.FromFeet(4); + + [BenchmarkCategory(ToSi), Benchmark(Baseline = true)] + public Double QuantityToSi() => imperial.To(Si()); + [BenchmarkCategory(ToSi), Benchmark] + public Double UnitsNetToSi() => nImperial.Millimeters; + + [BenchmarkCategory(ToImperial), Benchmark(Baseline = true)] + public Double QuantityToImperial() => si.To(Imperial()); + [BenchmarkCategory(ToImperial), Benchmark] + public Double UnitsNetToImperial() => nSi.Feet; + + [BenchmarkCategory(ToSame), Benchmark(Baseline = true)] + public Double QuantityToSame() => imperial.To(Imperial()); + [BenchmarkCategory(ToSame), Benchmark] + public Double UnitsNetToSame() => nImperial.Feet; +} + +/* Summary * + +BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3007/23H2/2023Update/SunValley3) +12th Gen Intel Core i7-1260P, 1 CPU, 16 logical and 12 physical cores +.NET SDK 8.0.101 + [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 + + +| Method | Mean | Error | Ratio | Allocated | Alloc Ratio | +|------------------- |-----------:|----------:|------:|----------:|------------:| +| QuantityToImperial | 0.7591 ns | 0.0442 ns | 1.00 | - | NA | +| UnitsNetToImperial | 39.1487 ns | 0.4276 ns | 51.91 | 48 B | NA | +| | | | | | | +| QuantityToSame | 0.3554 ns | 0.0148 ns | 1.00 | - | NA | +| UnitsNetToSame | 0.1525 ns | 0.0145 ns | 0.43 | - | NA | +| | | | | | | +| QuantityToSi | 0.7803 ns | 0.0433 ns | 1.00 | - | NA | +| UnitsNetToSi | 39.7448 ns | 0.3878 ns | 51.33 | 48 B | NA | +*/ diff --git a/source/Quantities.Benchmark/Quantities.Benchmark.csproj b/source/Quantities.Benchmark/Quantities.Benchmark.csproj index 8bd62ce5..df51465a 100644 --- a/source/Quantities.Benchmark/Quantities.Benchmark.csproj +++ b/source/Quantities.Benchmark/Quantities.Benchmark.csproj @@ -20,6 +20,7 @@ + diff --git a/source/Quantities.Test/Compare/QuantitiesAndUnitsNetAreEquallyAccurate.cs b/source/Quantities.Test/Compare/QuantitiesAndUnitsNetAreEquallyAccurate.cs new file mode 100644 index 00000000..412fd7b2 --- /dev/null +++ b/source/Quantities.Test/Compare/QuantitiesAndUnitsNetAreEquallyAccurate.cs @@ -0,0 +1,87 @@ +using UnitsNet; +using Quantities.Units.Si.Metric; +using Quantities.Units.Si.Metric.UnitsOfInformation; +using Quantities.Units.Si.Derived; +using Quantities.Units.Imperial.Temperature; +using Quantities.Units.NonStandard.Temperature; + +using nLength = UnitsNet.Length; +using nTemperature = UnitsNet.Temperature; +using Bytes = Quantities.Units.Si.Metric.UnitsOfInformation.Byte; + +namespace Quantities.Test.Compare; + +public class QuantitiesAndUnitsNetAreEquallyAccurate +{ + [Fact] + public void ÅngströmToNanoMetre() + { + const Double expectedNanoMetre = 1d; + + // Quantities + Length ångström = Length.Of(10, Metric<Ångström>()); + Length nanoMetres = ångström.To(Si()); + + // UnitsNet + nLength nÅngström = nLength.FromAngstroms(10); + nLength nNanoMetre = nÅngström.ToUnit(UnitsNet.Units.LengthUnit.Nanometer); + + // Equally precise + Assert.Equal(expectedNanoMetre, nanoMetres); + Assert.Equal(expectedNanoMetre, nNanoMetre.Value); + } + + [Fact] + public void GibiBitPerHourToKiloBytePerMinute() + { + const Double expectedRate = 1024d * 1024d * 1024d / 1000d; + + // Quantities + DataRate speed = DataRate.Of(8, Binary().Per(Si())); + DataRate actual = speed.To(Metric().Per(Si())); + + // UnitsNet + BitRate nSpeed = BitRate.FromGibibitsPerSecond(8); + BitRate nActual = nSpeed.ToUnit(UnitsNet.Units.BitRateUnit.KilobytePerSecond); + + // Equally precise + Assert.Equal(expectedRate, actual); + Assert.Equal(expectedRate, (Double)nActual.Value); // but Value is a Decimal here :-/ + } + + [Fact] + public void CelsiusToFahrenheit() + { + const Double expectedFahrenheit = 98.6; + + // Quantities + Temperature temperature = Temperature.Of(37.0, Metric()); + Temperature actual = temperature.To(Imperial()); + + // UnitsNet + nTemperature nTemperature = nTemperature.FromDegreesCelsius(37.0); + nTemperature nActual = nTemperature.ToUnit(UnitsNet.Units.TemperatureUnit.DegreeFahrenheit); + + // Equally precise :-) + Assert.Equal(expectedFahrenheit, actual); + Assert.Equal(expectedFahrenheit, nActual.Value); + } + + [Fact] + public void KelvinToRømer() + { + const Double expectedRømer = -1.8345; // calculated using full precision math. + + // Quantities + Temperature temperature = Temperature.Of(255.37, Si()); + Temperature actual = temperature.To(NonStandard()); + + // UnitsNet + nTemperature nTemperature = nTemperature.FromKelvins(255.37); + nTemperature nActual = nTemperature.ToUnit(UnitsNet.Units.TemperatureUnit.DegreeRoemer); + + // Quantities and UnitsNet are equally bad (measured by precision parameter only) + Assert.Equal(expectedRømer, actual, 13); // -1.8344999999999885 + Assert.Equal(expectedRømer, nActual.Value, 13); // -1.83449999999999 + } +} diff --git a/source/Quantities.Test/Compare/QuantitiesIsMoreAccurateThanUnitsNet.cs b/source/Quantities.Test/Compare/QuantitiesIsMoreAccurateThanUnitsNet.cs new file mode 100644 index 00000000..37fbd28d --- /dev/null +++ b/source/Quantities.Test/Compare/QuantitiesIsMoreAccurateThanUnitsNet.cs @@ -0,0 +1,116 @@ +using Quantities.Units.Si.Metric; +using Quantities.Units.Imperial.Area; +using Quantities.Units.Imperial.Mass; + +using nArea = UnitsNet.Area; +using nLength = UnitsNet.Length; +using nMass = UnitsNet.Mass; +using nVolume = UnitsNet.Volume; + +namespace Quantities.Test.Compare; + +public class QuantitiesIsMoreAccurateThanUnitsNet +{ + [Fact] + public void SquareMilesToSquareKilometres() + { + Double expectedSqKiloMetre = 5.179976220672; // 2×1.609344×1.609344 using full precision math. + + // Quantities + Area squareMiles = Area.Of(2, Square(Imperial())); + Area actual = squareMiles.To(Square(Si())); + + // UnitsNet + nArea nSquareMiles = nArea.FromSquareMiles(2); + nArea nActual = nSquareMiles.ToUnit(UnitsNet.Units.AreaUnit.SquareKilometer); + + // Quantities is marginally more accurate that UnitsNet + Assert.Equal(expectedSqKiloMetre, actual); + Assert.Equal(expectedSqKiloMetre, nActual.Value, 14); // two digits less accurate. + + // Sanity check + Double roundRobinSqMiles = actual.To(Square(Imperial())); + Double nRoundRobinSqMiles = nActual.ToUnit(UnitsNet.Units.AreaUnit.SquareMile).Value; + Assert.Equal(2d, roundRobinSqMiles); + Assert.Equal(2d, nRoundRobinSqMiles); + } + + [Fact] + public void SquareFeetTimesYards() + { + const Double expectedCubicFeet = 162; + + // Quantities + Area area = Area.Of(27, Square(Imperial())); + Length length = Length.Of(2, Imperial()); + + Volume actual = area * length; + + // UnitsNet + nArea nA = nArea.FromSquareFeet(27); + nLength nL = nLength.FromYards(2); + + nVolume nActualInCubicMetres = nA * nL; // results in m³ + // need to convert to cubic feet first... + nVolume nActual = nActualInCubicMetres.ToUnit(UnitsNet.Units.VolumeUnit.CubicFoot); + + // Quantities is marginally more accurate that UnitsNet + Assert.Equal(expectedCubicFeet, actual); + Assert.Equal(expectedCubicFeet, nActual.Value, 13); + + // Benefit of the doubt, let's check UnitsNet default answer: m³ + const Double expectedCubicMetre = 4.587329147904; // 162×(0.3048×0.3048×0.3048) using full precision math + Volume cubicMetre = actual.To(Cubic(Si())); + Assert.Equal(expectedCubicMetre, cubicMetre); // is accurate + Assert.Equal(expectedCubicMetre, nActualInCubicMetres.Value); // is also accurate + } + + [Fact] + public void PureArealDimensionDividedByLength() + { + const Double expectedYards = 16; + + // Quantities + Area area = Area.Of(2, Imperial()); + Length length = Length.Of(1815, Imperial()); + + Length actual = area / length; + + // UnitsNet + nArea nA = nArea.FromAcres(2); + nLength nL = nLength.FromFeet(1815); + + nLength nActualInMeters = nA / nL; // is in m + // need to convert to yards first + nLength nActual = nActualInMeters.ToUnit(UnitsNet.Units.LengthUnit.Yard); + + // Quantities is marginally more accurate that UnitsNet + Assert.Equal(expectedYards, actual); + Assert.Equal(expectedYards, nActual.Value, 14); + + // Benefit of the doubt, let's check UnitsNet default answer: m + const Double expectedMetre = 14.6304; // 16×0.9144 using full precision math + Length metres = actual.To(Si()); + Assert.Equal(expectedMetre, metres); // is accurate + Assert.Equal(expectedMetre, nActualInMeters.Value, 14); // is less accurate + } + + [Fact] + public void GramToPound() + { + const Double expectedPounds = 3d; + const Double gramsInPound = 453.59237; // this is the definition of the pound in grams... + + // Quantities + Mass mass = Mass.Of(expectedPounds * gramsInPound, Metric()); + Mass actual = mass.To(Imperial()); + + // UnitsNet + nMass nMass = nMass.FromGrams(expectedPounds * gramsInPound); + nMass nActual = nMass.ToUnit(UnitsNet.Units.MassUnit.Pound); + + // Quantities is marginally more accurate that UnitsNet + Assert.Equal(expectedPounds, actual); + Assert.Equal(expectedPounds, nActual.Value, 15); + } +} diff --git a/source/Quantities.Test/Compare/UnitsNetIsMoreAccurateThanQuantities.cs b/source/Quantities.Test/Compare/UnitsNetIsMoreAccurateThanQuantities.cs new file mode 100644 index 00000000..8e1522e7 --- /dev/null +++ b/source/Quantities.Test/Compare/UnitsNetIsMoreAccurateThanQuantities.cs @@ -0,0 +1,7 @@ +namespace Quantities.Test.Compare; + +public class UnitsNetIsMoreAccurateThanQuantities +{ + [Fact(Skip = "No known examples so far")] + public void NoKnownExamplesSoFar() { } +} diff --git a/source/Quantities.Test/Quantities.Test.csproj b/source/Quantities.Test/Quantities.Test.csproj index d319eb9b..114c797b 100644 --- a/source/Quantities.Test/Quantities.Test.csproj +++ b/source/Quantities.Test/Quantities.Test.csproj @@ -12,4 +12,8 @@ + + + +