jk-jeon / fp

IEEE-754 binary-to-decimal and decimal-to-binary conversion library
40 stars 5 forks source link

Looking for ftoa examples #9

Closed david-bouyssie closed 2 years ago

david-bouyssie commented 2 years ago

Hi there!

I'm looking for ftoa examples similar to the function described in this article: https://www.geeksforgeeks.org/convert-floating-point-number-string/

Here is the function prototype taken from this article:

/**
Converts a floating-point number to a string with a given number of digits considered after the point.
\param n          --> Input Number
\param res[]      --> Array where output string to be stored
\param afterpoint --> Number of digits to be considered after the point.
*/
void ftoa(float n, char* res, int afterpoint);

I'm able to use the provided implementation but I was wondering how I could achieve the same thing with your fp library or more directly with dragonbox. My need would be of course to use the fastest existing implementation.

Any help is welcome, thanks!

jk-jeon commented 2 years ago

First of all, I should say that this library is not yet in a usable state, and it is not in development right now as I cannot dedicate enough time. The repo for the Dragonbox algorithm, on the other hand, is relatively mature and well-maintained, so have a look there if you are interested. The Usage Example section contains an example usage.

Now, I am not sure if you are aware of the following or not, but let me assume you don't as you anyway asked me for help. If you are in fact aware of these, please forgive me for giving you boring duplicate information😅.

Note that it does not make a lot of sense for Dragonbox (and other similar algorithms like Grisu, Ryu, SwiftDtoa, Schubfach, etc.) to be given with a desired precision from the user, because finding out the natural precision is more or less what these algorithms' goal is. By the way, by precision I mean the number of significand digits, not the number of digits in the fractional part, and by the natural precision I mean the minimum precision that is required to reliably distinguish the input from other floating-point numbers of the same type. For example, for 1.0 you only need one digit to distinguish it from other double's, but for 1.2 you need two digits. You may have a look at the Introduction section of the Dragonbox repo (or maybe my paper) if you are not familiar with this. On the other hand, Ryu-printf is an algorithm that correctly produces digits for any given precision. You may have a look at the implementation by the algorithm author here (the functiond2fixed and friends). Or this repo also contains a different implementation of the same algorithm.

Note that the algorithm you showed to me actually doesn't quite work. For example, (int)n can overflow (which is UB), and when afterpoint is too large (int)fpart can also overflow. And of course replacing (int) with std::round does not solve the problem, as it will just return a nonsensical answer. Even when they do not overflow, there will be loss of precision since floating-point math almost always rounds the result and successive rounding of a series of computation can result in an incorrect rounding of the end-result. I mean, it will work for certain inputs, but figuring out when it will give you the precisely rounded answer will require a careful and nontrivial analysis. Well, of course if you can afford some small amount of error, it might be okay, but usually the best precision is a contract that a generic ftoa function promises. And providing the correctly rounded result is actually quite a demanding task, which is why a typical implementation of printf is far slower than what is written in the page you linked.

david-bouyssie commented 2 years ago

Thank you for the very detailed answer, this is very informative. And sorry if I was a bit terse in my request.

Actually my use case is to loose precision, as I want to write a lot of numbers in an output file, but with a fixed number of decimals. This is why I don't want to let the library determine itself the the natural precision of the numbers. I don't care about roundtrip precision issues, which can be a problem I agree in other circumstances. However, I care about obtaining the correctly rounded result. What I'm looking for is an efficient alternative to for instance sprintf(buf, "%.3f", xxx)

Fortunately, I just found another implementation of Dragonbox in Rust: https://github.com/Alexhuszagh/rust-lexical/blob/main/lexical-write-float/docs/Algorithm.md

It has interesting options matching my requirements (for instance it can be configured with a max_significant_digits and a round_mode): https://github.com/alexhuszagh/rust-lexical/#options-api https://docs.rs/lexical/6.0.1/lexical/struct.WriteFloatOptions.html

So I think I will investigate this one instead.

Thank you for your advices.

jk-jeon commented 2 years ago

As far as I know, what rust-lexical does is,

  1. Run Dragonbox to obtain decimal significand of the natural precision, and
  2. If the desired precision is lower than the natural one, round the obtained result, and
  3. If the desired precision is higher than the natural one, append zeros.

And this will not give you the correctly rounded result. Appending trailing zeros will obviously not correctly round the result, so when the desired precision is higher, you will not get the correctly rounded result. Even when the desired precision is lower, I am not sure if it will give you the correctly rounded result. I guess it will fail sometimes, but I really haven't thought about it carefully.

So far, to have correct rounding for given precision, Ryu-printf or Grisu3 + fallback are ways I've heard of. Dragonbox with a fallback might be able to do that as well, but I'm currently not aware of how it can be actually done.