Skip to content

Commit d739f7b

Browse files
committed
Merge branch 'float-extra-additions'
2 parents ec5c3ce + cca9170 commit d739f7b

File tree

3 files changed

+132
-10
lines changed

3 files changed

+132
-10
lines changed

docs.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/Float/Extra.elm

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
module 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-
330332
This 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
-}
337343
aboutEqual : Float -> Float -> Bool
338344
aboutEqual 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`.
408440
modBy : Float -> Float -> Float
409441
modBy 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)

tests/FloatTests.elm

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
module FloatTests exposing (modByTests, testAboutEqual, testBoundaryValuesAsUnicode, testRange, testToFixedDecimalPlaces, testToFixedSignificantDigits)
1+
module FloatTests exposing
2+
( modByTests
3+
, testAboutEqual
4+
, testBoundaryValuesAsUnicode
5+
, testEqualWithin
6+
, testInterpolateFrom
7+
, testRange
8+
, testToFixedDecimalPlaces
9+
, testToFixedSignificantDigits
10+
)
211

3-
import Expect exposing (FloatingPointTolerance(..))
12+
import Expect exposing (Expectation, FloatingPointTolerance(..))
413
import Float.Extra
514
import Fuzz exposing (Fuzzer)
615
import List.Extra exposing (Step(..))
@@ -214,6 +223,32 @@ testAboutEqual =
214223
]
215224

216225

226+
testEqualWithin : Test
227+
testEqualWithin =
228+
describe "equalWithin should compare numbers as equal within a given tolerance"
229+
[ test "small positive error" <|
230+
\() ->
231+
Float.Extra.equalWithin 0.01 1 1.001
232+
|> Expect.equal True
233+
, test "small negative error" <|
234+
\() ->
235+
Float.Extra.equalWithin 0.01 1 0.999
236+
|> Expect.equal True
237+
, test "large positive error" <|
238+
\() ->
239+
Float.Extra.equalWithin 0.01 1 1.1
240+
|> Expect.equal False
241+
, test "large negative error" <|
242+
\() ->
243+
Float.Extra.equalWithin 0.01 1 0.9
244+
|> Expect.equal False
245+
, test "infinity not equal to itself within any finite tolerance" <|
246+
\() ->
247+
Float.Extra.equalWithin 0.01 (1 / 0) (1 / 0)
248+
|> Expect.equal True
249+
]
250+
251+
217252
testRange : Test
218253
testRange =
219254
describe "range start stop step"
@@ -364,3 +399,26 @@ modByTests =
364399
Float.Extra.modBy (toFloat a) (toFloat b)
365400
|> Expect.within (Absolute 1.0e-20) (toFloat (modBy a b))
366401
]
402+
403+
404+
testInterpolateFrom : Test
405+
testInterpolateFrom =
406+
describe "interpolateFrom"
407+
[ fuzz2 Fuzz.niceFloat Fuzz.niceFloat "should return the start value exactly if given 0" <|
408+
\a b -> Float.Extra.interpolateFrom a b 0 |> expectExactly a
409+
, fuzz2 Fuzz.niceFloat Fuzz.niceFloat "should return the end value exactly if given 1" <|
410+
\a b -> Float.Extra.interpolateFrom a b 1 |> expectExactly b
411+
, fuzz2 Fuzz.niceFloat Fuzz.niceFloat "should return the mean if given 0.5" <|
412+
\a b ->
413+
Float.Extra.interpolateFrom a b 0.5
414+
|> Float.Extra.aboutEqual ((a + b) / 2)
415+
|> Expect.equal True
416+
]
417+
418+
419+
{-| In some cases (like above in testInterpolateFrom)
420+
you _do_ actually want to check two floating-point numbers for equality
421+
-}
422+
expectExactly : Float -> Float -> Expectation
423+
expectExactly expected actual =
424+
actual |> Expect.within (Expect.Absolute 0.0) expected

0 commit comments

Comments
 (0)