Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

string_of_float returns -NaN for most numbers formatted by printf #16

Closed
timbertson opened this issue Apr 20, 2017 · 5 comments
Closed

Comments

@timbertson
Copy link

I found this issue where yojson objects with timestamps (floats) were being serialized OK, but when deserialized they would came back as -NaN.

To test, I ran the following code:

    let float_test f : unit =
      let s = string_of_float f in
      let s_printf = Printf.sprintf "%f" f in
      Printf.printf "float=%f -> string_of_float=%s -> parsed=%f\n" f s (float_of_string s);
      Printf.printf "float=%f -> printf-string  =%s -> parsed=%f\n" f s_printf (float_of_string s_printf)
    in

    float_test 2.4;
    float_test 1493895831.871153;
    let attempts = ref 500 in
    while !attempts > 0 do
      decr attempts;
      let f = Random.float 999999999999999.0 in
      float_test f;
      float_test f
    done;

I got the following output in mirage-ukvm, with ocaml-freestanding 0.2.1:

float=2.400000 -> string_of_float=2.4 -> parsed=2.400000
float=2.400000 -> printf-string  =2.400000 -> parsed=2.400000
float=1493895831.871153 -> string_of_float=1493895831.87 -> parsed=1493895831.870000
float=1493895831.871153 -> printf-string  =1493895831.871153 -> parsed=-nan
float=481705019586434.312500 -> string_of_float=4.81705019586e+14 -> parsed=481705019586000.000000
float=481705019586434.312500 -> printf-string  =481705019586434.312500 -> parsed=-nan
float=481705019586434.312500 -> string_of_float=4.81705019586e+14 -> parsed=481705019586000.000000
float=481705019586434.312500 -> printf-string  =481705019586434.312500 -> parsed=-nan
float=554380010410619.812500 -> string_of_float=5.54380010411e+14 -> parsed=554380010411000.000000
float=554380010410619.812500 -> printf-string  =554380010410619.812500 -> parsed=-nan
float=554380010410619.812500 -> string_of_float=5.54380010411e+14 -> parsed=554380010411000.000000
float=554380010410619.812500 -> printf-string  =554380010410619.812500 -> parsed=-nan

i.e. when using string_of_float the number correctly roundtrips to a string and back, but using printf's float formatting it consistently gets parsed as -NaN.
2.4 was a random simple case I tried, and funnily enough it works both ways - perhaps the bug only affects numbers with a large number of decimal places?

I remember float_of_string used to not work at all in xen because strtod was missing(mirage/mirage-platform#118), so I'm guessing this is probably a bug in ocaml-freestanding's strtod?

@mato
Copy link
Contributor

mato commented Apr 24, 2017

That's possible; the strtod implementation in ocaml-freestanding comes from the standalone dtoa.c by David M. Gay, I've not really tested it much. If you could provide a test case in C that reproduces the problem I'll look into it.

(Aside: I wanted to use the musl implementation of strtod but that depends on dragging in more of stdio, so using the standalone dtoa.c was the more expedient solution at the time. Feel free to take this path instead...)

@hannesm
Copy link
Member

hannesm commented Apr 24, 2017

FWIW the dtoa.c in mirage-xen and here are identical.. (modulo -Wmaybe-uninitialized)

@timbertson
Copy link
Author

timbertson commented Apr 24, 2017

To clarify: I never got this far on xen since it lacked strtod last time I tried, so this bug could definitely be there too.

I whipped up a quick C version with just one problematic number. Results were... baffling:

#include <stdio.h>
#include "dtoa.c"

#define buflen 20

int main() {
	double f = 1493895831.871153;
	char buf[buflen];
	snprintf(buf, buflen, "%F", f);

	double f_parsed = strtod(buf, NULL);
	printf("double = %F; str = %s; double_of_str = %F\n", f, buf, f_parsed);
}

Compiling the above with gcc -O2 -std=c99 -Wall -Wno-parentheses -Werror float_test.c gives:

double = 1493895831.871153; str = 1493895831.871153; double_of_str = 1493895831.871153

But without -O2:

double = 1493895831.871153; str = 1493895831.871153; double_of_str = -NAN

And if I change the snprintf line to the seemingly-identical:

	snprintf(buf, buflen, "%s", "1493895831.871153");

... then with or without -O2, I get:

double = 1493895831.871153; str = 1493895831.871153; double_of_str = -NAN

This seems consistent on my machine, but I'd be interested if others get the same results.

@mato
Copy link
Contributor

mato commented Apr 24, 2017

Interesting. I get exactly the same results as you on Debian 9 (gcc 6.3.0) but on Debian 8 (gcc 4.9.2) I get double_of_str = -NAN regardless of which optimization setting I use.

@mato
Copy link
Contributor

mato commented Nov 22, 2017

Fixed in #28.

@mato mato closed this as completed Nov 22, 2017
dinosaure pushed a commit to dinosaure/ocaml-freestanding that referenced this issue Mar 1, 2018
mirage-solo5.pc should require ocaml-freestanding
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants