File 3231-otp-Explain-the-term-equivalence-operators.patch of Package erlang
From 6498901fe251507bc6576d673071b38e1b4e9168 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?John=20H=C3=B6gberg?= <john@erlang.org>
Date: Fri, 11 Aug 2023 10:36:46 +0200
Subject: [PATCH 1/2] otp: Explain the term equivalence operators
---
system/doc/reference_manual/data_types.xml | 31 ++++++++++++
system/doc/reference_manual/expressions.xml | 54 ++++++++++++++++++---
2 files changed, 78 insertions(+), 7 deletions(-)
diff --git a/system/doc/reference_manual/data_types.xml b/system/doc/reference_manual/data_types.xml
index 4b58c6a5ca..0fd65c43f5 100644
--- a/system/doc/reference_manual/data_types.xml
+++ b/system/doc/reference_manual/data_types.xml
@@ -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 operators 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>
diff --git a/system/doc/reference_manual/expressions.xml b/system/doc/reference_manual/expressions.xml
index 6b3ba96b6b..77f46d5363 100644
--- a/system/doc/reference_manual/expressions.xml
+++ b/system/doc/reference_manual/expressions.xml
@@ -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>
@@ -836,6 +836,28 @@ number < atom < reference < fun < port < pid < tuple < 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>
@@ -845,17 +867,35 @@ number < atom < reference < fun < port < pid < tuple < 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><<2:2>> < <<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><<3:2>> < <<128>>.</input>
+9> <input><<2:2>> < <<128>>.</input>
+true
+10> <input><<3:2>> < <<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>
--
2.35.3