diff --git a/README.md b/README.md index dfb5ce3e2..351fc3ee9 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,7 @@ The library comes with these predefined predicates: * `NonDivisible[N]`: checks if an integral value is not evenly divisible by `N` * `Even`: checks if an integral value is evenly divisible by 2 * `Odd`: checks if an integral value is not evenly divisible by 2 +* `NotNaN`: checks if a floating-point number is not NaN [`string`](https://github.com/fthomas/refined/blob/master/modules/core/shared/src/main/scala/eu/timepit/refined/string.scala) diff --git a/modules/core/shared/src/main/scala/eu/timepit/refined/numeric.scala b/modules/core/shared/src/main/scala/eu/timepit/refined/numeric.scala index bfc81226c..6e7fc5b88 100644 --- a/modules/core/shared/src/main/scala/eu/timepit/refined/numeric.scala +++ b/modules/core/shared/src/main/scala/eu/timepit/refined/numeric.scala @@ -41,6 +41,9 @@ object numeric extends NumericInference { /** Predicate that checks if an integral value modulo `N` is `O`. */ final case class Modulo[N, O](n: N, o: O) + /** Predicate that checks if a floating-point number value is not NaN. */ + case class NotNaN() + /** Predicate that checks if a numeric value is less than or equal to `N`. */ type LessEqual[N] = Not[Greater[N]] @@ -117,6 +120,14 @@ object numeric extends NumericInference { Modulo(wn.fst, wo.fst) ) } + + object NotNaN { + implicit def floatNotNaNValidate: Validate.Plain[Float, NotNaN] = fromIsNaN(_.isNaN) + implicit def doubleNotNaNValidate: Validate.Plain[Double, NotNaN] = fromIsNaN(_.isNaN) + + def fromIsNaN[A](isNaN: A => Boolean): Validate.Plain[A, NotNaN] = + Validate.fromPredicate(x => !isNaN(x), x => s"$x != NaN", NotNaN()) + } } private[refined] trait NumericInference { diff --git a/modules/core/shared/src/main/scala/eu/timepit/refined/predicates/numeric.scala b/modules/core/shared/src/main/scala/eu/timepit/refined/predicates/numeric.scala index 27ae37d63..7ba6c65a6 100644 --- a/modules/core/shared/src/main/scala/eu/timepit/refined/predicates/numeric.scala +++ b/modules/core/shared/src/main/scala/eu/timepit/refined/predicates/numeric.scala @@ -14,6 +14,9 @@ trait NumericPredicates { final type Modulo[N, O] = refined.numeric.Modulo[N, O] final val Modulo = refined.numeric.Modulo + final type NotNaN = refined.numeric.NotNaN + final val NotNaN = refined.numeric.NotNaN + final type LessEqual[N] = refined.numeric.LessEqual[N] final type GreaterEqual[N] = refined.numeric.GreaterEqual[N] diff --git a/modules/core/shared/src/main/scala/eu/timepit/refined/types/numeric.scala b/modules/core/shared/src/main/scala/eu/timepit/refined/types/numeric.scala index 0448af8f3..7386aad9b 100644 --- a/modules/core/shared/src/main/scala/eu/timepit/refined/types/numeric.scala +++ b/modules/core/shared/src/main/scala/eu/timepit/refined/types/numeric.scala @@ -1,7 +1,7 @@ package eu.timepit.refined.types import eu.timepit.refined.api.{Refined, RefinedTypeOps} -import eu.timepit.refined.numeric.{Negative, NonNegative, NonPositive, Positive} +import eu.timepit.refined.numeric.{Negative, NonNegative, NonPositive, NotNaN, Positive} /** Module for numeric refined types. */ object numeric { @@ -165,6 +165,16 @@ object numeric { type NonPosBigDecimal = BigDecimal Refined NonPositive object NonPosBigDecimal extends RefinedTypeOps[NonPosBigDecimal, BigDecimal] + + /** A `Float` that is not NaN. */ + type FloatNotNaN = Float Refined NotNaN + + object FloatNotNaN extends RefinedTypeOps[FloatNotNaN, Float] + + /** A `Double` that is not NaN. */ + type DoubleNotNaN = Double Refined NotNaN + + object DoubleNotNaN extends RefinedTypeOps[DoubleNotNaN, Double] } trait NumericTypes { @@ -215,6 +225,12 @@ trait NumericTypes { final type NonPosDouble = numeric.NonPosDouble final val NonPosDouble = numeric.NonPosDouble + + final type FloatNotNaN = numeric.FloatNotNaN + final val FloatNotNaN = numeric.FloatNotNaN + + final type DoubleNotNaN = numeric.DoubleNotNaN + final val DoubleNotNaN = numeric.DoubleNotNaN } trait NumericTypesBinCompat1 { diff --git a/modules/core/shared/src/test/scala/eu/timepit/refined/NumericValidateSpec.scala b/modules/core/shared/src/test/scala/eu/timepit/refined/NumericValidateSpec.scala index 1fd7cac94..516f3afb8 100644 --- a/modules/core/shared/src/test/scala/eu/timepit/refined/NumericValidateSpec.scala +++ b/modules/core/shared/src/test/scala/eu/timepit/refined/NumericValidateSpec.scala @@ -2,8 +2,8 @@ package eu.timepit.refined import eu.timepit.refined.TestUtils._ import eu.timepit.refined.numeric._ +import org.scalacheck.{Arbitrary, Gen, Properties} import org.scalacheck.Prop._ -import org.scalacheck.Properties import shapeless.nat._ class NumericValidateSpec extends Properties("NumericValidate") { @@ -179,4 +179,25 @@ class NumericValidateSpec extends Properties("NumericValidate") { val s = showExpr[Interval.Closed[_0, _1]](0.5) (s ?= "(!(0.5 < 0) && !(0.5 > 1))") || (s ?= "(!(0.5 < 0.0) && !(0.5 > 1.0))") } + + + val floatWithNaN: Gen[Float] = Gen.frequency(8 -> Arbitrary.arbitrary[Float], 2 -> Float.NaN) + val doubleWithNaN: Gen[Double] = Gen.frequency(8 -> Arbitrary.arbitrary[Double], 2 -> Double.NaN) + + property("NotNaN.Float.isValid") = forAll(floatWithNaN) { (d: Float) => + isValid[NotNaN](d) ?= !d.isNaN + } + + property("NotNaN.Float.showExpr") = secure { + showExpr[NotNaN](Float.NaN) ?= "NaN != NaN" + } + + property("NotNaN.Double.isValid") = forAll(doubleWithNaN) { (d: Double) => + isValid[NotNaN](d) ?= !d.isNaN + } + + property("NotNaN.Double.showExpr") = secure { + showExpr[NotNaN](Double.NaN) ?= "NaN != NaN" + } + } diff --git a/modules/scalacheck/shared/src/main/scala/eu/timepit/refined/scalacheck/numeric.scala b/modules/scalacheck/shared/src/main/scala/eu/timepit/refined/scalacheck/numeric.scala index c3202bab0..e0e64c8fc 100644 --- a/modules/scalacheck/shared/src/main/scala/eu/timepit/refined/scalacheck/numeric.scala +++ b/modules/scalacheck/shared/src/main/scala/eu/timepit/refined/scalacheck/numeric.scala @@ -57,6 +57,12 @@ trait NumericInstances { ): Arbitrary[F[T, GreaterEqual[N]]] = rangeClosedArbitrary(wn.snd, max.max) + implicit def floatNotNaNArbitrary[F[_, _]: RefType](implicit arb: Arbitrary[Float]): Arbitrary[F[Float, NotNaN]] = + arbitraryRefType(arb.arbitrary.suchThat(x => !x.isNaN)) + + implicit def doubleNotNaNArbitrary[F[_, _]: RefType](implicit arb: Arbitrary[Double]): Arbitrary[F[Double, NotNaN]] = + arbitraryRefType(arb.arbitrary.suchThat(x => !x.isNaN)) + /// implicit def intervalOpenArbitrary[F[_, _]: RefType, T: Numeric: Choose: Adjacent, L, H]( diff --git a/modules/scalacheck/shared/src/test/scala/eu/timepit/refined/scalacheck/NumericArbitrarySpec.scala b/modules/scalacheck/shared/src/test/scala/eu/timepit/refined/scalacheck/NumericArbitrarySpec.scala index b8c4f7ab5..1a2c8fbba 100644 --- a/modules/scalacheck/shared/src/test/scala/eu/timepit/refined/scalacheck/NumericArbitrarySpec.scala +++ b/modules/scalacheck/shared/src/test/scala/eu/timepit/refined/scalacheck/NumericArbitrarySpec.scala @@ -4,7 +4,7 @@ import eu.timepit.refined.W import eu.timepit.refined.api.Refined import eu.timepit.refined.numeric._ import eu.timepit.refined.scalacheck.numeric._ -import eu.timepit.refined.types.numeric.{NegDouble, NonNegInt, NonNegLong, PosFloat} +import eu.timepit.refined.types.numeric.{DoubleNotNaN, FloatNotNaN, NegDouble, NonNegInt, NonNegLong, PosFloat} import eu.timepit.refined.types.time.Minute import org.scalacheck.Prop._ import org.scalacheck.Properties @@ -36,6 +36,10 @@ class NumericArbitrarySpec extends Properties("NumericArbitrary") { property("GreaterEqual[_10]") = checkArbitraryRefinedType[Int Refined GreaterEqual[_10]] + property("FloatNotNaN") = checkArbitraryRefinedType[FloatNotNaN] + + property("DoubleNotNaN") = checkArbitraryRefinedType[DoubleNotNaN] + property("PosFloat") = checkArbitraryRefinedType[PosFloat] property("NonPositive") = checkArbitraryRefinedType[Short Refined NonPositive]