1 | Approximately Equals Operator
|
---|
2 | =============================
|
---|
3 |
|
---|
4 | This is a proposal for the inclusion of two new operators into Cforall.
|
---|
5 |
|
---|
6 | Approximate equality is a very useful concept for floating point arithmetic.
|
---|
7 | Due to floating point error values are very rarely the same when they are
|
---|
8 | supposed to be. Because of this it is standard practice to provide some
|
---|
9 | wrapper function (or in C++ an entire class) when doing these operations.
|
---|
10 |
|
---|
11 | This proposal offers to do the same thing, but introduces a new trinary
|
---|
12 | operator to provide an easy to read interface for this operation.
|
---|
13 |
|
---|
14 | For example if you have two floats `a` and `b` to see if they are equal with
|
---|
15 | error `e` you would write `a ~== b : e`. The underlying function might look
|
---|
16 | like the following:
|
---|
17 |
|
---|
18 | ```
|
---|
19 | bool ?~==?:?(float lhs, float rhs, float epsilon) {
|
---|
20 | return lhs <= rhs + epsilon && rhs <= lhs + epsilon
|
---|
21 | }
|
---|
22 | ```
|
---|
23 |
|
---|
24 | Approximately Inequals Operator
|
---|
25 | ------------------------------
|
---|
26 |
|
---|
27 | Called `?~!=?:?` and usually written as `a ~!= b : e`, this is the negated
|
---|
28 | variant of approximately equals. That's it one should always be equal to the
|
---|
29 | negation of the other.
|
---|
30 |
|
---|
31 | Although this is not commonly considered a basic operation it is included to
|
---|
32 | ease negating a condition.
|
---|
33 |
|
---|
34 | Default Implementation
|
---|
35 | ----------------------
|
---|
36 |
|
---|
37 | The provided operations do not have to example provided above. The behaviour
|
---|
38 | should be to return true if the absolute value of the difference between the
|
---|
39 | compared values is less than or equal to the error value. So approximate
|
---|
40 | equality with error of zero should be the same as equality.
|
---|
41 |
|
---|
42 | Each implementation could be provided individually or it could be provided
|
---|
43 | through a generic function, as in the following:
|
---|
44 |
|
---|
45 | ```
|
---|
46 | forall(otype T | { T ?+?(T, T); bool ?<=?(T, T); })
|
---|
47 | bool ?~==?:?(float lhs, float rhs, float epsilon) {
|
---|
48 | return lhs <= rhs + epsilon && rhs <= lhs + epsilon
|
---|
49 | }
|
---|
50 | ```
|
---|
51 |
|
---|
52 | This could be organized like the concurrency extensions. That is the minimal
|
---|
53 | syntax is provided but to get full use a library include is required. If so
|
---|
54 | the library might be called `approx.hfa`.
|
---|
55 |
|
---|
56 | Required Syntax
|
---|
57 | ---------------
|
---|
58 |
|
---|
59 | We will need `~==` and `~!=` as new operator tokens. `?~==?:?` and `?~!=?:?`
|
---|
60 | must be recognized as two new special operator names. Then the operators
|
---|
61 | have to be included in the grammar as a new type of expression.
|
---|
62 |
|
---|
63 | Because of the natural use it should bind more tightly than logical operations
|
---|
64 | so that `b && x ~== y : e` is the same as `b && (x ~== y : e)` but not as much
|
---|
65 | arithmetic operations so `n + x ~== y : e` is the same as `(n + x) ~== y : e`.
|
---|
66 | Either at the same precedence as equality or in-between equality and
|
---|
67 | comparison.
|
---|
68 |
|
---|
69 | Choice of Symbols
|
---|
70 | -----------------
|
---|
71 |
|
---|
72 | The first operator symbols (`~==` and `~!=`) were chosen considering that the
|
---|
73 | only two operations we are adding approximate variants for. (See end of
|
---|
74 | section for details.) If only approximately equals was included then `~=`
|
---|
75 | might be the better choice for consistency with `!=`, `<=` and `>=`. However
|
---|
76 | that would make it less consistent with `~!=` and we can't use both `~` and
|
---|
77 | `!` to replace the first symbol in that case.
|
---|
78 |
|
---|
79 | The `:` for the second separator was used because of symmetry with the
|
---|
80 | conditional operator `?:`. The symmetry is not perfect, the colon on the
|
---|
81 | conditional is a divider that separates two equivalent options while here it
|
---|
82 | adds an extra detail to the main operation, but the additional separator for
|
---|
83 | a third argument remains the same. In addition `:` is already a token and it
|
---|
84 | never appears to begin something so it is unlikely to ever cause conflicts.
|
---|
85 |
|
---|
86 | ### Why Not Add More Approximate Operators?
|
---|
87 | To begin with very few operations have a meaningful error value in them.
|
---|
88 | Besides some exotic recursive calculation it is limited to comparisons.
|
---|
89 |
|
---|
90 | With equality and inequality covered that leaves use with the ordering
|
---|
91 | comparisons. In these cases because one side (above or below) is covered in
|
---|
92 | its entirety adding an error simply shifts (down or up) by that amount. This
|
---|
93 | is a less useful operation and easier to code in line. For example:
|
---|
94 |
|
---|
95 | `( a ~<= b : epsilon ) == ( a <= b + epsilon )`
|
---|
96 |
|
---|
97 | Default Epsilon
|
---|
98 | ---------------
|
---|
99 |
|
---|
100 | For primitive (or library) types that have approximate equality defined on
|
---|
101 | them it may also be useful to provide a general default for the error. Some
|
---|
102 | epsilon that is small, but large enough to catch the usual build up of error
|
---|
103 | when someone is doing just a bit of math with these types.
|
---|
104 |
|
---|
105 | However it should probably not be called epsilon to avoid any confusion with
|
---|
106 | machine epsilon, which in many cases would be much smaller than the default
|
---|
107 | error value.
|
---|
108 |
|
---|
109 | Even if the default implementations are in the prelude and not a library,
|
---|
110 | these might be useful contexts of `approx.hfa`.
|
---|