-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add small-domain floating-point type MiniFloat to discipline package #4033
Conversation
…he methods on java.lang.Math since these are not available in ScalaJS
@@ -24,7 +24,7 @@ class MiniIntSuite extends CatsSuite { | |||
|
|||
{ | |||
implicit val m: CommutativeMonoid[MiniInt] = miniIntMultiplication | |||
checkAll("MiniInt addition", CommutativeMonoidTests[MiniInt].commutativeMonoid) | |||
checkAll("MiniInt multiplication", CommutativeMonoidTests[MiniInt].commutativeMonoid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated change that fixes a typo in the tests for MiniInt
.
/** | ||
* Note that since `MiniFloat` is used primarily for tests, this `Eq` instance defines `NaN` as equal to itself. This | ||
* differs from the `Order` defined for `Float`. | ||
*/ | ||
implicit val catsLawsEqInstancesForMiniFloat: Order[MiniFloat] with Hash[MiniFloat] = | ||
new Order[MiniFloat] with Hash[MiniFloat] { | ||
override def compare(x: MiniFloat, y: MiniFloat): Int = Order[Float].compare(x.toFloat, y.toFloat) | ||
override def hash(x: MiniFloat): Int = Hash[Float].hash(x.toFloat) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we're using this class in tests it's really only useful if we define Eq
such that NaN == NaN
. I'm aware that this is slightly unintuitive but otherwise the Eq
instance becomes useless.
An alternative approach would be to provide this Eq
in such a way that it needs to be manually imported when used. I'd be happy to make that change as needed.
val miniFloatMax: CommutativeMonoid[MiniFloat] with BoundedSemilattice[MiniFloat] = | ||
new CommutativeMonoid[MiniFloat] with BoundedSemilattice[MiniFloat] { | ||
override def empty: MiniFloat = MiniFloat.NegativeInfinity | ||
override def combine(x: MiniFloat, y: MiniFloat): MiniFloat = if (x > y) x else y | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming we define Eq[MiniFloat]
as above, I believe this is the main lawful Monoid you can define for MiniFloat
. I'd be happy to provide instances for addition and multiplication (with appropriate tests and disclaimers) if requested, but I'm not sure they'd be super useful.
Hi, thanks for the PR! Apologies, just working our way through some of the backlog. Sorry if I missed it, but I'm confused about one point: do we actually need/use |
# Conflicts: # tests/shared/src/test/scala/cats/tests/MiniFloatSuite.scala
In order to test Invariant[Fractional], we need some way to define an Eq[Invariant[Fractional]] for some fractional type. We do this using Float as the fractional type, but this requires the use of the deprecated cats.laws.discipline.DeprecatedEqInstances#catsLawsEqForFn1. If we instead use MiniFloat, we can take advantage of ExhaustiveCheck[MiniFloat] to use cats.laws.discipline.eq#catsLawsEqForFn1Exhaustive. See typelevel#2577 for a broader explanation of this.
Thanks @armanbilge. This PR is obviously a bit old but I think it's in a good place and can still add value.
I've added changes to this PR since you had a look that use In order to test whether our Ideally we would instead use It's possible there are other ways to an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! 👍 on avoidng DeprecatedEqInstances
, and MiniInt
certainly sets precedent.
It is unfortunate we'd have to take on so much non-trivial code essentially to test this one thing (unless there are some other possible uses outside of Cats?).
Out of curiosity could we use some sort of rational extended with infinities/NaN here? At least personally I could reason about that more easily than about floating point arithmetic :)
Currently we use Float when doing laws testing for Invariant[Fractional]. This in turn requires us to generate an Eq[Fractional[Float]], which can only be done using DeprecatedEqInstances.catsLawsEqForFn1. In order to stop using this deprecated instances, in this change we now use MiniInt to do our laws testing for Invariant[Fractional]. Note that this approach is spurious since MiniInt is not a fractional number type. But we can use it to test the lawfulness of the Invariant[Fractional] instance, and there is no "MiniFloat" type that is both small enough to have an ExhaustiveCheck instance and fractional. See typelevel#4033 for a broader discussion of this.
Personally I think this is a fascinating data type that makes some of the interesting corner-cases of floating-point numbers a lot more obvious. But I'm not sure that's a great reason to put it in the core discipline package, particularly given I've had to trade away some of that behaviour in order to make it useful for testing (I've made
This might be doable but I think it might just be easier to cheat a little bit and use I have raised #4216 taking this approach which may be a more fruitful way of removing the |
For posterity's sake, I should note that the Hash consistency test is flakey in Scala native. It seems to work sometimes and not other times, even on the CI? If we ever return to this code in the future that'll need to be fixed before this is merged. |
This PR adds a new data class to the
cats.laws.discipline
package:MiniFloat
. Similar toMiniInt
,MiniFloat
is intended to provide a floating-point type whose domain is small enough to have anExhaustiveCheck
instance.This PR is a re-attempt at #3813, but I have broken out the introduction of
MiniFloat
into a separate PR.Note that there is a failing test in the scala-native build. This is due to a bug in scala-native that was fixed in
0.4.1
. Accordingly, this PR depends on #4022.FAQ
How big is the domain of
MiniFloat?
As written,
MiniFloat
has 14 values:NaN
,PositiveInfinity
,NegativeInfinity
,-8.0
,-4.0
,-2.0
,-1.0
,-0.5
,0.0
,0.5
,1.0
,2.0
,4.0
, and8.0
. This compares to16
forMiniInt
, and2
forBoolean
, which are the other types for whichExhaustiveCheck
is defined.Why not just use a 8- or 16-bit float?
These types have much larger cardinalities (roughly 2⁸ and 2¹⁶ respectively) than
MiniFloat
that make them too big to be used for exhaustive laws checks.Why would
MiniFloat
be useful in the discipline package?Since #2577, testing function equivalence using
Arbitrary
has been deprecated withExhaustiveCheck
now the preferred way to test.MiniInt
serves this purpose, but I found a use for something likeMiniFloat
when defining anEq
instance forFractional
. Without something likeMiniFloat
, we have to fall back on usingArbitrary
to defineEq[Fractional[A]]
(see here). More broadly, It seems like it would be nice to have another option for tests requiring anExhaustiveCheck
instance. It's also a nice small type that can be used to test the quirky behaviours of very large, very small, and infinite floating-point values.