* [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
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