caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* [Caml-list] string_of_float (0.1 +. 0.2)
@ 2022-06-15  1:59 Kenichi Asai
  2022-06-15  6:22 ` Andreas Rossberg
  2022-06-15 14:07 ` Gabriel Scherer
  0 siblings, 2 replies; 9+ messages in thread
From: Kenichi Asai @ 2022-06-15  1:59 UTC (permalink / raw)
  To: caml-list

On OCaml 4.12.0 on M1 mac, I got:

# 0.1 +. 0.2;;
- : float = 0.300000000000000044
# string_of_float (0.1 +. 0.2);;
- : string = "0.3"

Why don't I obtain "0.300000000000000044" here?

Here is some background.  I am writing an OCaml interpreter that
mimics most part of the original OCaml interpreter.  In the OCaml
interpreter, 0.1 and 0.2 are represented as

Pexp_constant (Pconst_float ("0.1", None))
Pexp_constant (Pconst_float ("0.2", None))

When I add these two numbers, I would have to execute

let a = float_of_string "0.1"
let b = float_of_string "0.2"
let c = a +. b
let d = string_of_float c

and then return

Pexp_constant (Pconst_float (d, None))

At this point, however, since d is "0.3" instead of
"0.300000000000000044" (even though c is 0.300000000000000044), I
cannot return 0.300000000000000044 as a result.  How can I mimic the
OCaml behavior?

Sincerely,

-- 
Kenichi Asai

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Caml-list] string_of_float (0.1 +. 0.2)
  2022-06-15  1:59 [Caml-list] string_of_float (0.1 +. 0.2) Kenichi Asai
@ 2022-06-15  6:22 ` Andreas Rossberg
  2022-06-15  7:00   ` François Pottier
  2022-06-15 14:07 ` Gabriel Scherer
  1 sibling, 1 reply; 9+ messages in thread
From: Andreas Rossberg @ 2022-06-15  6:22 UTC (permalink / raw)
  To: Kenichi Asai; +Cc: caml-list

You can use sprintf, which allows you to specify a precision:

# Printf.sprintf "%g" (0.1 +. 0.2);;
- : string = "0.3"
# Printf.sprintf "%.16g" (0.1 +. 0.2);;
- : string = "0.3"
# Printf.sprintf "%.18g" (0.1 +. 0.2);;
- : string = "0.300000000000000044"
# Printf.sprintf "%.24g" (0.1 +. 0.2);;
- : string = “0.300000000000000044408921”

The OCaml manual does not say what the default is, but it appears to be .6 for printf (like in C), while string_of_float is equivalent to (sprintf "%.12g”) and the REPL uses .18 (which is the maximum meaningful decimal precision for 64 bit floats):

# Printf.sprintf "%g" 0.1234567890123456789;;
- : string = "0.123457"
# string_of_float 0.123456789012345678;;
- : string = “0.123456789012”
# 0.1234567890123456789;;
- : float = 0.123456789012345677

/Andreas


> On 15. 6. 2022, at 03:59, Kenichi Asai <asai@is.ocha.ac.jp> wrote:
> 
> On OCaml 4.12.0 on M1 mac, I got:
> 
> # 0.1 +. 0.2;;
> - : float = 0.300000000000000044
> # string_of_float (0.1 +. 0.2);;
> - : string = "0.3"
> 
> Why don't I obtain "0.300000000000000044" here?
> 
> Here is some background.  I am writing an OCaml interpreter that
> mimics most part of the original OCaml interpreter.  In the OCaml
> interpreter, 0.1 and 0.2 are represented as
> 
> Pexp_constant (Pconst_float ("0.1", None))
> Pexp_constant (Pconst_float ("0.2", None))
> 
> When I add these two numbers, I would have to execute
> 
> let a = float_of_string "0.1"
> let b = float_of_string "0.2"
> let c = a +. b
> let d = string_of_float c
> 
> and then return
> 
> Pexp_constant (Pconst_float (d, None))
> 
> At this point, however, since d is "0.3" instead of
> "0.300000000000000044" (even though c is 0.300000000000000044), I
> cannot return 0.300000000000000044 as a result.  How can I mimic the
> OCaml behavior?
> 
> Sincerely,
> 
> -- 
> Kenichi Asai


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Caml-list] string_of_float (0.1 +. 0.2)
  2022-06-15  6:22 ` Andreas Rossberg
@ 2022-06-15  7:00   ` François Pottier
  0 siblings, 0 replies; 9+ messages in thread
From: François Pottier @ 2022-06-15  7:00 UTC (permalink / raw)
  To: Andreas Rossberg, Kenichi Asai; +Cc: caml-list


Hi,

Le 15/06/2022 à 08:22, Andreas Rossberg a écrit :
 > The OCaml manual does not say what the default is, but it appears to 
be .6 for printf (like in C), while string_of_float is equivalent to 
(sprintf "%.12g”) and the REPL uses .18 (which is the maximum meaningful 
decimal precision for 64 bit floats):

Indeed, the implementation of string_of_float uses "%.12g".

The manual says that string_of_float returns "the" string representation 
of a
floating-point number. I would claim that there is a problem here -- either
the manual should warn that there is a potential loss of information, or the
code should be fixed so as to lose no information.

--
François Pottier
francois.pottier@inria.fr
http://cambium.inria.fr/~fpottier/

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Caml-list] string_of_float (0.1 +. 0.2)
  2022-06-15  1:59 [Caml-list] string_of_float (0.1 +. 0.2) Kenichi Asai
  2022-06-15  6:22 ` Andreas Rossberg
@ 2022-06-15 14:07 ` Gabriel Scherer
  2022-06-15 14:25   ` Daniel Bünzli
  1 sibling, 1 reply; 9+ messages in thread
From: Gabriel Scherer @ 2022-06-15 14:07 UTC (permalink / raw)
  To: Kenichi Asai; +Cc: caml-list

[-- Attachment #1: Type: text/plain, Size: 2049 bytes --]

Nathanaëlle Courant, with help from Julien Lepiller and myself, wrote a
rather complete interpreter/evaluator for OCaml parsetrees (in OCaml) as
part of the Camlboot project

  https://arxiv.org/abs/2202.09231
  https://github.com/Ekdohibs/camlboot
  https://github.com/Ekdohibs/camlboot/tree/master/interpreter

We take as input OCaml parsetrees, but we evaluate into a type of "value"
that we defined ourselves, and stores a "float" for floating-point numbers:

https://github.com/Ekdohibs/camlboot/blob/2692b14a4e685387194556e511fe23057d25c6c3/interpreter/data.ml#L46-L66

type value =
...
| Float of float
...

Kenichi, I don´t understand what your own constraints, but in general I
have the impression that "float" is better than "string" to represent
double values used for computation. "string" was meant to accurately
represent the source value and avoid any serialization/portability issue,
but those constraints are rather for data exchange.

On Wed, Jun 15, 2022 at 4:00 AM Kenichi Asai <asai@is.ocha.ac.jp> wrote:

> On OCaml 4.12.0 on M1 mac, I got:
>
> # 0.1 +. 0.2;;
> - : float = 0.300000000000000044
> # string_of_float (0.1 +. 0.2);;
> - : string = "0.3"
>
> Why don't I obtain "0.300000000000000044" here?
>
> Here is some background.  I am writing an OCaml interpreter that
> mimics most part of the original OCaml interpreter.  In the OCaml
> interpreter, 0.1 and 0.2 are represented as
>
> Pexp_constant (Pconst_float ("0.1", None))
> Pexp_constant (Pconst_float ("0.2", None))
>
> When I add these two numbers, I would have to execute
>
> let a = float_of_string "0.1"
> let b = float_of_string "0.2"
> let c = a +. b
> let d = string_of_float c
>
> and then return
>
> Pexp_constant (Pconst_float (d, None))
>
> At this point, however, since d is "0.3" instead of
> "0.300000000000000044" (even though c is 0.300000000000000044), I
> cannot return 0.300000000000000044 as a result.  How can I mimic the
> OCaml behavior?
>
> Sincerely,
>
> --
> Kenichi Asai
>

[-- Attachment #2: Type: text/html, Size: 3093 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Caml-list] string_of_float (0.1 +. 0.2)
  2022-06-15 14:07 ` Gabriel Scherer
@ 2022-06-15 14:25   ` Daniel Bünzli
  2022-06-16  1:45     ` Kenichi Asai
  0 siblings, 1 reply; 9+ messages in thread
From: Daniel Bünzli @ 2022-06-15 14:25 UTC (permalink / raw)
  To: Kenichi Asai, Gabriel Scherer; +Cc: caml-list

On 15 June 2022 at 16:07:58, Gabriel Scherer (gabriel.scherer@gmail.com) wrote:

> Kenichi, I don´t understand what your own constraints, but  
> in general I have the impression that "float" is better than "string"  
> to represent double values used for computation.

If there a reason not to do what Gabriel suggests you can serialize a bit-by-bit accurate representation of floats[^1] by using `Format.sprintf "%h"`. This format can be input again with float_of_string.

Daniel

[^1]: There may be edge cases with nans since those will all be serialized to the string "nan" and input back as OCaml's Float.nan value.

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Caml-list] string_of_float (0.1 +. 0.2)
  2022-06-15 14:25   ` Daniel Bünzli
@ 2022-06-16  1:45     ` Kenichi Asai
  2022-06-16  6:24       ` Oleg
  0 siblings, 1 reply; 9+ messages in thread
From: Kenichi Asai @ 2022-06-16  1:45 UTC (permalink / raw)
  To: Daniel Bünzli; +Cc: Gabriel Scherer, caml-list

Thank you all for the information.

> Nathanaëlle Courant, with help from Julien Lepiller and myself, wrote a
> rather complete interpreter/evaluator for OCaml parsetrees (in OCaml) as
> part of the Camlboot project

Yes, I read the programming paper.  That's an interesting project.

> Kenichi, I don't understand what your own constraints, but
> in general I have the impression that "float" is better than "string"
> to represent double values used for computation.

I am developing an OCaml stepper that executes an OCaml program step
by step.  From the OCaml parsetree for

let a = 0.1 +. 0.2 +. 0.4

I want an OCaml parsetree for

let a = 0.3 +. 0.4

or

let a = 0.300000000000000044 +. 0.4

if this is what OCaml uses internally.  I want to produce an OCaml
parsetree rather than my own parsetree that maintains float as is,
because I could then reuse pretty printer of OCaml.

From Andreas' e-mail, I understand what's happening.  (Thank you!)  I
thought I would use Printf.sprintf "%.18g" in place of string_of_float,
but Daniel's e-mail made me think it could be insufficient, because
even if I use .18 (or .24), it is still an approximation of the float.
(Am I correct?)  I tried to use `Format.sprintf "%h"` in place of
string_of_float, but the OCaml pretty printer produces

let a = 0x1.3333333333334p-2 +. 0.4

which is not suitable for a stepper used by novice programmers.  For
now, I think I will use Printf.sprintf "%.18g" and see if students see
any difference between OCaml execution and stepper execution.

I agree with Francois that it would be nice if the OCaml manual could
mention a potential loss of information.  I first thought that
replacing "%.12g" with "%.18g" solves the problem, but "the" string
representation of a float turned out to be more complicated than I
thought.

Thank you all for the discussion!

Sincerely,

-- 
Kenichi Asai

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Caml-list] string_of_float (0.1 +. 0.2)
  2022-06-16  1:45     ` Kenichi Asai
@ 2022-06-16  6:24       ` Oleg
  2022-06-16  9:01         ` Andreas Rossberg
  0 siblings, 1 reply; 9+ messages in thread
From: Oleg @ 2022-06-16  6:24 UTC (permalink / raw)
  To: asai; +Cc: caml-list


Actually the similar problem of accurately conveying floats also
occurs in MetaOCaml/Code generation. After all, what you are doing is
a sort of reflection.

First of all, accurate (lossless) printing of floats is a research
area in itself. The latest result is

  http://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf
  https://github.com/google/double-conversion

Perhaps some day it can be incorporated in OCaml, so that
string_of_float truly returns *the* printable representation of a float.

Hexademical printout (which is also supported in C) 
mentioned by Daniel Buenzli is another way -- provided we don't
actually have to look at the printed value, because we probably won't
understand it anyway.

In practice in our recent project, we settled on

let float : float -> float cde = fun x ->
  let str = if Float.is_integer x then string_of_float x else
            Printf.sprintf "%.17g" x

which seems to work well. At least, it solved the problems when the
results of our generated signal processing code differed slightly from
the results of the hand-written reference C code, due to slightly
different printed FP values in FP array initializers (filter
coefficients). We used to use string_of_float back then. With the
above float, the problem is solved.

I have to add that float above does not account for NaN, plus/minus
infinity and -0. So, the fully production code also has to add these
cases.


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Caml-list] string_of_float (0.1 +. 0.2)
  2022-06-16  6:24       ` Oleg
@ 2022-06-16  9:01         ` Andreas Rossberg
  2022-06-16  9:14           ` [Caml-list] unsubscribe Jean-Denis EIDEN JEAN-DENIS
  0 siblings, 1 reply; 9+ messages in thread
From: Andreas Rossberg @ 2022-06-16  9:01 UTC (permalink / raw)
  To: Oleg; +Cc: asai, caml-list

[-- Attachment #1: Type: text/plain, Size: 1438 bytes --]

Since I believe it hasn’t been stated explicitly in this thread yet, a reminder that it is generally _impossible_ to represent arbitrary float values accurately (and finitely) in decimal notation. Except for few cases, you will have to cut off and round at some point.

But that doesn’t necessarily mean that round-tripping between text and binary loses precision, as long as the rounding is precise enough for both binary-to-text and text-to-binary conversion. Though as Oleg points out, that is not an easy problem.

> On 16. 6. 2022, at 08:24, Oleg <oleg@okmij.org> wrote:
> 
> In practice in our recent project, we settled on
> 
> let float : float -> float cde = fun x ->
>  let str = if Float.is_integer x then string_of_float x else
>            Printf.sprintf "%.17g" x

"%.17g" is what we use in the WebAssembly reference interpreter as well. Plus, we do some extra work to also preserve -0.0 and NaNs. Simplified a bit, it's something like this:

  let float_to_string x =
    let x' = abs_float x in
    (if Int64.bits_of_float x < 0L then "-" else "") ^
    if x' <> x' then
      let payload = Int64.(logand (bits_of_float x') 0x000f_ffff_ffff_ffffL) in
      "nan:0x" ^ Printf.sprintf "%Lx" payload
    else
      let s = Printf.sprintf "%.17g" x' in
      if s.[String.length s - 1] = '.' then s ^ "0" else s

Our of_string function recognises the special NaN syntax accordingly.

/Andreas


[-- Attachment #2: Type: text/html, Size: 2684 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [Caml-list] unsubscribe
  2022-06-16  9:01         ` Andreas Rossberg
@ 2022-06-16  9:14           ` Jean-Denis EIDEN JEAN-DENIS
  0 siblings, 0 replies; 9+ messages in thread
From: Jean-Denis EIDEN JEAN-DENIS @ 2022-06-16  9:14 UTC (permalink / raw)
  To: Oleg, Andreas Rossberg; +Cc: asai, caml-list

[-- Attachment #1: Type: text/html, Size: 357 bytes --]

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2022-06-16  9:14 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-06-15  1:59 [Caml-list] string_of_float (0.1 +. 0.2) Kenichi Asai
2022-06-15  6:22 ` Andreas Rossberg
2022-06-15  7:00   ` François Pottier
2022-06-15 14:07 ` Gabriel Scherer
2022-06-15 14:25   ` Daniel Bünzli
2022-06-16  1:45     ` Kenichi Asai
2022-06-16  6:24       ` Oleg
2022-06-16  9:01         ` Andreas Rossberg
2022-06-16  9:14           ` [Caml-list] unsubscribe Jean-Denis EIDEN JEAN-DENIS

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).