Live data from Hacker News

Python rounds float values by converting them to string and then back

github.com

131–140 of 152 posts

Re: Python rounds float values by converting them to string and then back

#131
post #85
post #83

Earlier quoted context omitted.

Using native x86_64 instructions isn't portable.

Why not have optimized versions that use native instructions when available, and then fall back to the portable version when they are not?

I suspect different behaviors might be an issue in some tricky cases.

Re: Python rounds float values by converting them to string and then back

#132
post #6

Apples libc used to shell-out to perl in a function: https://github.com/Apple-FOSS-Mirror/Libc/blob/2ca2ae7464771...

What if Perl uses libc ?????

It does on my system:

  $ otool -L /usr/bin/perl
  /usr/bin/perl:
      /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1663.0.0)
      /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.0.0)

Re: Python rounds float values by converting them to string and then back

#133

Am I the only one grimacing at the lack of curlies around if/else scope? Just good practice!

In case you didn't know, Python doesn't use curly braces to block scope. They're pretty much only used for dictionaries and sets.

In case you didn't know, we're talking about c code.

Re: Python rounds float values by converting them to string and then back

#134
post #14

Seems to be one of the best ways to go about it. From the comment in protobuf source (which does the same thing as Python), mentioned in the Twitter thread: (...) An arguably better strategy would be to use the algorithm described in "How to Print Floating-Point Numbers Accurately" by Steele & White, e.g. as implemented by David M. Gay's dtoa(). It turns out, however, that the following implementation is about as fas…

Yep, I'd be curious what any better alternative is.

Consider that a float's internal representation is in base 2, and you're trying to round in base 10. Even if you didn't use a string, I'd assume you'd have to create an array of ints that contain base 10 digits in order to do the rounding, unless there are some weird math tricks that can be employed that can avoid you having to process all base 2 digits. And an array of ints isn't all that computationally different from a string.

Re: Python rounds float values by converting them to string and then back

#135

Earlier quoted context omitted.

Fun fact: floor(x + 0.5) rounds 0.49999997 to 1.0 (this is 32 bit floats, the same principle applies to 64). Most libraries have slower than ideal round conversion because of historical dross; modern chips have a very fast SIMD round instruction but its behavior doesn't exactly match libc round. See https://github.com/rust-lang/rust/issues/55107 for a deeper discussion.

I just tried this on Python3 on a 64-bit x86 system: import math x = 0.49999999999999994 print(x-0.5) print(math.floor(x+0.5)) I got these printouts: -5.551115123125783e-17 1 So yes, something less than 1/2, with 1/2 added to it, has a floor of 1 in floating point math. Yet another reminder that floating point calculations are approximations , and not exact.

In Julia it's easy to see the difference between promoting to full number tower and sticking to 64 floats (ed: note difference in result 0 vs 1):

  julia> using Benchmarktools

  julia> @btime floor(0.49999999999999997+0.5)
0.027 ns (0 allocations: 0 bytes)

1.0

  julia> @btime floor(0.49999999999999997+BigFloat(0.5))
280.594 ns (6 allocations: 336 bytes)

0.0

  julia> @code_native floor(0.49999999999999997+BigFloat(0.5))
    .text
  ; ┌ @ floatfuncs.jl:152 within `floor'
    subq    $24, %rsp
    movq    %rsi, 16(%rsp)
    movq    (%rsi), %rax
  ; │┌ @ floatfuncs.jl:152 within `#floor#543'
    movq    %rax, (%rsp)
    movabsq    $jl_system_image_data, %rax
    movq    %rax, 8(%rsp)
    movabsq    $japi1_round_16005, %rax
    movabsq    $jl_system_image_data, %rdi
    movq    %rsp, %rsi
    movl    $2, %edx
    callq    *%rax
  ; │└
    addq    $24, %rsp
    retq
    nopw    %cs:(%rax,%rax)
  ; └

  julia> @code_native floor(0.49999999999999997+0.5)
    .text
  ; ┌ @ floatfuncs.jl:152 within `floor'
  ; │┌ @ floatfuncs.jl:152 within `#floor#543'
  ; ││┌ @ floatfuncs.jl:152 within `round'
    vroundsd    $9, %xmm0, %xmm0, %xmm0
  ; │└└
    retq
    nopw    (%rax,%rax)
  ; └

Re: Python rounds float values by converting them to string and then back

#136
post #121

Earlier quoted context omitted.

>Seems to be one of the best ways to go about it. The C/C++ standards do not require formatting to round correctly or even be portable. I recently had an issue where a developer used this method to round floats for display, and there were differences on PC and on Mac. It literally rounded something like 18.25 to 18.2 on one platform and 18.3 on the other. This led to all sorts of other bugs as some parts of the progr…

IEEE754 defines 5 rounding modes. This one sounds like nearest, ties to even. Not all decimal values are representable as float. Depending on if you compile for x87 (80-bit internal repreaentation) or SSE (64-bit) you might get slightly different results.

I'm well aware of that, having written at length about floating-point tricks, numerical issues, etc.

The issue here is you don't know what a library that formats a float does, and is the function is not specified clearly (as in C/C++), you have zero way of knowing what you will get.

Thus I said to do it yourself, using proper numerics.

Re: Python rounds float values by converting them to string and then back

#137
post #57

Earlier quoted context omitted.

>Seems to be one of the best ways to go about it. The C/C++ standards do not require formatting to round correctly or even be portable. I recently had an issue where a developer used this method to round floats for display, and there were differences on PC and on Mac. It literally rounded something like 18.25 to 18.2 on one platform and 18.3 on the other. This led to all sorts of other bugs as some parts of the progr…

> The C/C++ standards do not require formatting to round correctly or even be portable. The linked-to method uses PyOS_snprintf(). Its documentation at https://docs.python.org/3/c-api/conversion.html says: """PyOS_snprintf() and PyOS_vsnprintf() wrap the Standard C library functions snprintf() and vsnprintf(). Their purpose is to guarantee consistent behavior in corner cases, which the Standard C functions do not."""

And those functions in C/C++ are not specified.

The python wrapper also does not specify in this case, so you should not use them for rounding, or you will have the same problem. No where on that page does it specify proper rounding will be cross platform.

Simply do it with floats. There are perfectly good, numerically stable, fast rounding methods, that avoid all this nonsense.

Re: Python rounds float values by converting them to string and then back

#138
post #68

Earlier quoted context omitted.

Would be pretty awesome if Perl called wordexp(3) somewhere along this code path

I seem to recall that perl used to shell out to /bin/sh for some related task...

Yep, still there in the latest perl5: Perl_start_glob https://github.com/Perl/perl5/blob/blead/doio.c

It's somewhat messier than I remember, because it uses csh as the first choice and falls back to sh.

Re: Python rounds float values by converting them to string and then back

#139

Earlier quoted context omitted.

response was to the fact that the comment said the method of format strings was one of “the best ways to go about it” It’s obvious that PyOS_snprintf is not a standard library function

Sure, PyOS_snprintf isn't a standard library function, but it's a thin wrapper to snprintf, which is. Python/mysnprintf.c is the location of the: snprintf() wrappers. If the platform has vsnprintf, we use it, else we emulate it in a half-hearted way. Even if the platform has it, we wrap it because platforms differ in what vsnprintf does in case the buffer is too small: It mentions that one corner cases what happens w…

"correctly rounded" is implementation defined is the problem. You cannot do it portably, and you cannot query it portably. As such, different platforms, compilers, etc do it differently. Thus using formatting for rounding is inconsistent.

Here's [1] where you can query the current floating-point environment in C: "Specifics about this type depend on the library implementation".

Here's [2] where you can set some rounding modes in C++: "Additional rounding modes may be supported by an implementation.". Note this does not have by default bankers rounding which is used to make many scientific calculations more stable (lowers errors and drift in accumulated calculations). Many platforms do this by default, but it's not in the standard.

You can chase down this rabbit hole. I (and several others) did during the issue on the last project, and got to where it was well-known in numerics circles that this is not a well-defined process in C/C++. If it were, printing and parsing should round-trip, and it does not before a recent C++ addition, and now it only is guaranteed in a special case.

[1] http://www.cplusplus.com/reference/cfenv/fenv_t/

[2] https://en.cppreference.com/w/cpp/numeric/fenv/FE_round

Re: Python rounds float values by converting them to string and then back

#140

Earlier quoted context omitted.

Sure, PyOS_snprintf isn't a standard library function, but it's a thin wrapper to snprintf, which is. Python/mysnprintf.c is the location of the: snprintf() wrappers. If the platform has vsnprintf, we use it, else we emulate it in a half-hearted way. Even if the platform has it, we wrap it because platforms differ in what vsnprintf does in case the buffer is too small: It mentions that one corner cases what happens w…

"correctly rounded" is implementation defined is the problem. You cannot do it portably, and you cannot query it portably. As such, different platforms, compilers, etc do it differently. Thus using formatting for rounding is inconsistent. Here's [1] where you can query the current floating-point environment in C: "Specifics about this type depend on the library implementation". Here's [2] where you can set some round…

Thank you for the clarification!
Post reply on HN