11module Float.Extra exposing
2- ( aboutEqual
2+ ( aboutEqual, equalWithin
33 , toFixedDecimalPlaces, toFixedSignificantDigits, boundaryValuesAsUnicode
44 , range
55 , modBy
6+ , interpolateFrom
67 )
78
89{- | Convenience functions for dealing with Floats.
910
1011
1112# Equality
1213
13- @docs aboutEqual
14+ @docs aboutEqual, equalWithin
1415
1516
1617# Formatting Floats
@@ -27,6 +28,11 @@ module Float.Extra exposing
2728
2829@docs modBy
2930
31+
32+ # Interpolation
33+
34+ @docs interpolateFrom
35+
3036-}
3137
3238-- toFixedDecimalDigits implementation
@@ -310,7 +316,7 @@ boundaryValuesAsUnicode formatter value =
310316
311317
312318
313- -- aboutEqual
319+ -- Equality
314320
315321
316322{- | Comparing Floats with `==` is usually wrong, unless you basically care for reference equality, since floating point
@@ -323,16 +329,16 @@ due to floating point drift rather than a result of meaningful difference in cal
323329
324330 (0.1 + 0.2) |> Float.Extra.aboutEqual 0.3 --> True
325331
326- Note: this is unlikely to be appropriate if you are performing computations much smaller than one.
327-
328- (0.00001 + 0.00002) |> Float.Extra.aboutEqual 0.00003 --> True
329-
330332This value handles Infinity and NaN like so:
331333
332334 (1 / 0) |> Float.Extra.aboutEqual (100 / 0) --> True
333335
334336 (0 / 0) |> Float.Extra.aboutEqual (0 / 0) --> False
335337
338+ **Warning:** This function is handy for casual usecases, where floats are giving you some modest trouble.
339+ But for serious usecases, you should transition to using `equalWithin` and specify a tolerance that is
340+ appropriate for your usecase.
341+
336342-}
337343aboutEqual : Float -> Float -> Bool
338344aboutEqual a b =
@@ -343,6 +349,32 @@ aboutEqual a b =
343349 abs ( a - b) <= 1.0e-5 + 1.0e-8 * abs a
344350
345351
352+ {- | Check if two values are equal within a given (absolute) tolerance.
353+
354+ Float.Extra.equalWithin 1.0e-6 1.9999 2.0001
355+ --> False
356+
357+ Float.Extra.equalWithin 1.0e-3 1.9999 2.0001
358+ --> True
359+
360+ **Picking a tolerance**
361+
362+ `equalWithin` uses an absolute tolerance, meaning that the absolute difference between the two values should not exceed the tolerance.
363+ As such, you should choose a number based on the overall magnitude of the domain you are computing in. For instance,
364+ in a geometrical context, you can pick a value based on the size of the overall bounding box.
365+ If measuring sizes of people, perhaps you can pick a value based on the tallest person alive, etc. In that context,
366+ you may consider two persons equally tall, if they have the same number of millimeters in height.
367+
368+ -}
369+ equalWithin : Float -> Float -> Float -> Bool
370+ equalWithin tolerance firstValue secondValue =
371+ if isInfinite firstValue || isInfinite secondValue then
372+ firstValue == secondValue
373+
374+ else
375+ abs ( secondValue - firstValue) <= tolerance
376+
377+
346378
347379-- Range
348380
@@ -408,3 +440,35 @@ in `Float.Extra.modBy modulus x`.
408440modBy : Float -> Float -> Float
409441modBy modulus x =
410442 x - modulus * toFloat ( floor ( x / modulus))
443+
444+
445+ {- | Interpolate from the first value to the second, based on a parameter that
446+ ranges from zero to one. Passing a parameter value of zero will return the start
447+ value and passing a parameter value of one will return the end value.
448+
449+ Float.Extra.interpolateFrom 5 10 0 == 5
450+
451+ Float.Extra.interpolateFrom 5 10 1 == 10
452+
453+ Float.Extra.interpolateFrom 5 10 0.6 == 8
454+
455+ The end value can be less than the start value:
456+
457+ Float.Extra.interpolateFrom 10 5 0.1 == 9.5
458+
459+ Parameter values less than zero or greater than one can be used to extrapolate:
460+
461+ Float.Extra.interpolateFrom 5 10 1.5 == 12.5
462+
463+ Float.Extra.interpolateFrom 5 10 -0.5 == 2.5
464+
465+ Float.Extra.interpolateFrom 10 5 -0.2 == 11
466+
467+ -}
468+ interpolateFrom : Float -> Float -> Float -> Float
469+ interpolateFrom start end parameter =
470+ if parameter <= 0.5 then
471+ start + parameter * ( end - start)
472+
473+ else
474+ end + ( 1 - parameter) * ( start - end)
0 commit comments