diff --git a/Semantics.Quantities/Vector0Guards.cs b/Semantics.Quantities/Vector0Guards.cs
index 548d64a6..c0fbc8ba 100644
--- a/Semantics.Quantities/Vector0Guards.cs
+++ b/Semantics.Quantities/Vector0Guards.cs
@@ -27,11 +27,13 @@ public static class Vector0Guards
/// The value (already converted to the SI base unit) to validate.
/// Name of the originating parameter, used for the exception message.
/// The validated, non-negative value.
- /// When is negative.
+ /// When is NaN or negative.
public static T EnsureNonNegative(T value, string paramName)
where T : struct, INumber
{
- if (T.Sign(value) < 0)
+ EnsureNumber(value, paramName);
+
+ if (value < T.Zero)
{
throw new ArgumentException(
$"Magnitude must be non-negative; received {value}.",
@@ -53,11 +55,13 @@ public static T EnsureNonNegative(T value, string paramName)
/// The value (already converted to the SI base unit) to validate.
/// Name of the originating parameter, used for the exception message.
/// The validated, strictly-positive value.
- /// When is zero or negative.
+ /// When is NaN, zero or negative.
public static T EnsurePositive(T value, string paramName)
where T : struct, INumber
{
- if (T.Sign(value) <= 0)
+ EnsureNumber(value, paramName);
+
+ if (value <= T.Zero)
{
throw new ArgumentException(
$"Value must be strictly positive; received {value}.",
@@ -66,4 +70,18 @@ public static T EnsurePositive(T value, string paramName)
return value;
}
+
+ ///
+ /// Throws when is NaN. Checked before the
+ /// sign test because T.Sign(NaN) throws for the IEEE types,
+ /// and a NaN compares false against zero, so neither test rejects it on its own.
+ ///
+ private static void EnsureNumber(T value, string paramName)
+ where T : struct, INumber
+ {
+ if (T.IsNaN(value))
+ {
+ throw new ArgumentException("Magnitude must be a number; received NaN.", paramName);
+ }
+ }
}
diff --git a/Semantics.Test/Quantities/Vector0InvariantTests.cs b/Semantics.Test/Quantities/Vector0InvariantTests.cs
index af58da18..6228feab 100644
--- a/Semantics.Test/Quantities/Vector0InvariantTests.cs
+++ b/Semantics.Test/Quantities/Vector0InvariantTests.cs
@@ -12,6 +12,7 @@ namespace ktsu.Semantics.Test.Quantities;
/// - Issue #50: factories reject negative inputs with .
/// - Issue #52: V0 - V0 returns the same V0 of T.Abs(a - b).
/// - Issue #285: a V0 has no unary negation, and scaling it by a negative number throws.
+/// - Issue #326: a NaN input is rejected with , not .
///
///
[TestClass]
@@ -332,4 +333,37 @@ public void Vector0_Scaled_By_NonNegative_Scalar_Is_Allowed()
[TestMethod]
public void Vector1_Scaled_By_Negative_Scalar_Is_Signed()
=> Assert.AreEqual(-6.0, (Velocity1D.FromMeterPerSecond(2.0) * -3.0).Value, Tolerance);
+
+ // =========================================================== #326: NaN is an ArgumentException
+
+ [TestMethod]
+ public void EnsureNonNegative_NaN_Throws_ArgumentException()
+ {
+ _ = Assert.ThrowsExactly(() => Vector0Guards.EnsureNonNegative(double.NaN, "value"));
+ _ = Assert.ThrowsExactly(() => Vector0Guards.EnsureNonNegative(float.NaN, "value"));
+ _ = Assert.ThrowsExactly(() => Vector0Guards.EnsureNonNegative(Half.NaN, "value"));
+ }
+
+ [TestMethod]
+ public void EnsurePositive_NaN_Throws_ArgumentException()
+ {
+ _ = Assert.ThrowsExactly(() => Vector0Guards.EnsurePositive(double.NaN, "value"));
+ _ = Assert.ThrowsExactly(() => Vector0Guards.EnsurePositive(float.NaN, "value"));
+ _ = Assert.ThrowsExactly(() => Vector0Guards.EnsurePositive(Half.NaN, "value"));
+ }
+
+ [TestMethod]
+ public void Generated_Factories_NaN_Throw_ArgumentException()
+ {
+ _ = Assert.ThrowsExactly(() => Speed.FromMeterPerSecond(double.NaN));
+ _ = Assert.ThrowsExactly(() => Mass.FromKilogram(double.NaN));
+ _ = Assert.ThrowsExactly(() => Wavelength.FromMeter(double.NaN));
+ }
+
+ [TestMethod]
+ public void Guards_Accept_Infinity_And_Reject_NegativeInfinity()
+ {
+ Assert.AreEqual(double.PositiveInfinity, Vector0Guards.EnsurePositive(double.PositiveInfinity, "value"));
+ _ = Assert.ThrowsExactly(() => Vector0Guards.EnsureNonNegative(double.NegativeInfinity, "value"));
+ }
}