Skip to content

Commit

Permalink
otp: Explain the term equivalence operators
Browse files Browse the repository at this point in the history
  • Loading branch information
jhogberg committed Aug 15, 2023
1 parent 4c05f38 commit ab3f59c
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 7 deletions.
31 changes: 31 additions & 0 deletions system/doc/reference_manual/data_types.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,37 @@
11> <input>1_234.333_333</input>
1234.333333
</pre>
<marker id="numeric_comparisons" />
<section>
<title>Comparisons</title>
<p>Both integers and floats share the same linear order. That is,
<c>1</c> compares less than <c>2.4</c>, <c>3</c> compares greater than
<c>2.99999</c>, and <c>5</c> is equal to <c>5.0</c>.</p>
<p>When wanting to compare an integer with another integer or a float
with another float, it may be tempting to use the term equivalence
operators (<c>=:=</c>, <c>=/=</c>) or pattern matching. This works for
integers which has a distinct representation for every number, but
there's a surprising edge case for floating-point as the latter has two
representations for zero which are considered different by the term
equivalence operator (<c>=:=</c>) and pattern matching.</p>
<p>If you wish to compare floating-point numbers <em>numerically</em>,
use the regular comparison operators (such as <c>==</c>) and add guards
that require both the arguments to be floating-point.</p>
<note>
<p>Prior to OTP 27, the term equivalence operators had a bug where they
considered <c>0.0</c> and <c>-0.0</c> to be the same term. Legacy
code that makes equality comparisons on floating-point zero should
migrate to using the equal-to (<c>==</c>) operator with
<c>is_float/1</c> guards, and compiler warnings have been added to
that effect. These can be silenced by writing <c>+0.0</c> instead,
which is the same as <c>0.0</c> but makes the compiler interpret the
comparison as being purposely made against <c>0.0</c>.</p>
<p>Note that this does <em>not</em> break compatibility with IEEE 754
which mandates that <c>0.0</c> and <c>-0.0</c> should compare
equal: they are equal when interpreted as numbers (<c>==</c>), and
unequal when interpreted as opaque terms (<c>=:=</c>).</p>
</note>
</section>
<marker id="float_representation_problem" />
<section>
<title>Representation of Floating Point Numbers</title>
Expand Down
54 changes: 47 additions & 7 deletions system/doc/reference_manual/expressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -801,11 +801,11 @@ Expr1 <input>op</input> Expr2</pre>
</row>
<row>
<cell align="left" valign="middle">=:=</cell>
<cell align="left" valign="middle">Exactly equal to</cell>
<cell align="left" valign="middle">Term equivalence</cell>
</row>
<row>
<cell align="left" valign="middle">=/=</cell>
<cell align="left" valign="middle">Exactly not equal to</cell>
<cell align="left" valign="middle">Term non-equivalence</cell>
</row>
<tcaption>Term Comparison Operators.</tcaption>
</table>
Expand Down Expand Up @@ -836,6 +836,28 @@ number &lt; atom &lt; reference &lt; fun &lt; port &lt; pid &lt; tuple &lt; map
depending on the size of the float because otherwise comparison of large
floats and integers would lose their transitivity.</p>

<p>The term equivalence operators, <c>=:=</c> and <c>=/=</c>, return
whether two terms are indistinguishable. While the other operators
consider the same <em>numbers</em> equal even when their types differ
(<c>1 == 1.0</c> is true), the term equivalence operators return whether
there exists any function that can tell their arguments apart.</p>

<p>For example, while the terms <c>0</c> and <c>0.0</c> represent the same
<em>number</em>, we can tell them apart by using the <c>is_integer/1</c>
function. Hence, <c>=:=</c> and <c>=/=</c> consider them different.</p>

<p>Furthermore, the terms <c>0.0</c> and <c>-0.0</c> also represent the
same <em>number</em>, but they yield different results when converted to
string form through <c>float_to_list/1</c>: when given the former it
returns a string without a sign, and when given the latter it returns a
string with a sign. Therefore, <c>=:=</c> and <c>=/=</c> consider
them different.</p>

<p>The term equivalence operators are useful when reasoning about terms as
opaque values, for example in associative containers or memoized
functions where using the equal-to operator (<c>==</c>) can result in
subtly incorrect results.</p>

<p>Term comparison operators return the Boolean value of the
expression, <c>true</c> or <c>false</c>.</p>

Expand All @@ -845,17 +867,35 @@ number &lt; atom &lt; reference &lt; fun &lt; port &lt; pid &lt; tuple &lt; map
true
2> <input>1=:=1.0.</input>
false
3> <input>1 > a.</input>
3> <input>0=:=0.0.</input>
false
4> <input>#{c => 3} > #{a => 1, b => 2}.</input>
4> <input>0.0=:=-0.0.</input>
false
5> <input>#{a => 1, b => 2} == #{a => 1.0, b => 2.0}.</input>
5> <input>0.0=:=+0.0.</input>
true
6> <input>&lt;&lt;2:2>> &lt; &lt;&lt;128>>.</input>
6> <input>1 > a.</input>
false
7> <input>#{c => 3} > #{a => 1, b => 2}.</input>
false
8> <input>#{a => 1, b => 2} == #{a => 1.0, b => 2.0}.</input>
true
7> <input>&lt;&lt;3:2>> &lt; &lt;&lt;128>>.</input>
9> <input>&lt;&lt;2:2>> &lt; &lt;&lt;128>>.</input>
true
10> <input>&lt;&lt;3:2>> &lt; &lt;&lt;128>>.</input>
false
</pre>
<note>
<p>Prior to OTP 27, the term equivalence operators had a bug where they
considered <c>0.0</c> and <c>-0.0</c> to be the same term.</p>
<p>This was fixed in OTP 27 but legacy code may have expected them to
be considered the same. To help users catch errors that may arise from
an upgrade, the compiler raises a warning when <c>0.0</c> is
pattern-matched or used in a term equivalence test.</p>
<p>If you need to match <c>0.0</c> specifically, then the warning can be
silenced by writing <c>+0.0</c> instead, which produces the same term
but makes the compiler interpret the match as being done on
purpose.</p>
</note>
</section>

<section>
Expand Down

0 comments on commit ab3f59c

Please sign in to comment.