Compare numbers

181 views
Skip to first unread message

Jean-Philippe Humbert

unread,
Jul 17, 2024, 10:21:52 AM7/17/24
to lu...@googlegroups.com
Hi,

I have a problem with this code:

Lua 5.4.7 Copyright (C) 1994-2024 Lua.org, PUC-Rio
> n1 = -1.00
> n2 = -1.01
> diff = math.abs(n1-n2)
> tol = 0.01
> if (diff > tol) then print("why ?") end
why ?

diff is 0.01
tol is 0.01
Why is the evaluation (diff > tol) true?

Is it a bug or it is something magical with floating numbers?

Regards
Jean-Philippe

Sainan

unread,
Jul 17, 2024, 10:42:11 AM7/17/24
to lu...@googlegroups.com
Floating point numbers aren't perfectly precise, as I think should be common knowledge for programmers. You will see that 'diff' ends up, in fact, being ever-so-slightly greater than 'tol':

n1 = -1.00
n2 = -1.01
diff = math.abs(n1-n2)
tol = 0.01
print(string.format("%.20f", diff)) --> 0.01000000000000000888
print(string.format("%.20f", tol)) --> 0.01000000000000000021

Arnaud Delobelle

unread,
Jul 17, 2024, 10:46:03 AM7/17/24
to lu...@googlegroups.com
It's because floating point numbers cannot represent fractions with a
denominator which is not a power of 2 exactly. It's something to get
used to! See https://0.30000000000000004.com/, it's got links to
interesting reads about it.

To continue with your example

> n1 = -1.00
> n2 = -1.01
> diff = math.abs(n1-n2)
> tol = 0.01
> diff - tol
8.673617379884e-18
> string.format("%0.17f", diff)
0.01000000000000001
> string.format("%0.17f", tol)
0.01000000000000000
> --
> You received this message because you are subscribed to the Google Groups "lua-l" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/lua-l/2B8E986C-9C5B-461B-AE5A-5241731062E8%40me.com.

Christophe Delord

unread,
Jul 17, 2024, 10:50:06 AM7/17/24
to 'Jean-Philippe Humbert' via lua-l
These values have no exact representation with floating point numbers
and intermediate operations add errors.

n1 = -1.00
n2 = -1.01
math.abs(n1-n2)                     ❯ 0.01
("%.20f"):format(math.abs(n1-n2))   ❯ 0.01000000000000000888
math.abs(n1-n2) - 0.01              ❯ 8.673617379884e-18

A classical example is:

0.1+0.1+0.1                         ❯ 0.3
("%.20f"):format(0.1+0.1+0.1)       ❯ 0.30000000000000004441
0.1+0.1+0.1 - 0.3                   ❯ 5.5511151231258e-17

George Georgalis

unread,
Jul 17, 2024, 10:53:10 AM7/17/24
to lu...@googlegroups.com
an easy way to verify earlier comments...
if you set tol = 0.0101 it will behave as expected

--
You received this message because you are subscribed to the Google Groups "lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.


--

George Georgalis

unread,
Jul 17, 2024, 12:49:51 PM7/17/24
to lu...@googlegroups.com
and thinking this over further, why is the default floating point, or the string format, not printed in higher (highest) precision? that would encourage understanding through the needful use of printrf precision... and how does printf %s happen to use the same input precision???

 n1 = -1.00
 n2 = -1.01
 diff = math.abs(n1-n2)
 tol = 0.01
  print(string.format("%.70f\n%.70f\n%.20f\n%.20f\n%f\n%f\n%s\n%s\n", tol,diff,tol,diff,tol,diff,tol,diff))
0.0100000000000000002081668171172168513294309377670288085937500000000000
0.0100000000000000088817841970012523233890533447265625000000000000000000
0.01000000000000000021
0.01000000000000000888
0.010000
0.010000
0.01
0.01



Roberto Ierusalimschy

unread,
Jul 17, 2024, 2:42:42 PM7/17/24
to lu...@googlegroups.com
> and thinking this over further, why is the default floating point, or the
> string format, not printed in higher (highest) precision? that would
> encourage understanding through the needful use of printrf precision... and
> how does printf %s happen to use the same input precision???

Because then we would have a lot of questions from the other side of
this story, complaining that Lua gets all numbers wrong:

> 0.1
0.10000000000000001
> 0.2
0.20000000000000001
> 1.1
1.1000000000000001
> 1.6
1.6000000000000001

Anyway, because 10 is not a power of 2, any precision we use when
printing in decimal either cuts some precision or "creates" some
precision. To see a floating-point number accurately, I would
use hexa-decimal:

> string.format("%a", 0.1)
0x1.999999999999ap-4

Here it becomes clear that 0.1 is a repeating "decimal" in base 16
(and therefore also in base 2). The truncation creates the error.

-- Roberto

George Georgalis

unread,
Jul 17, 2024, 3:43:30 PM7/17/24
to lu...@googlegroups.com
thanks. by why, I only meant to inquire if it should be different.... what is the reasoning of the way it is...
however, I remain very curious how come %s has reasonable precision?
> tol=tol + 0.0002
> print(string.format("%s\n", tol))
0.0102
> print(string.format("%f\n", tol))
0.010200
is it just %.60f with trailing 0s removed?

is there a function to reset floats to their string interpreted values (truncate base translation errors which some calculations could propagate)?




--
You received this message because you are subscribed to the Google Groups "lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.

Foster

unread,
Jul 17, 2024, 3:49:44 PM7/17/24
to lua-l
It has to do with IEEE floats and how they work.  I have this link bookmarked.  Code is python, but a good example / discussion about floats. 


Notice how old the stackoverflow post is.  

K.S. Bhaskar

unread,
Jul 17, 2024, 5:09:22 PM7/17/24
to lu...@googlegroups.com
It is not just IEEE floats. Every numeric implementation has numbers that cannot be exactly represented, and what numeric implementation software should use depends on the application. In our case, for example, because YottaDB used in financial, electronic medical records, and library applications (among others) that need exact decimal numbers, the underlying numeric implementation is decimal (https://docs.yottadb.com/MultiLangProgGuide/programmingnotes.html#numeric-considerations). As computers have hardware for floating point arithmetic, but not decimal arithmetic (except some rather ancient primitive opcodes), the runtime system tries to use integer operations for small numbers.

Regards
– Bhaskar
To view this discussion on the web visit https://groups.google.com/d/msgid/lua-l/cfa68909-8f6c-4ed2-b697-ce6236dd9061n%40googlegroups.com.

--

Pierpaolo Bernardi

unread,
Jul 17, 2024, 5:18:33 PM7/17/24
to lu...@googlegroups.com
On Wed, Jul 17, 2024 at 9:43 PM George Georgalis <geo...@galis.org> wrote:
>
> thanks. by why, I only meant to inquire if it should be different.... what is the reasoning of the way it is...
> however, I remain very curious how come %s has reasonable precision?
> > tol=tol + 0.0002
> > print(string.format("%s\n", tol))
> 0.0102
> > print(string.format("%f\n", tol))
> 0.010200
> is it just %.60f with trailing 0s removed?
>
> is there a function to reset floats to their string interpreted values (truncate base translation errors which some calculations could propagate)?

You want DECIMAL floats. Some programming languages have them built
in, Lua does not.

Google "decimal floating point" to know more, or you can start from
here: https://en.wikipedia.org/wiki/Decimal_floating_point

P.

sur-behoffski

unread,
Jul 17, 2024, 11:41:45 PM7/17/24
to lua-l
Prof. William Kahan is probably the principal architect of the
IEEE 754 floating-point standard, as implemented by everybody
everywhere. He was honoured with a Turing Award in 1989 for his
effort.

He was he central figure behind HP's calculator computation
engine(s) in the 1970s+, where calculations were done to
13-decimal-digits internally, and rounded back to 10 digits when
finally presented to a user-visible target. The extra precision
generally did a very good job of mitigating effects of limited
precision.

His home page has a wealth of information on floating-point
computation, and how it can be fragile, including how "tolerances"
can be difficult to choose on an algorithm-by-algorithm case, let
alone a general solution.

https://people.eecs.berkeley.edu/~wkahan/

One example is how the "simple" way of calculating the area of a
triangle can go haywire in the presence of needle-like triangles,
due to floating-point roundoff:

Miscalculating Area and Angles of a Needle-like Triangle
https://people.eecs.berkeley.edu/~wkahan/Triangle.pdf

In the case of modern programming languages, including language
specification, compiler etc toolchains, and external numerical-methods
libraries, the IEEE standard provides facilities that a language
may internally use to improve accuracy, without this being explicit
to the end user.

For example, long double (64 bits) has 53 bits of significand
(which I previously knew as "mantissa").

However, internally, IEEE 754 provides 80-bit floating-point
numbers, ("double extended" precision), where the significand has
at least 64 bits of precision. Language+toolchains+library are
encouraged to exploit the extra precision, unless explicitly told
not to (in cold blood) by the programmer. As with the calculator
example above, doing intermediate computations at a higher precision,
and only rounding when the result becomes visible to the user, helps
mitigate rounding problems. However, using this feature may disrupt
a deliberate choice by an informed user.

For example, gcc has optimization options such as:

-ffloat-store
-fexcess-precision=STYLE

-------

Stepping outside the realm of numbers Lua (briefly), C99 and C++17
provide explicit ways to access and maintain this excess precision.
Lua, however, is strictly C89, and so does not directly offer this
option. However, a user can, of course, create an external
package that exploits these features, and can access it via
require().

-------

Finally, see also this article from the November 1997 Dr Dobb's
Journal:

A Conversation with William Kahan

By Jack Woehr, November 01, 1997

https://www.drdobbs.com/architecture-and-design/a-conversation-with-william-kahan/184410314

--
cheers,

sur-behoffski (Brenton Hoff)
programmer, Grouse Software

Jean-Philippe Humbert

unread,
Jul 18, 2024, 5:17:56 AM7/18/24
to lu...@googlegroups.com
Hi,

I’d like to thank everybody for your help.
Yes, I’m aware of the problematic with floating point number. But I had a bad day :-)

My main problem was using print(to_string(tol)) which was always displaying « 0.01 ».
After reading the documentation and comments, I should have used string.format.

I have changed the code to use epsilon for the comparison. Now it works perfectly.

n1 = -1.00

n2 = -1.01

diff = math.abs(n1-n2)

tol = 0.01

epsilon = tol / 100 — This is for my case a good value


if (diff > tol) then print("It's due to floating number representation.") end

if (diff-too < epsilon) then print("the numbers are equal. That´s what I want.") end


If there is a better way to solve this problem, please let me know.

Regards,
Jp.


--
You received this message because you are subscribed to the Google Groups "lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.

Sainan

unread,
Jul 18, 2024, 7:29:47 AM7/18/24
to lu...@googlegroups.com
Syntax Highlighting in an email is certainly interesting, tho this looks like a non-faithful Monokai (strings in green?!)

Chris Leonard

unread,
Jul 18, 2024, 7:36:41 AM7/18/24
to lu...@googlegroups.com
Especially helpful is fenv.h rounding modes, from C99, as you can set to round approximate results towards +infinity and -infinity and then compare the difference. If the difference is huge relatively then you know your calculation has a large error, without analysing the code manually.

https://en.cppreference.com/w/c/numeric/fenv

I would guess you can set these and affect Lua? Would be nice to have a luarocks package for this, might try this out tonight.

Chris Leonard

Lars Müller

unread,
Jul 18, 2024, 7:39:22 AM7/18/24
to lu...@googlegroups.com
What Lua currently does is create a false sense of numbers being equal
by silently truncating them when tostring'ing them. This is a problem in
particular when this is carelessly used in serialization code, causing a
loss of information.

What Lua could do is instead is find the shortest decimal representation
that converts to the appropriate float (I believe there to be efficient
algorithms for this). Hence tostring(0.3) would be "0.3" - and in
general "round tripping" numbers through tonumber/tostring would not
make their string representation any longer - but tostring(0.1 + 0.2)
would be 0.30000000000000004, revealing the rounding error.

Roberto Ierusalimschy

unread,
Jul 18, 2024, 9:13:48 AM7/18/24
to lu...@googlegroups.com
> For example, long double (64 bits) has 53 bits of significand
> (which I previously knew as "mantissa").
>
> However, internally, IEEE 754 provides 80-bit floating-point
> numbers, ("double extended" precision), where the significand has
> at least 64 bits of precision. [...]

Just to avoid confusion: What you called "long double" (64 bits),
C calls "double", and what you called "double extended" (80 bits),
C calls "long double". (Although, by the standard, "long double" can
be implemented just like a regular "double".)

Lua by default uses "double", but with a single change in luaconf.h
it can use "float" (32 bits) or "long double".

-- Roberto

Philippe Verdy

unread,
Jul 18, 2024, 9:25:08 AM7/18/24
to lu...@googlegroups.com

What does forbid one to compile a C99 compatible version of Lua, with full ieee 764 features ?
Anyway you can still load (require) an addon library when you extra precision, or need support for decimal floating point (which, as opposés to what was sais in a prior msg, has hardware support notably for IEEE 754 decimal formats, plus features allowing efficient implementations of BCD decimals for big integers, big rationals or big decimal floats with very large mantissa (used in several well known librairies to implement them in other languages and math tools) it's easy to implement a Lua extension library that is used to expose in Lua the features implemented in well known efficient librairies)


--
You received this message because you are subscribed to the Google Groups "lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.

Luiz Henrique de Figueiredo

unread,
Jul 18, 2024, 9:39:22 AM7/18/24
to lu...@googlegroups.com

Anyway you can still load (require) an addon library when you extra precision, or need support for decimal floating point 


There are Lua bindings for several such libraries at 

Roberto Ierusalimschy

unread,
Jul 18, 2024, 10:03:15 AM7/18/24
to 'Lars Müller' via lua-l
> What Lua currently does is create a false sense of numbers being equal
> by silently truncating them when tostring'ing them. This is a problem in
> particular when this is carelessly used in serialization code, causing a
> loss of information.

As already discussed, for any fixed decimal representation either we
"create a false sense of numbers being equal" or we create a real sense
of numbers being different from what is written. For serialization,
one should use hexadecimal, which is always correct and usually
faster. (And careless code is always a problem. :-)


> What Lua could do is instead is find the shortest decimal representation
> that converts to the appropriate float (I believe there to be efficient
> algorithms for this). Hence tostring(0.3) would be "0.3" - and in
> general "round tripping" numbers through tonumber/tostring would not
> make their string representation any longer - but tostring(0.1 + 0.2)
> would be 0.30000000000000004, revealing the rounding error.

There are algorithms for this: "grisu" and its variants. They require
64-bit integers to format doubles; Lua code does not assume the
existence of such integers. Moreover, if we wanted to keep support
for long double, that would need integers with at least 66 bits.

-- Roberto
Reply all
Reply to author
Forward
0 new messages