About Epsilon lenient comparison logic
I am interested to learn about other's opinion and advise regarding approaches to the “lenient comparison of floating point numbers” problem.
With “lenient comparison” I mean the concept of considering two Double (or Float for that matter) as being essentialy the same, if they differ only by a (configurable) epsilon (see for an example: https://github.com/GPlates/GPlates).
We all know that asking whether two Double values are equal, does not make really sense. For example the following simple comparison yields FALSE as it probably should. This is obviously the consequence of to be expected inaccuracies of floating point number calculation (see What Every Computer Scientist Should Know About Floating Point Arithmetic).
val d1 = 0.1 + 0.2
val d2 = 0.3
print(d1 == d2) // yields falseIn many applications you want to identify values that are ‘nearly’ the same, ‘nearly’ being defined by the application context.
An overwriting of ‘equals’ and ‘compareTo’ for Double (it that was possible) is not the way. The envisaged comparison logic would not exhibit important integrity characteristics. For example, it is not transitive: if both (d1 isAboutEqual d2) and (d2 isAboutEqual d3) hold, it is not necessarily also (d1 isAboutEqual d3) ).
So, what would be a good practise or even idiomatic way to handle such a challenge in Kotlin?
My current approach is to specify my custom infix comparison functions and provide for custom versions of any double function I need to use and that needs to be aware of lenient comparison.
typealias MyReal = Double
infix fun MyReal.isEqual(other: Double) = abs(other - this) <= EPSILON
infix fun MyReal.isMoreThan(other: Double) = (this - other) >= EPSILON
infix fun MyReal.isLessThan(other: Double) = (other - this) >= EPSILON
val MyReal.asin
get() = if (this isLessThan -1.0 || this isMoreThan 1.0) throw Exception("asin: invalid argument")
else asin(coerceIn(-1.0..1.0))
Please sign in to leave a comment.
Interesting explanation about the epsilon/lenient comparison logic thanks for breaking it down. It’s helpful to understand how and when this comparison is used!
Well, at this moment my prominent example is certainly the project mentioned before: https://github.com/GPlates/GPlates (it's a tectonic plate simulation). They provide a wrapper for a Double value (called “Real”) (see https://github.com/GPlates/GPlates/blob/gplates/src/maths/Real.cc ).
They claim that there approach helps to provide for “more robust and correct code”.
For example, say some trigonometic calculation cannot take a value greater than 1.0 and might crash otherwise. Your precursing own calculations might not plan to produce such a value (like 1.00000000000001 or such like). but it might happen. Inaccuracies in floating point calculations could bring you over that brink when you are operating NEAR to that limit.
There is also an argument that works on a somewhat higher logical level. You might want to identify vectors that represent geometrical points as identical, when they are close enough together.
I have been thinking also, that you might avoid a formalization of such an envisaged epsilon comparison logic (with all the logical pitfalls it necessarily must come with). You would use instead Long Values of sufficient size instead. This has drawbacks too I believe. You have to do transit between Long and Double values each time when you need a trigonometric calculation. Also most of your proints are not EXACTLY sphere points any more, but Integer approximations on a large Sphere. That itself leads to other nasty issues like rasterization challenges.
So, I guess you have to choose your posion. And may the best answer really depends on the specifics of you application. But as a mathematician I cannot help and wonder about the generic aspects of this challenge.
So, any thoughs on that are wellcome. Or maybe a reference to asolution that others have put together and worked with successfullly,
Hi! Please feel free to ask this questions in our Slack community: https://slack-chats.kotlinlang.org/.
OK. will do so. This slack thing is new to me. Working on it.
Anyway, I think we can close this thread here in view of me moving to slack with this.