diff --git a/README.md b/README.md index dfb5ce3e2..e13b3fde5 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 +* `NonNaN`: 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..fe847e200 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. */ + final case class NonNaN() + /** 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 NonNaN { + implicit def floatNonNaNValidate: Validate.Plain[Float, NonNaN] = fromIsNaN(_.isNaN) + implicit def doubleNonNaNValidate: Validate.Plain[Double, NonNaN] = fromIsNaN(_.isNaN) + + def fromIsNaN[A](isNaN: A => Boolean): Validate.Plain[A, NonNaN] = + Validate.fromPredicate(x => !isNaN(x), x => s"($x != NaN)", NonNaN()) + } } private[refined] trait NumericInference { diff --git a/modules/core/shared/src/main/scala/eu/timepit/refined/predicates/all.scala b/modules/core/shared/src/main/scala/eu/timepit/refined/predicates/all.scala index a90d88cea..e6c8eacc9 100644 --- a/modules/core/shared/src/main/scala/eu/timepit/refined/predicates/all.scala +++ b/modules/core/shared/src/main/scala/eu/timepit/refined/predicates/all.scala @@ -1,6 +1,6 @@ package eu.timepit.refined.predicates -object all extends AllPredicates with AllPredicatesBinCompat1 +object all extends AllPredicates with AllPredicatesBinCompat1 with AllPredicatesBinCompat2 trait AllPredicates extends BooleanPredicates @@ -11,3 +11,5 @@ trait AllPredicates with StringPredicates trait AllPredicatesBinCompat1 extends StringPredicatesBinCompat1 + +trait AllPredicatesBinCompat2 extends NumericPredicatesBinCompat1 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..0e4619681 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 @@ -2,7 +2,7 @@ package eu.timepit.refined.predicates import eu.timepit.refined -object numeric extends NumericPredicates +object numeric extends NumericPredicates with NumericPredicatesBinCompat1 trait NumericPredicates { final type Less[N] = refined.numeric.Less[N] @@ -36,3 +36,8 @@ trait NumericPredicates { final val Interval = refined.numeric.Interval } + +trait NumericPredicatesBinCompat1 { + final type NonNaN = refined.numeric.NonNaN + final val NonNaN = refined.numeric.NonNaN +} diff --git a/modules/core/shared/src/main/scala/eu/timepit/refined/types/all.scala b/modules/core/shared/src/main/scala/eu/timepit/refined/types/all.scala index b0cbcaaed..f4cd8dd49 100644 --- a/modules/core/shared/src/main/scala/eu/timepit/refined/types/all.scala +++ b/modules/core/shared/src/main/scala/eu/timepit/refined/types/all.scala @@ -1,7 +1,7 @@ package eu.timepit.refined.types /** Module for all predefined refined types. */ -object all extends AllTypes with AllTypesBinCompat1 +object all extends AllTypes with AllTypesBinCompat1 with AllTypesBinCompat2 trait AllTypes extends CharTypes @@ -12,3 +12,5 @@ trait AllTypes with TimeTypes trait AllTypesBinCompat1 extends NumericTypesBinCompat1 + +trait AllTypesBinCompat2 extends NumericTypesBinCompat2 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..58355f1d2 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, NonNaN, NonNegative, NonPositive, 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 NonNaNFloat = Float Refined NonNaN + + object NonNaNFloat extends RefinedTypeOps[NonNaNFloat, Float] + + /** A `Double` that is not NaN. */ + type NonNaNDouble = Double Refined NonNaN + + object NonNaNDouble extends RefinedTypeOps[NonNaNDouble, Double] } trait NumericTypes { @@ -266,3 +276,11 @@ trait NumericTypesBinCompat1 { final type NonPosBigDecimal = numeric.NonPosBigDecimal final val NonPosBigDecimal = numeric.NonPosBigDecimal } + +trait NumericTypesBinCompat2 { + final type NonNaNFloat = numeric.NonNaNFloat + final val NonNaNFloat = numeric.NonNaNFloat + + final type NonNaNDouble = numeric.NonNaNDouble + final val NonNaNDouble = numeric.NonNaNDouble +} 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..364f1ba6c 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,24 @@ 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("NonNaN.Float.isValid") = forAll(floatWithNaN) { (d: Float) => + isValid[NonNaN](d) ?= !d.isNaN + } + + property("NonNaN.Float.showExpr") = secure { + showExpr[NonNaN](Float.NaN) ?= "(NaN != NaN)" + } + + property("NonNaN.Double.isValid") = forAll(doubleWithNaN) { (d: Double) => + isValid[NonNaN](d) ?= !d.isNaN + } + + property("NonNaN.Double.showExpr") = secure { + showExpr[NonNaN](Double.NaN) ?= "(NaN != NaN)" + } + } diff --git a/modules/scalacheck/shared/src/main/scala/eu/timepit/refined/scalacheck/all.scala b/modules/scalacheck/shared/src/main/scala/eu/timepit/refined/scalacheck/all.scala index 3228611fd..ff320c82b 100644 --- a/modules/scalacheck/shared/src/main/scala/eu/timepit/refined/scalacheck/all.scala +++ b/modules/scalacheck/shared/src/main/scala/eu/timepit/refined/scalacheck/all.scala @@ -5,6 +5,7 @@ object all with CharInstances with GenericInstances with NumericInstances + with NumericInstancesBinCompat1 with RefTypeInstances with StringInstances with StringInstancesBinCompat1 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..9af24c093 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 @@ -10,7 +10,7 @@ import org.scalacheck.Gen.Choose * Module that provides `Arbitrary` instances and generators for * numeric predicates. */ -object numeric extends NumericInstances +object numeric extends NumericInstances with NumericInstancesBinCompat1 trait NumericInstances { @@ -111,3 +111,15 @@ trait NumericInstances { ): Arbitrary[F[T, P]] = arbitraryRefType(Gen.chooseNum(min, max)) } + +trait NumericInstancesBinCompat1 { + implicit def floatNonNaNArbitrary[F[_, _]: RefType]( + implicit arb: Arbitrary[Float] + ): Arbitrary[F[Float, NonNaN]] = + arbitraryRefType(arb.arbitrary.map(x => if (x.isNaN) 0.0f else x)) + + implicit def doubleNonNaNArbitrary[F[_, _]: RefType]( + implicit arb: Arbitrary[Double] + ): Arbitrary[F[Double, NonNaN]] = + arbitraryRefType(arb.arbitrary.map(x => if (x.isNaN) 0.0d else x)) +} 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..7303d2558 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._ 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("NonNaNFloat") = checkArbitraryRefinedType[NonNaNFloat] + + property("NonNaNDouble") = checkArbitraryRefinedType[NonNaNDouble] + property("PosFloat") = checkArbitraryRefinedType[PosFloat] property("NonPositive") = checkArbitraryRefinedType[Short Refined NonPositive]