Skip to content

Commit

Permalink
Add Float.Extra.[equalWithin,interpolateFrom]
Browse files Browse the repository at this point in the history
  • Loading branch information
ianmackenzie committed Mar 26, 2024
1 parent 2ab7ca0 commit 4c2e853
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 5 deletions.
65 changes: 62 additions & 3 deletions src/Float/Extra.elm
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
module Float.Extra exposing
( aboutEqual
( aboutEqual, equalWithin
, toFixedDecimalPlaces, toFixedSignificantDigits, boundaryValuesAsUnicode
, range
, modBy
, interpolateFrom
)

{-| Convenience functions for dealing with Floats.
# Equality
@docs aboutEqual
@docs aboutEqual, equalWithin
# Formatting Floats
Expand All @@ -27,6 +28,11 @@ module Float.Extra exposing
@docs modBy
# Interpolation
@docs interpolateFrom
-}

-- toFixedDecimalDigits implementation
Expand Down Expand Up @@ -310,7 +316,7 @@ boundaryValuesAsUnicode formatter value =



-- aboutEqual
-- Equality


{-| Comparing Floats with `==` is usually wrong, unless you basically care for reference equality, since floating point
Expand Down Expand Up @@ -343,6 +349,20 @@ aboutEqual a b =
abs (a - b) <= 1.0e-5 + 1.0e-8 * abs a


{-| Check if two values are equal within a given tolerance.
Float.Extra.equalWithin 1e-6 1.9999 2.0001
--> False
Float.Extra.equalWithin 1e-3 1.9999 2.0001
--> True
-}
equalWithin : Float -> Float -> Float -> Bool
equalWithin tolerance firstValue secondValue =
abs (secondValue - firstValue) <= tolerance



-- Range

Expand Down Expand Up @@ -408,3 +428,42 @@ in `Float.Extra.modBy modulus x`.
modBy : Float -> Float -> Float
modBy modulus x =
x - modulus * toFloat (floor (x / modulus))


{-| Interpolate from the first value to the second, based on a parameter that
ranges from zero to one. Passing a parameter value of zero will return the start
value and passing a parameter value of one will return the end value.
Float.Extra.interpolateFrom 5 10 0
--> 5
Float.Extra.interpolateFrom 5 10 1
--> 10
Float.Extra.interpolateFrom 5 10 0.6
--> 8
The end value can be less than the start value:
Float.Extra.interpolateFrom 10 5 0.1
--> 9.5
Parameter values less than zero or greater than one can be used to extrapolate:
Float.Extra.interpolateFrom 5 10 1.5
--> 12.5
Float.Extra.interpolateFrom 5 10 -0.5
--> 2.5
Float.Extra.interpolateFrom 10 5 -0.2
--> 11
-}
interpolateFrom : Float -> Float -> Float -> Float
interpolateFrom start end parameter =
if parameter <= 0.5 then
start + parameter * (end - start)

else
end + (1 - parameter) * (start - end)
62 changes: 60 additions & 2 deletions tests/FloatTests.elm
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
module FloatTests exposing (modByTests, testAboutEqual, testBoundaryValuesAsUnicode, testRange, testToFixedDecimalPlaces, testToFixedSignificantDigits)
module FloatTests exposing
( modByTests
, testAboutEqual
, testBoundaryValuesAsUnicode
, testEqualWithin
, testInterpolateFrom
, testRange
, testToFixedDecimalPlaces
, testToFixedSignificantDigits
)

import Expect exposing (FloatingPointTolerance(..))
import Expect exposing (Expectation, FloatingPointTolerance(..))
import Float.Extra
import Fuzz exposing (Fuzzer)
import List.Extra exposing (Step(..))
Expand Down Expand Up @@ -214,6 +223,32 @@ testAboutEqual =
]


testEqualWithin : Test
testEqualWithin =
describe "equalWithin should compare numbers as equal within a given tolerance"
[ test "small positive error" <|
\() ->
Float.Extra.equalWithin 0.01 1 1.001
|> Expect.equal True
, test "small negative error" <|
\() ->
Float.Extra.equalWithin 0.01 1 0.999
|> Expect.equal True
, test "large positive error" <|
\() ->
Float.Extra.equalWithin 0.01 1 1.1
|> Expect.equal False
, test "large negative error" <|
\() ->
Float.Extra.equalWithin 0.01 1 0.9
|> Expect.equal False
, test "infinity not equal to itself within any finite tolerance" <|
\() ->
Float.Extra.equalWithin 0.01 (1 / 0) (1 / 0)
|> Expect.equal False
]


testRange : Test
testRange =
describe "range start stop step"
Expand Down Expand Up @@ -364,3 +399,26 @@ modByTests =
Float.Extra.modBy (toFloat a) (toFloat b)
|> Expect.within (Absolute 1.0e-20) (toFloat (modBy a b))
]


testInterpolateFrom : Test
testInterpolateFrom =
describe "interpolateFrom"
[ fuzz2 Fuzz.niceFloat Fuzz.niceFloat "should return the start value exactly if given 0" <|
\a b -> Float.Extra.interpolateFrom a b 0 |> expectExactly a
, fuzz2 Fuzz.niceFloat Fuzz.niceFloat "should return the end value exactly if given 1" <|
\a b -> Float.Extra.interpolateFrom a b 1 |> expectExactly b
, fuzz2 Fuzz.niceFloat Fuzz.niceFloat "should return the mean if given 0.5" <|
\a b ->
Float.Extra.interpolateFrom a b 0.5
|> Float.Extra.aboutEqual ((a + b) / 2)
|> Expect.equal True
]


{-| In some cases (like above in testInterpolateFrom)
you _do_ actually want to check two floating-point numbers for equality
-}
expectExactly : Float -> Float -> Expectation
expectExactly expected actual =
actual |> Expect.within (Expect.Absolute 0.0) expected

0 comments on commit 4c2e853

Please sign in to comment.