Skip to content

Commit 2018baa

Browse files
committed
Refactor comparison functions into own module, fixes #7
1 parent d5c21c1 commit 2018baa

File tree

3 files changed

+141
-132
lines changed

3 files changed

+141
-132
lines changed

src/Data/Number.purs

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
11
-- | Functions for working with PureScripts builtin `Number` type.
22
module Data.Number
33
( fromString
4-
, Fraction(..)
5-
, eqRelative
6-
, eqApproximate
7-
, (~=)
8-
, (≅)
9-
, neqApproximate
10-
, (≇)
11-
, Tolerance(..)
12-
, eqAbsolute
134
, nan
145
, isNaN
156
, infinity
@@ -19,7 +10,6 @@ module Data.Number
1910
import Prelude
2011

2112
import Data.Maybe (Maybe(..))
22-
import Math (abs)
2313
import Global as G
2414

2515
-- | Attempt to parse a `Number` using JavaScripts `parseFloat`. Returns
@@ -55,85 +45,6 @@ fromString = G.readFloat >>> check
5545
check num | isFinite num = Just num
5646
| otherwise = Nothing
5747

58-
-- | A newtype for (small) numbers, typically in the range *[0:1]*. It is used
59-
-- | as an argument for `eqRelative`.
60-
newtype Fraction = Fraction Number
61-
62-
-- | Compare two `Number`s and return `true` if they are equal up to the
63-
-- | given *relative* error (`Fraction` parameter).
64-
-- |
65-
-- | This comparison is scale-invariant, i.e. if `eqRelative frac x y`, then
66-
-- | `eqRelative frac (s * x) (s * y)` for a given scale factor `s > 0.0`
67-
-- | (unless one of x, y is exactly `0.0`).
68-
-- |
69-
-- | Note that the relation that `eqRelative frac` induces on `Number` is
70-
-- | not an equivalence relation. It is reflexive and symmetric, but not
71-
-- | transitive.
72-
-- |
73-
-- | Example:
74-
-- | ``` purs
75-
-- | > (eqRelative (Fraction 0.01)) 133.7 133.0
76-
-- | true
77-
-- |
78-
-- | > (eqRelative (Fraction 0.001)) 133.7 133.0
79-
-- | false
80-
-- |
81-
-- | > (eqRelative (Fraction 0.01)) (0.1 + 0.2) 0.3
82-
-- | true
83-
-- | ```
84-
eqRelative Fraction Number Number Boolean
85-
eqRelative (Fraction frac) 0.0 y = abs y <= frac
86-
eqRelative (Fraction frac) x 0.0 = abs x <= frac
87-
eqRelative (Fraction frac) x y = abs (x - y) <= frac * abs (x + y) / 2.0
88-
89-
-- | Test if two numbers are approximately equal, up to a relative difference
90-
-- | of one part in a million:
91-
-- | ``` purs
92-
-- | eqApproximate = eqRelative (Fraction 1.0e-6)
93-
-- | ```
94-
-- |
95-
-- | Example
96-
-- | ``` purs
97-
-- | > 0.1 + 0.2 == 0.3
98-
-- | false
99-
-- |
100-
-- | > 0.1 + 0.2 ≅ 0.3
101-
-- | true
102-
-- | ```
103-
eqApproximate Number Number Boolean
104-
eqApproximate = eqRelative onePPM
105-
where
106-
onePPM Fraction
107-
onePPM = Fraction 1.0e-6
108-
109-
infix 4 eqApproximate as ~=
110-
infix 4 eqApproximate as
111-
112-
-- | The complement of `eqApproximate`.
113-
neqApproximate Number Number Boolean
114-
neqApproximate x y = not (x ≅ y)
115-
116-
infix 4 neqApproximate as
117-
118-
-- | A newtype for (small) numbers. It is used as an argument for `eqAbsolute`.
119-
newtype Tolerance = Tolerance Number
120-
121-
-- | Compare two `Number`s and return `true` if they are equal up to the given
122-
-- | (absolute) tolerance value. Note that this type of comparison is *not*
123-
-- | scale-invariant. The relation induced by `(eqAbsolute (Tolerance eps))` is
124-
-- | symmetric and reflexive, but not transitive.
125-
-- |
126-
-- | Example:
127-
-- | ``` purs
128-
-- | > (eqAbsolute (Tolerance 1.0)) 133.7 133.0
129-
-- | true
130-
-- |
131-
-- | > (eqAbsolute (Tolerance 0.1)) 133.7 133.0
132-
-- | false
133-
-- | ```
134-
eqAbsolute Tolerance Number Number Boolean
135-
eqAbsolute (Tolerance tolerance) x y = abs (x - y) <= tolerance
136-
13748
-- | Not a number (NaN).
13849
nan Number
13950
nan = G.nan

src/Data/Number/Approximate.purs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
-- | This module defines functions for comparing numbers.
2+
module Data.Number.Approximate
3+
( Fraction(..)
4+
, eqRelative
5+
, eqApproximate
6+
, (~=)
7+
, (≅)
8+
, neqApproximate
9+
, (≇)
10+
, Tolerance(..)
11+
, eqAbsolute
12+
) where
13+
14+
import Prelude
15+
16+
import Math (abs)
17+
18+
-- | A newtype for (small) numbers, typically in the range *[0:1]*. It is used
19+
-- | as an argument for `eqRelative`.
20+
newtype Fraction = Fraction Number
21+
22+
-- | Compare two `Number`s and return `true` if they are equal up to the
23+
-- | given *relative* error (`Fraction` parameter).
24+
-- |
25+
-- | This comparison is scale-invariant, i.e. if `eqRelative frac x y`, then
26+
-- | `eqRelative frac (s * x) (s * y)` for a given scale factor `s > 0.0`
27+
-- | (unless one of x, y is exactly `0.0`).
28+
-- |
29+
-- | Note that the relation that `eqRelative frac` induces on `Number` is
30+
-- | not an equivalence relation. It is reflexive and symmetric, but not
31+
-- | transitive.
32+
-- |
33+
-- | Example:
34+
-- | ``` purs
35+
-- | > (eqRelative (Fraction 0.01)) 133.7 133.0
36+
-- | true
37+
-- |
38+
-- | > (eqRelative (Fraction 0.001)) 133.7 133.0
39+
-- | false
40+
-- |
41+
-- | > (eqRelative (Fraction 0.01)) (0.1 + 0.2) 0.3
42+
-- | true
43+
-- | ```
44+
eqRelative Fraction Number Number Boolean
45+
eqRelative (Fraction frac) 0.0 y = abs y <= frac
46+
eqRelative (Fraction frac) x 0.0 = abs x <= frac
47+
eqRelative (Fraction frac) x y = abs (x - y) <= frac * abs (x + y) / 2.0
48+
49+
-- | Test if two numbers are approximately equal, up to a relative difference
50+
-- | of one part in a million:
51+
-- | ``` purs
52+
-- | eqApproximate = eqRelative (Fraction 1.0e-6)
53+
-- | ```
54+
-- |
55+
-- | Example
56+
-- | ``` purs
57+
-- | > 0.1 + 0.2 == 0.3
58+
-- | false
59+
-- |
60+
-- | > 0.1 + 0.2 ≅ 0.3
61+
-- | true
62+
-- | ```
63+
eqApproximate Number Number Boolean
64+
eqApproximate = eqRelative onePPM
65+
where
66+
onePPM Fraction
67+
onePPM = Fraction 1.0e-6
68+
69+
infix 4 eqApproximate as ~=
70+
infix 4 eqApproximate as
71+
72+
-- | The complement of `eqApproximate`.
73+
neqApproximate Number Number Boolean
74+
neqApproximate x y = not (x ≅ y)
75+
76+
infix 4 neqApproximate as
77+
78+
-- | A newtype for (small) numbers. It is used as an argument for `eqAbsolute`.
79+
newtype Tolerance = Tolerance Number
80+
81+
-- | Compare two `Number`s and return `true` if they are equal up to the given
82+
-- | (absolute) tolerance value. Note that this type of comparison is *not*
83+
-- | scale-invariant. The relation induced by `(eqAbsolute (Tolerance eps))` is
84+
-- | symmetric and reflexive, but not transitive.
85+
-- |
86+
-- | Example:
87+
-- | ``` purs
88+
-- | > (eqAbsolute (Tolerance 1.0)) 133.7 133.0
89+
-- | true
90+
-- |
91+
-- | > (eqAbsolute (Tolerance 0.1)) 133.7 133.0
92+
-- | false
93+
-- | ```
94+
eqAbsolute Tolerance Number Number Boolean
95+
eqAbsolute (Tolerance tolerance) x y = abs (x - y) <= tolerance
96+

test/Main.purs

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ module Test.Main where
33
import Prelude
44

55
import Data.Maybe (Maybe(..), fromMaybe)
6-
import Data.Number (Fraction(..), Tolerance(..), nan, isNaN, infinity,
7-
isFinite, eqRelative, eqAbsolute, fromString, (≅), (≇))
6+
import Data.Number (nan, isNaN, infinity, isFinite, fromString)
7+
import Data.Number.Approximate (Fraction(..), Tolerance(..), eqRelative,
8+
eqAbsolute, (≅), (≇))
89

910
import Control.Monad.Aff.AVar (AVAR)
1011
import Control.Monad.Eff (Eff)
@@ -31,7 +32,46 @@ infix 1 eqAbsolute' as =~=
3132
main Eff (console CONSOLE, testOutput TESTOUTPUT, avar AVAR) Unit
3233
main = runTest do
3334

34-
suite "eqRelative" do
35+
36+
suite "Data.Number.fromString" do
37+
test "valid number string" do
38+
assert "integer strings are coerced" $
39+
fromMaybe false $ map (_ == 123.0) $ fromString "123"
40+
41+
assert "decimals are coerced" $
42+
fromMaybe false $ map (_ == 12.34) $ fromString "12.34"
43+
44+
assert "exponents are coerced" $
45+
fromMaybe false $ map (_ == 1e4) $ fromString "1e4"
46+
47+
assert "decimals exponents are coerced" $
48+
fromMaybe false $ map (_ == 1.2e4) $ fromString "1.2e4"
49+
50+
test "invalid number string" do
51+
assert "invalid strings are not coerced" $
52+
Nothing == fromString "bad string"
53+
54+
test "too large numbers" do
55+
assert "too large numbers are not coerced" $
56+
Nothing == fromString "1e1000"
57+
58+
59+
suite "Data.Number.isNaN" do
60+
test "Check for NaN" do
61+
assert "NaN is not a number" $ isNaN nan
62+
assertFalse "infinity is a number" $ isNaN infinity
63+
assertFalse "1.0 is a number" $ isNaN 1.0
64+
65+
66+
suite "Data.Number.isFinite" do
67+
test "Check for infinity" do
68+
assert "1.0e100 is a finite number" $ isFinite 1.0e100
69+
assertFalse "detect positive infinity" $ isFinite infinity
70+
assertFalse "detect negative infinity" $ isFinite (-infinity)
71+
assertFalse "detect NaN" $ isFinite nan
72+
73+
74+
suite "Data.Number.Approximate.eqRelative" do
3575
test "eqRelative" do
3676
assert "should return true for differences smaller 10%" $
3777
10.0 ~= 10.9
@@ -112,7 +152,7 @@ main = runTest do
112152
eqRelative (Fraction 3.0) 10.0 29.5
113153

114154

115-
suite "eqApproximate" do
155+
suite "Data.Number.Approximate.eqApproximate" do
116156
test "0.1 + 0.2 ≅ 0.3" do
117157
assert "0.1 + 0.2 should be approximately equal to 0.3" $
118158
0.1 + 0.20.3
@@ -121,45 +161,7 @@ main = runTest do
121161
0.1 + 0.2000010.3
122162

123163

124-
suite "isNaN" do
125-
test "Check for NaN" do
126-
assert "NaN is not a number" $ isNaN nan
127-
assertFalse "infinity is a number" $ isNaN infinity
128-
assertFalse "1.0 is a number" $ isNaN 1.0
129-
130-
131-
suite "isFinite" do
132-
test "Check for infinity" do
133-
assert "1.0e100 is a finite number" $ isFinite 1.0e100
134-
assertFalse "detect positive infinity" $ isFinite infinity
135-
assertFalse "detect negative infinity" $ isFinite (-infinity)
136-
assertFalse "detect NaN" $ isFinite nan
137-
138-
139-
suite "fromString" do
140-
test "valid number string" do
141-
assert "integer strings are coerced" $
142-
fromMaybe false $ map (_ == 123.0) $ fromString "123"
143-
144-
assert "decimals are coerced" $
145-
fromMaybe false $ map (_ == 12.34) $ fromString "12.34"
146-
147-
assert "exponents are coerced" $
148-
fromMaybe false $ map (_ == 1e4) $ fromString "1e4"
149-
150-
assert "decimals exponents are coerced" $
151-
fromMaybe false $ map (_ == 1.2e4) $ fromString "1.2e4"
152-
153-
test "invalid number string" do
154-
assert "invalid strings are not coerced" $
155-
Nothing == fromString "bad string"
156-
157-
test "too large numbers" do
158-
assert "too large numbers are not coerced" $
159-
Nothing == fromString "1e1000"
160-
161-
162-
suite "eqAbsolute" do
164+
suite "Data.Number.Approximate.eqAbsolute" do
163165
test "eqAbsolute" do
164166
assert "should succeed for differences smaller than the tolerance" $
165167
10.0 =~= 10.09

0 commit comments

Comments
 (0)