From 4e3f32566a112d24cc83b76b65ef318fca2889a0 Mon Sep 17 00:00:00 2001 From: Daishi Nakase Date: Sat, 24 Jan 2026 15:33:16 +0900 Subject: [PATCH 1/4] fixes scripts --- Measure-Coverage.ps1 | 4 ++-- global.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Measure-Coverage.ps1 b/Measure-Coverage.ps1 index 12c3312..a214bbb 100644 --- a/Measure-Coverage.ps1 +++ b/Measure-Coverage.ps1 @@ -15,10 +15,10 @@ try { Get-ChildItem -Directory src/*.Test*/TestResults/* | Remove-Item -Recurse # rebuild target project to generate source generator files - dotnet build src/QuantitiesDotNet.slnx --no-incremental --property:EmitCompilerGeneratedFiles=true + dotnet build QuantitiesDotNet.slnx --no-incremental --property:EmitCompilerGeneratedFiles=true # test and measure coverage - dotnet test src/QuantitiesDotNet.slnx --collect:"XPlat Code Coverage" --settings src/etc/coverlet.runsettings + dotnet test QuantitiesDotNet.slnx --collect:"XPlat Code Coverage" --settings src/etc/coverlet.runsettings # export HTML coverage report Get-ChildItem src/*.Test*/TestResults/*/coverage.cobertura.xml ` diff --git a/global.json b/global.json index c30100d..1e2cd47 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.101", + "version": "10.0.0", "rollForward": "latestFeature" } } From 16ed5c175b83bdb5f1b78441d0d4911c1764459b Mon Sep 17 00:00:00 2001 From: Daishi Nakase Date: Sat, 24 Jan 2026 16:07:55 +0900 Subject: [PATCH 2/4] adds comments --- src/QuantitiesDotNet/IQuantity.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/QuantitiesDotNet/IQuantity.cs b/src/QuantitiesDotNet/IQuantity.cs index 0418cde..b8c2649 100644 --- a/src/QuantitiesDotNet/IQuantity.cs +++ b/src/QuantitiesDotNet/IQuantity.cs @@ -39,12 +39,18 @@ public interface IQuantity /// /// /// + /// + /// The implementation must be x.RawValue == y.RawValue. + /// public static abstract bool Equals(TSelf x, TSelf y); /// Determines which value is greater than another. /// /// /// + /// + /// The implementation must be x.RawValue == y.RawValue ? 0 : (x.RawValue < y.RawValue ? -1 : 1). + /// public static abstract int Compare(TSelf x, TSelf y); #endif From 9802255a012da78e4d9798927d0658bab17858c7 Mon Sep 17 00:00:00 2001 From: Daishi Nakase Date: Sat, 24 Jan 2026 16:53:53 +0900 Subject: [PATCH 3/4] fixes numerics edge case behaviour --- .../QuantityImplementBuilder.cs | 30 ++++++++++--------- src/QuantitiesDotNet/IQuantity.cs | 17 +++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/QuantitiesDotNet.Generators/QuantityImplementBuilder.cs b/src/QuantitiesDotNet.Generators/QuantityImplementBuilder.cs index b6e715c..5aa7418 100644 --- a/src/QuantitiesDotNet.Generators/QuantityImplementBuilder.cs +++ b/src/QuantitiesDotNet.Generators/QuantityImplementBuilder.cs @@ -308,25 +308,27 @@ private void GenerateSelfOperatorsShape(SourceBuilderSlim sb) /// /// /// - public static bool Equals({{TypeName}} x, {{TypeName}} y) - => x._rawValue == y._rawValue; + public static bool Equals({{TypeName}} x, {{TypeName}} y) => + x._rawValue == y._rawValue; /// Determines which value is greater than another. /// /// /// - public static int Compare({{TypeName}} x, {{TypeName}} y) - { - if (x._rawValue == y._rawValue) { return 0; } - return x._rawValue < y._rawValue ? -1 : 1; - } - - /** */ public static bool operator ==({{TypeName}} x, {{TypeName}} y) => Equals(x, y); - /** */ public static bool operator !=({{TypeName}} x, {{TypeName}} y) => !Equals(x, y); - /** */ public static bool operator < ({{TypeName}} x, {{TypeName}} y) => Compare(x, y) < 0; - /** */ public static bool operator > ({{TypeName}} x, {{TypeName}} y) => Compare(x, y) > 0; - /** */ public static bool operator <=({{TypeName}} x, {{TypeName}} y) => Compare(x, y) <= 0; - /** */ public static bool operator >=({{TypeName}} x, {{TypeName}} y) => Compare(x, y) >= 0; + public static int Compare({{TypeName}} x, {{TypeName}} y) => + (x._rawValue < y._rawValue, x._rawValue > y._rawValue) switch + { + (true, false) => -1, + (false, true) => +1, + _ => 0, + }; + + /** */ public static bool operator ==({{TypeName}} x, {{TypeName}} y) => x._rawValue == y._rawValue; + /** */ public static bool operator !=({{TypeName}} x, {{TypeName}} y) => x._rawValue != y._rawValue; + /** */ public static bool operator < ({{TypeName}} x, {{TypeName}} y) => x._rawValue < y._rawValue; + /** */ public static bool operator > ({{TypeName}} x, {{TypeName}} y) => x._rawValue > y._rawValue; + /** */ public static bool operator <=({{TypeName}} x, {{TypeName}} y) => x._rawValue <= y._rawValue; + /** */ public static bool operator >=({{TypeName}} x, {{TypeName}} y) => x._rawValue >= y._rawValue; #endregion comparison operator implements diff --git a/src/QuantitiesDotNet/IQuantity.cs b/src/QuantitiesDotNet/IQuantity.cs index b8c2649..87ef13a 100644 --- a/src/QuantitiesDotNet/IQuantity.cs +++ b/src/QuantitiesDotNet/IQuantity.cs @@ -41,6 +41,7 @@ public interface IQuantity /// /// /// The implementation must be x.RawValue == y.RawValue. + /// The behaviour for edge cases conforms to the implementation of . /// public static abstract bool Equals(TSelf x, TSelf y); @@ -50,8 +51,24 @@ public interface IQuantity /// /// /// The implementation must be x.RawValue == y.RawValue ? 0 : (x.RawValue < y.RawValue ? -1 : 1). + /// The behaviour for edge cases conforms to the implementation of . /// public static abstract int Compare(TSelf x, TSelf y); + /// + /// + /// The implementation must be: + /// + /// (x.RawValue < y.RawValue, x.RawValue > y.RawValue) switch + /// { + /// (true, false) => -1, + /// (false, true) => +1, + /// _ => 0, + /// } + /// + /// The behaviour for edge cases conforms to the implementation of . + /// + public int GetHashCode(); + #endif } From f95c159861715d05ed0a4b40aeb94472c560a3e0 Mon Sep 17 00:00:00 2001 From: Daishi Nakase Date: Sat, 24 Jan 2026 16:54:04 +0900 Subject: [PATCH 4/4] adds test --- src/QuantitiesDotNet.Test/QuantityTest.cs | 206 ++++++++++++++++------ 1 file changed, 149 insertions(+), 57 deletions(-) diff --git a/src/QuantitiesDotNet.Test/QuantityTest.cs b/src/QuantitiesDotNet.Test/QuantityTest.cs index 1ade5b1..d5c5cc5 100644 --- a/src/QuantitiesDotNet.Test/QuantityTest.cs +++ b/src/QuantitiesDotNet.Test/QuantityTest.cs @@ -60,8 +60,12 @@ public void Compare_NonGeneric(T placeHolder) where T : struct, IQuantity { InternalHelpers.NoUse(placeHolder); - var _0 = Unsafe.BitCast(0.0); + var p0 = Unsafe.BitCast(+0.0); + var n0 = Unsafe.BitCast(-0.0); var _1 = Unsafe.BitCast(1.0); + var nan = Unsafe.BitCast(double.NaN); + var pinf = Unsafe.BitCast(double.PositiveInfinity); + var ninf = Unsafe.BitCast(double.NegativeInfinity); Assert.Equal(_1, _1); Assert.True (_1.Equals(_1)); @@ -75,34 +79,90 @@ public void Compare_NonGeneric(T placeHolder) Assert.False(_1 > _1); Assert.True (_1 <= _1); Assert.False(_1 < _1); + Assert.Equal(_1.GetHashCode(), _1.GetHashCode()); + + Assert.NotEqual(p0, _1); + Assert.False(p0.Equals(_1)); + Assert.False(p0.Equals((object)_1)); + Assert.False(T.Equals(p0, _1)); + Assert.True(0 > T.Compare(p0, _1)); + Assert.True(0 > p0.CompareTo(_1)); + Assert.False(p0 == _1); + Assert.True (p0 != _1); + Assert.False(p0 >= _1); + Assert.False(p0 > _1); + Assert.True (p0 <= _1); + Assert.True (p0 < _1); + + Assert.NotEqual(_1, p0); + Assert.False(_1.Equals(p0)); + Assert.False(_1.Equals((object)p0)); + Assert.False(T.Equals(_1, p0)); + Assert.True(0 < T.Compare(_1, p0)); + Assert.True(0 < _1.CompareTo(p0)); + Assert.False(_1 == p0); + Assert.True (_1 != p0); + Assert.True (_1 >= p0); + Assert.True (_1 > p0); + Assert.False(_1 <= p0); + Assert.False(_1 < p0); + + Assert.False(p0.Equals(new object())); + + Assert.Equal(+0.0.GetHashCode(), p0.GetHashCode()); + Assert.Equal(-0.0.GetHashCode(), n0.GetHashCode()); + Assert.Equal(1.0.GetHashCode(), _1.GetHashCode()); + Assert.Equal(double.NaN.GetHashCode(), nan.GetHashCode()); + Assert.Equal(double.PositiveInfinity.GetHashCode(), pinf.GetHashCode()); + Assert.Equal(double.NegativeInfinity.GetHashCode(), ninf.GetHashCode()); + +#pragma warning disable format +#pragma warning disable CA2242 + static int compare(double x, double y) => + (x < y, x > y) switch + { + (true, false) => -1, + (false, true) => +1, + _ => 0, + }; + + Assert.Equal(+0.0 == +0.0, p0 == p0); + Assert.Equal(+0.0 < +0.0, p0 < p0); + Assert.Equal(+0.0 > +0.0, p0 > p0); + Assert.Equal(+0.0 <= +0.0, p0 <= p0); + Assert.Equal(+0.0 >= +0.0, p0 >= p0); + Assert.Equal(compare(+0.0, +0.0), T.Compare(p0, p0)); + + Assert.Equal(+0.0 == -0.0, p0 == n0); + Assert.Equal(+0.0 < -0.0, p0 < n0); + Assert.Equal(+0.0 > -0.0, p0 > n0); + Assert.Equal(+0.0 <= -0.0, p0 <= n0); + Assert.Equal(+0.0 >= -0.0, p0 >= n0); + Assert.Equal(compare(+0.0, -0.0), T.Compare(p0, n0)); + + Assert.Equal(double.NaN == double.NaN, nan == nan); + Assert.Equal(double.NaN < double.NaN, nan < nan); + Assert.Equal(double.NaN > double.NaN, nan > nan); + Assert.Equal(double.NaN <= double.NaN, nan <= nan); + Assert.Equal(double.NaN >= double.NaN, nan >= nan); + Assert.Equal(compare(double.NaN, double.NaN), T.Compare(nan, nan)); + + Assert.Equal(double.PositiveInfinity == double.PositiveInfinity, pinf == pinf); + Assert.Equal(double.PositiveInfinity < double.PositiveInfinity, pinf < pinf); + Assert.Equal(double.PositiveInfinity > double.PositiveInfinity, pinf > pinf); + Assert.Equal(double.PositiveInfinity <= double.PositiveInfinity, pinf <= pinf); + Assert.Equal(double.PositiveInfinity >= double.PositiveInfinity, pinf >= pinf); + Assert.Equal(compare(double.PositiveInfinity, double.PositiveInfinity), T.Compare(pinf, pinf)); + + Assert.Equal(double.NegativeInfinity == double.NegativeInfinity, ninf == ninf); + Assert.Equal(double.NegativeInfinity < double.NegativeInfinity, ninf < ninf); + Assert.Equal(double.NegativeInfinity > double.NegativeInfinity, ninf > ninf); + Assert.Equal(double.NegativeInfinity <= double.NegativeInfinity, ninf <= ninf); + Assert.Equal(double.NegativeInfinity >= double.NegativeInfinity, ninf >= ninf); + Assert.Equal(compare(double.NegativeInfinity, double.NegativeInfinity), T.Compare(ninf, ninf)); +#pragma warning restore CA2242 +#pragma warning restore format - Assert.NotEqual(_0, _1); - Assert.False(_0.Equals(_1)); - Assert.False(_0.Equals((object)_1)); - Assert.False(T.Equals(_0, _1)); - Assert.True(0 > T.Compare(_0, _1)); - Assert.True(0 > _0.CompareTo(_1)); - Assert.False(_0 == _1); - Assert.True (_0 != _1); - Assert.False(_0 >= _1); - Assert.False(_0 > _1); - Assert.True (_0 <= _1); - Assert.True (_0 < _1); - - Assert.NotEqual(_1, _0); - Assert.False(_1.Equals(_0)); - Assert.False(_1.Equals((object)_0)); - Assert.False(T.Equals(_1, _0)); - Assert.True(0 < T.Compare(_1, _0)); - Assert.True(0 < _1.CompareTo(_0)); - Assert.False(_1 == _0); - Assert.True (_1 != _0); - Assert.True (_1 >= _0); - Assert.True (_1 > _0); - Assert.False(_1 <= _0); - Assert.False(_1 < _0); - - Assert.False(_0.Equals(new object())); } [Theory] @@ -111,7 +171,8 @@ public void Compare_Generic(T placeHolder) where T : struct, IQuantity { InternalHelpers.NoUse(placeHolder); - var _0 = Unsafe.BitCast(0.0m); + var p0 = Unsafe.BitCast(+0.0m); + var n0 = Unsafe.BitCast(-0.0m); var _1 = Unsafe.BitCast(1.0m); Assert.Equal(_1, _1); @@ -126,34 +187,65 @@ public void Compare_Generic(T placeHolder) Assert.False(_1 > _1); Assert.True (_1 <= _1); Assert.False(_1 < _1); - - Assert.NotEqual(_0, _1); - Assert.False(_0.Equals(_1)); - Assert.False(_0.Equals((object)_1)); - Assert.False(T.Equals(_0, _1)); - Assert.True(0 > T.Compare(_0, _1)); - Assert.True(0 > _0.CompareTo(_1)); - Assert.False(_0 == _1); - Assert.True (_0 != _1); - Assert.False(_0 >= _1); - Assert.False(_0 > _1); - Assert.True (_0 <= _1); - Assert.True (_0 < _1); - - Assert.NotEqual(_1, _0); - Assert.False(_1.Equals(_0)); - Assert.False(_1.Equals((object)_0)); - Assert.False(T.Equals(_1, _0)); - Assert.True(0 < T.Compare(_1, _0)); - Assert.True(0 < _1.CompareTo(_0)); - Assert.False(_1 == _0); - Assert.True (_1 != _0); - Assert.True (_1 >= _0); - Assert.True (_1 > _0); - Assert.False(_1 <= _0); - Assert.False(_1 < _0); - - Assert.False(_0.Equals(new object())); + Assert.Equal(_1.GetHashCode(), _1.GetHashCode()); + + Assert.NotEqual(p0, _1); + Assert.False(p0.Equals(_1)); + Assert.False(p0.Equals((object)_1)); + Assert.False(T.Equals(p0, _1)); + Assert.True(0 > T.Compare(p0, _1)); + Assert.True(0 > p0.CompareTo(_1)); + Assert.False(p0 == _1); + Assert.True (p0 != _1); + Assert.False(p0 >= _1); + Assert.False(p0 > _1); + Assert.True (p0 <= _1); + Assert.True (p0 < _1); + + Assert.NotEqual(_1, p0); + Assert.False(_1.Equals(p0)); + Assert.False(_1.Equals((object)p0)); + Assert.False(T.Equals(_1, p0)); + Assert.True(0 < T.Compare(_1, p0)); + Assert.True(0 < _1.CompareTo(p0)); + Assert.False(_1 == p0); + Assert.True (_1 != p0); + Assert.True (_1 >= p0); + Assert.True (_1 > p0); + Assert.False(_1 <= p0); + Assert.False(_1 < p0); + + Assert.False(p0.Equals(new object())); + + Assert.Equal(+0.0m.GetHashCode(), p0.GetHashCode()); + Assert.Equal(-0.0m.GetHashCode(), n0.GetHashCode()); + Assert.Equal(1.0m.GetHashCode(), _1.GetHashCode()); + +#pragma warning disable format +#pragma warning disable CA2242 + static int compare(double x, double y) => + (x < y, x > y) switch + { + (true, false) => -1, + (false, true) => +1, + _ => 0, + }; + + Assert.Equal(+0.0 == +0.0, p0 == p0); + Assert.Equal(+0.0 < +0.0, p0 < p0); + Assert.Equal(+0.0 > +0.0, p0 > p0); + Assert.Equal(+0.0 <= +0.0, p0 <= p0); + Assert.Equal(+0.0 >= +0.0, p0 >= p0); + Assert.Equal(compare(+0.0, +0.0), T.Compare(p0, p0)); + + Assert.Equal(+0.0 == -0.0, p0 == n0); + Assert.Equal(+0.0 < -0.0, p0 < n0); + Assert.Equal(+0.0 > -0.0, p0 > n0); + Assert.Equal(+0.0 <= -0.0, p0 <= n0); + Assert.Equal(+0.0 >= -0.0, p0 >= n0); + Assert.Equal(compare(+0.0, -0.0), T.Compare(p0, n0)); +#pragma warning restore CA2242 +#pragma warning restore format } #pragma warning restore CS1718