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

Not getting the shortest decimal representation of 0.3 #199

Open
ShaiWavesAudio opened this issue Aug 4, 2021 · 9 comments
Open

Not getting the shortest decimal representation of 0.3 #199

ShaiWavesAudio opened this issue Aug 4, 2021 · 9 comments

Comments

@ShaiWavesAudio
Copy link

I ran the following code:

    char buffer[128];
    const double num = 0.30000000;
    d2fixed_buffered(num, 4, buffer);
    std::cout  << buffer << "\n";

I expected 0.3 to be printed, instead I got 0.3000, surly 0.3000 is not the shortest decimal representation of this floating point number.
What am I missing?
What is the correct way to get the shortest representation?
Thnaks,
Shai

@StephanTLavavej
Copy link
Contributor

You want d2s_buffered() for Ulf's Ryu shortest-round trip algorithm:

ryu/ryu/d2s.c

Line 498 in 6f85836

void d2s_buffered(double f, char* result) {

d2fixed_buffered() implements Ulf's Ryu Printf algorithm which is given a precision for fixed notation (the precision is the number of digits after the decimal point). Passing a precision of 4 is like calling printf("%.4f", num); in C.

@ShaiWavesAudio
Copy link
Author

Thanks Stephan!

calling

    char buff[64];
    d2s_buffered(0.3, buff);
    std::cout << buff << "\n";

prints:
3E-1

which is:
A) not the shortest representation - "0.3" is shorter
B) not the fixed representation I wanted

What should I call to get the shortest fixed representation i.e. "0.3" ?

@StephanTLavavej
Copy link
Contributor

This repo doesn't generate shortest fixed notation. However, if you're using C++, you can use C++17 <charconv> - I did the work in MSVC to adapt Ulf's code.

If you call std::to_chars() without a chars_format argument, that's "plain shortest" mode, where it will emit the shortest output between fixed and scientific, preferring fixed as a tiebreaker.

C:\Temp>type meow.cpp
#include <charconv>
#include <cstdio>
#include <iterator>
#include <system_error>
using namespace std;

void print_shortest(const double val) {
    char buf[24]; // worst case: "-1.2345678901234567e-100"
    const auto result = to_chars(buf, end(buf), val);
    if (result.ec == errc{}) {
        printf("%.*s\n", static_cast<int>(result.ptr - buf), buf);
    } else {
        printf("FAIL!\n");
    }
}

int main() {
    print_shortest(0.00003);
    print_shortest(0.0003);
    print_shortest(0.003);
    print_shortest(0.03);
    print_shortest(0.3);
    print_shortest(3);
    print_shortest(30);
    print_shortest(300);
    print_shortest(3000);
    print_shortest(30000);
    print_shortest(300000);
    print_shortest(3000000);
}
C:\Temp>cl /EHsc /nologo /W4 /std:c++17 meow.cpp
meow.cpp

C:\Temp>meow
3e-05
3e-04
0.003
0.03
0.3
3
30
300
3000
30000
3e+05
3e+06

If you always want fixed notation (but otherwise want shortest round-trip), passing chars_format::fixed will select that. (This runs Ryu, then adapts the output to fixed notation - to follow C++'s rules, sometimes it needs to use Ryu Printf instead.) Note that this will be shortest, in the sense of the fewest digits after the decimal point, but it is not necessarily short - large integers will have tons of digits (and no decimal part), very small integers will begin with 0.0000000 before ending with as few non-zero digits as needed to capture all of the info.

MSVC's STL supports both the MSVC and Clang compilers on Windows; if you're targeting another platform, you'll need to ask your Standard Library implementers (I am unsure of their exact status).

@newbie-02
Copy link

hello folks,

beg your pardon for this silly? question,
I tried the code from @StephanTLavavej with different compilers on linux (Kali, debian), best results where from icpx and g++ with errors like:

test.c:9:52: error: call of overloaded ‘to_chars(char [24], char*, const double&)’ is ambiguous
9 | const auto result = to_chars(buf, end(buf), val);

acc.

https://stackoverflow.com/questions/63963961/what-is-the-correct-way-to-call-stdto-chars

it might be less a fail of me but a shortcoming in gcc, g++, libc++ or libstdc++?

I'm not! an experienced coder, 'script kiddie' at best, but would like to - try to - use ryu in another project, gnumeric, and for that I need a 'startpoint', a template, a working call to the ryu conversion functions from another piece of code. I had hope to find that with above path but 'm stuck again. I kindly ask for any help ... :-)

Best Regards,

b.

@StephanTLavavej
Copy link
Contributor

@newbie-02 Both Clang's libc++ and GCC's libstdc++ now support floating-point to_chars() powered by Ryu. Clang's implementation was ported by @mordante from MSVC, which in turn was derived from Ulf's repo here, and is available in Clang trunk (should be part of Clang 14). GCC's implementation shipped in GCC 11, see their release notes. Example on Compiler Explorer: https://godbolt.org/z/f8Ms36qxc Note that you must enable -std=c++17 for this feature.

@newbie-02
Copy link

@StephanTLavavej:

thank you for that hint, am I right that is support for g++ / clang++ / C++? If yes are there any means to make it available for 'pure C'?

Got something up and running, see recipe below to evtl. save time for others who search for similar.

And an additional question, 'long double to string' should be supported by generic128.h or similar, can someone provide a working example how to achieve that? e.g. 'long double x = 77.4L;', now convert into a SRT-string. would be very nice, I waste too much time in searching / trial and error. ( I need real long doubles, for the moment 80-bit x87 figures, not! the substitution partly used by Microsoft ( and valgrind? ): long double = double.

A basic recipe to get converting up and running ( debian based linux, gcc available ):

  • fetch ryu from git,
  • in folder ryu/ryu fire 'make',
  • place produced 'libryu.a' in a directory accessible for your linker,
    or place it in a subdir e.g. 'ryu' of your project and add -L ./ryu to your compiler call,
  • place 'ryu.h' in a directory of your header search path, add '#include <ryu.h>' to your includes,
    or place it in a subdir e.g. 'ryu' of your project and add '#include "ryu/ryu.h" ' to your includes,
  • accompany the directories with copies of README and the LICENSES found in ryu-root,
  • compile your program with the -L directive if necessary, and with '-lryu',
  • use e.g.
    double xd = 77.4; 
    char btxt[45]; 
    sprintf( btxt, "%s", d2s( xd ) );   

in your program to get sthg. like '7.74E1' in btxt.
( above may be altered / improved by any experienced dev. )

@newbie-02
Copy link

howto script to get 'long' and 'generic' stuff to work: #207 (comment)

@newbie-02
Copy link

hello @ShaiWavesAudio,
if you or others are still interested in other output formats pls. have a look at #211 (comment) and check if the patched versions there solve your problem or enable adapting to your needs by yourself. feedback welcome.

@newbie-02
Copy link

hello@StephanTLavavej,
I tried to adapt ryu's output for other format wishes, and have now seen that there already have been some discussions about that topic, and you often contributed. I placed my code in issue #211 . could you possibly have a look and tell me if:
A.) I re-invented the wheel? or
B.) I produced bullshit? or
C.) that could be useful?
would appreciate very much and TIA for any help / comments.

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