# History of \`isapprox\` in languages

**URL:** <https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347>\
**Category:** Numerics\
**Tags:** question, numerics, approx\
**Created:** [September 12, 2024, 6:51pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347 "2024-09-12T18:51:09Z")\
**Posts on this page:** 18\
**Page:** 3

<div class="post-metadata">

**Author:** ![Richard\_Fateman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/richard_fateman/32/212038_2.png) [@Richard\_Fateman](https://discourse.julialang.org/u/Richard_Fateman)\
**Post date:** [September 19, 2024, 3:24pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/41 "2024-09-19T15:24:02Z")

</div>

There are a couple of different contexts at work here.

Maybe you are doing essentially a one-off sanity check, like interactively eyeballing an answer to see if a result is not absurd. But using isapprox() instead of your eyes.

Maybe you are about to cement-in a test in a routine in the midst of a lengthy, critical, and difficult-to-read program typically used by others.  
Maybe like that Bessel program? ]Oddly enough I’ve written  
a (too long- 21 page) paper on evaluating this function…  
[https://people.eecs.berkeley.edu/~fateman/papers/hermite.pdf](https://people.eecs.berkeley.edu/~fateman/papers/hermite.pdf) ]

I’m reminded of the ChatGPT (etc) programs that offer to write you a program from a natural language description. The implicit (or  
sometimes explicit) warning is that you should confirm that the program is correct. My thought is that if you use isapprox() in that second context, you should be warned.

A historical note… I seem to recall that Iverson’s APL, circa 1957) had a symbol for approximate equality; I haven’t tracked it down though. If no one else has mentioned it here, it occurs to me that it may be some sort of historical signpost. APL was (is?) worth knowing about.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [September 19, 2024, 3:30pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/42 "2024-09-19T15:30:23Z")

</div>

> [@Richard\_Fateman](#):
>
> My thought is that if you use isapprox() in that second context, you should be warned.

Do you mean for program validation? Why is calling `isapprox` (ideally with custom tolerances, at least in tests to validate the precise accuracy as opposed to tests for coarse bugs) worse than writing your own similar test over and over (often done badly)?

That’s what I don’t get here. Definitely, you should know what _any_ function (not just `isapprox`) does if you are going to call it! But what do you have against code re-use? All of the examples you have given of approximate comparisons were special cases of `isapprox`.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [September 19, 2024, 8:35pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/43 "2024-09-19T20:35:17Z")

</div>

2 posts were split to a new topic: [How to nondimensionalize problems with multiple/variable scales?](https://discourse.julialang.org/t/how-to-nondimensionalize-problems-with-multiple-variable-scales/119624)

---

<div class="post-metadata">

**Author:** ![Richard\_Fateman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/richard_fateman/32/212038_2.png) [@Richard\_Fateman](https://discourse.julialang.org/u/Richard_Fateman)\
**Post date:** [September 19, 2024, 3:52pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/44 "2024-09-19T15:52:23Z")

</div>

In brief, anyone reading/ perhaps modifying/ your code will have to know (or ignore) the meaning of isapprox. Also maybe  
find out where you have set or defined atol, rtol, norm. If you are repeatedly comparing floating-point values for  
approximate equality with similar but not identical code segments, written badly, then you have other problems.  
Testing for abs(a-b)\<test is really pretty clear at least for a,b, floating-point values, if that’s what you mean.

Offhand, I have not much uniform intuition as to how I might be comparing structures, matrices, vectors approximately  
Say a,b are polynomials or symbolic expressions.  
. Do they have similar coefficients? similar location of zeros? similar limiting values? similar some norm?

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [September 19, 2024, 3:57pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/45 "2024-09-19T15:57:37Z")

</div>

> [@Richard\_Fateman](#):
>
> In brief, anyone reading/ perhaps modifying/ your code will have to know (or ignore) the meaning of isapprox.

I agree, you should understand the functions you call. But the definition of `isapprox` is not especially complicated (if you know what relative and absolute error mean), and it is widely used for validation suites in the Julia ecosystem so it is not an obscure function. The hard part is understanding floating-point comparisons in the first place.

> [@Richard\_Fateman](#):
>
> Testing for abs(a-b)\<test is really pretty clear at least for a,b, floating-point values,

But most validation tests are not like this, they are relative-error tests, because for most floating-point comparisons it is more natural to set a relative tolerance than an absolute one. And those are much more cumbersome to write: `norm(a - b) <= rtol * max(norm(a), norm(b))` or similar.

For one thing, this involves writing `a` and/or `b` twice, which means that you have to write a function or use temporary variables if they are expressions and you don’t want to write/eval the expressions twice. And if the arguments are arrays, many people get it wrong on their first try — they check [approximate equality elementwise](https://discourse.julialang.org/t/how-to-add-a-tolerance-to-isapprox/118711/7) rather than using a norm to set the overall scale.

The Julia stdlib and packages have thousands of validation tests that check relative errors; I don’t understand why they should all have to rewrite this code, even though it is short. (Many of the existing tests could benefit from tightened tolerances, however; `@test` supports compact syntax such as `@test x ≈ y rtol=1e-13` for this.)

---

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [September 19, 2024, 6:27pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/47 "2024-09-19T18:27:08Z")

</div>

Just sharing a comment to say that I appreciate the opinion of various experts in the subject. And to say that there are different objectives at play in the arguments.

From one side @stevengj seems to promote a deeper understanding of floating point arithmetic. From the other side @Richard_Fateman seems to value a friendlier end-user experience that escapes the floating point difficulties.

As someone not trained in the deepest details of floating points, I would really appreciate if a scientific language could hide these counter-intuitive results from me. I bet that the `isapprox` behavior near `0` is surprising for \>95% of users of programming languages, including Julia users.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [September 19, 2024, 6:31pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/48 "2024-09-19T18:31:00Z")

</div>

> [@juliohm](#):
>
> From the other side @Richard_Fateman seems to value a friendlier end-user experience that escapes the floating point difficulties.

It seems like he’s advocating the opposite? He wants to remove/discourage the `isapprox` function and tell people to re-implement the desired comparison manually whenever they need it.

> [@juliohm](#):
>
> I bet that the `isapprox` behavior near `0` is surprising for \>95% of users of programming languages, including Julia users.

Yes, 95% of people think that 10^{-8} is approximately zero, because they’ve not internalized that to say whether something is big or small you first have to ask “compared to what?”

---

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [September 19, 2024, 6:32pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/49 "2024-09-19T18:32:39Z")

</div>

My understanding of the comments is a bit different. Maybe I misunderstood the message.

---

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [September 19, 2024, 6:42pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/50 "2024-09-19T18:42:15Z")

</div>

> [@stevengj](#):
>
> Yes, 95% of people think that 10^{-8}10−810^{-8} is approximately zero, because they’ve not internalized that to say whether something is big or small you first have to ask “compared to what?”

I think most people would compare it with what they know about floating points. If they know that Float64 has n digits of precision, they will create a threshold in their minds that discards anything beyond k digits. I know that k isn’t well-defined for all cases based on the comments above, but people still will apply similar criteria to pick “atol” implicitly in their applications. This “fallback” value that most people pick without thinking too much (assuming order of magnitude 1 for example) is something that could be fine tuned with ScopedValues.jl as suggested above.

My personal opinion is that this behavior would be more intuitive for end-users than simply returning `false` whenever one of the arguments is `0`. These are all design choices after all, and if we know our audience is well served with the current choice, that is fine.

---

<div class="post-metadata">

**Author:** ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)\
**Post date:** [September 19, 2024, 7:16pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/51 "2024-09-19T19:16:21Z")

</div>

> [@juliohm](#):
>
> I think most people would compare it with what they know about floating points.

I think “most people” know next to nothing about floating points. If however we speak about (potential) Julia users, they come from very different backgrounds and will have very different notion about approximate equality.

For me, Unitful.jl is one of the largest Julia blessings. I’d let it convert all units to default SI units - meaning, even in our macroworld there would be numbers spanning a dozen of orders of magnitude both up and down. You cannot have the same `atol` for the Young’s modulus of steel (`1e11 Pa`) and the diffusion coefficient of hydrogen in steel (`1e-15 m²/s`).

---

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [September 19, 2024, 7:17pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/52 "2024-09-19T19:17:04Z")

</div>

> [@Eben60](#):
>
> I think “most people” know next to nothing about floating points

Exactly.

> [@Eben60](#):
>
> If however we speak about (potential) Julia users

Subset of most people.

---

<div class="post-metadata">

**Author:** ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)\
**Post date:** [September 19, 2024, 7:18pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/53 "2024-09-19T19:18:58Z")

</div>

> [@juliohm](#):
>
> Subset of most people.

Not representative one

---

<div class="post-metadata">

**Author:** ![DanielVandH](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielvandh/32/31134_2.png) [@DanielVandH](https://discourse.julialang.org/u/DanielVandH)\
**Post date:** [September 19, 2024, 7:25pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/54 "2024-09-19T19:25:44Z")

</div>

I’m not convinced that the default `atol=0` is such a problem for the average user? They might get surprised by it once, see the docstring where it elaborates on it (though maybe it could be given as an admonition to highlight it better), and then understand to choose a better tolerance from then on (which I believe is a good thing - you should be thinking about tolerances). As @stevengj already mentions, I don’t see what other good default could be chosen. Not everything can be so simple.

I also don’t think the note in `isapprox`’s docstring (or the main point behind this discussion) really requires anyone to know anything about floating point arithmetic anyway. It would be obvious (upon reflection, at least) that comparing zero to other numbers is hard since zero loses all information about units in the first place as well (among the other reasons given already).

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [September 19, 2024, 7:51pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/55 "2024-09-19T19:51:39Z")

</div>

> [@juliohm](#):
>
> If they know that Float64 has n digits of precision, they will create a threshold in their minds that discards anything beyond k digits.

Right. As I’ve said many times, many people’s mental model of floating-point arithmetic is closer to fixed-point arithmetic: they think of it as a fixed number of _digits past the decimal point_, rather than a fixed number of _significant digits_.

---

<div class="post-metadata">

**Author:** ![favba](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/favba/32/2735_2.png) [@favba](https://discourse.julialang.org/u/favba)\
**Post date:** [September 19, 2024, 8:04pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/56 "2024-09-19T20:04:38Z")

</div>

> [@juliohm](#):
>
> than simply returning `false` whenever one of the arguments is `0`

Nitpicking on that, `isapprox` will return `true` if the variable you want to test is exactly zero:

```julia
julia> f(x) = x*x - x^2
f (generic function with 1 method)

julia> b = 100.0
100.0

julia> f(b) ≈ 0.0
true

```

So, `isapprox(0.0)` simplyfies to `isequal(0.0)`.

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [September 19, 2024, 8:21pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/57 "2024-09-19T20:21:18Z")

</div>

> [@Richard\_Fateman](#):
>
> A historical note… I seem to recall that Iverson’s APL, circa 1957) had a symbol for approximate equality; I haven’t tracked it down though. If no one else has mentioned it here, it occurs to me that it may be some sort of historical signpost. APL was (is?) worth knowing about.

I found a pair of blog posts on “tolerated comparison” in APL.

[https://www.dyalog.com/blog/2018/11/tolerated-comparison-part-1/](https://www.dyalog.com/blog/2018/11/tolerated-comparison-part-1/)

[https://www.dyalog.com/blog/2019/06/tolerated-comparison-part-2/](https://www.dyalog.com/blog/2019/06/tolerated-comparison-part-2/)

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [September 19, 2024, 8:32pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/58 "2024-09-19T20:32:48Z")

</div>

> [@jar1](#):
>
> I found a pair of blog posts on “tolerated comparison” in APL.
> 
> [https://www.dyalog.com/blog/2018/11/tolerated-comparison-part-1/](https://www.dyalog.com/blog/2018/11/tolerated-comparison-part-1/)
> 
> [Tolerated Comparison, Part 2 | Dyalog Blog](https://www.dyalog.com/blog/2019/06/tolerated-comparison-part-2/)

They give these criteria:

> [@](#):
>
> 1. It’s reflexive: `a≂a` for any number `a`.
> 2. It’s symmetric in `a` and `b`: reversing them makes no difference in the result. This is because `⌈` and `(|-)` are symmetric functions.
> 3. It scales linearly: multiplying both `a` and `b` by the same nonzero value (even a negative number) does not change the result.
> 4. It’s convex in each variable: if `a≂b` and `a≂d` for some `a`, and `c` is between `b` and `d`, then `a≂c`.

which are satisfied by `x ≈ y` (i.e. `isapprox`) in Julia (until you scale to overflow/underflow, of course, and `NaN` is not reflexive due to IEEE equality rules unless you pass `nans=true`). They _aren’t_ satisfied if you had a nonzero default `atol`, because that would violate scale-invariance (3). (In contrast, [`numpy.isclose`](https://numpy.org/doc/stable/reference/generated/numpy.isclose.html) violates both 2 and 3.)

In particular, the APL tolerant comparison is [defined by](https://www.jsoftware.com/papers/satn23.htm):

> Tolerant comparison considers two numbers to be equal if they are within some neighborhood. The neighborhood has a radius of `⎕ct` times the larger of the two in absolute value.

where `⎕ct` is a “system variable”. This definition corresponds _exactly_ to Julia’s `isapprox(x,y, rtol=⎕ct)`, i.e. `abs(x - y) ≤ rtol*max(abs(x),abs(y))`.

~~I couldn’t find any clear description of their default system `rtol` value, and tastes could certainly vary on what choice to make here.~~ @jar1 points out [this paper](https://www.dyalog.com/uploads/documents/Papers/tolerant_comparison/tolerant_comparison.htm) which suggests that the default tolerance was `2^-32 ≈ 2.3e-10`, a bit smaller than Julia’s `√ε ≈ 1.5e-8` (in `Float64` precision).

So, it seems the only difference between APL’s “tolerant comparison” and Julia’s `x ≈ y` might be the choice of the default `rtol`.

(As @juliohm noted above, Python 3.5’s [`math.isclose`](https://docs.python.org/3/library/math.html#math.isclose) is _also_ equivalent to `≈`, except with a default `rtol` of `1e-9` … very similar to Julia’s choice except that it effectively assumes the precision is always `Float64`.)

---

<div class="post-metadata">

**Author:** ![GunnarFarneback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gunnarfarneback/32/1827_2.png) [@GunnarFarneback](https://discourse.julialang.org/u/GunnarFarneback)\
**Post date:** [September 19, 2024, 8:58pm UTC](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347/59 "2024-09-19T20:58:20Z")

</div>

The way I see it, the default `atol` of 0 means that you might get some unexpected test failures that you need to investigate and find out that you need to modify your tests or set an appropriate `atol`. I rather have that than having tests pass that shouldn’t because your data was scaled in a way which wasn’t appropriate for a non-zero default `atol`.

[Previous page](https://discourse.julialang.org/t/history-of-isapprox-in-languages/119347.md?page=2)
