简体   繁体   中英

Trying to recreate printf's behaviour with doubles and given precisions (rounding) and have a question about handling big numbers

I'm trying to recreate printf and I'm currently trying to find a way to handle the conversion specifiers that deal with floats. More specifically: I'm trying to round doubles at a specific decimal place. Now I have the following code:

double  ft_round(double value, int precision)
{
    long long int power;
    long long int result;

    power = ft_power(10, precision);
    result = (long long int) (value * power);
    return ((double)result / power);
}

Which works for relatively small numbers (I haven't quite figured out whether printf compensates for truncation and rounding errors caused by it but that's another story). However, if I try a large number like

-154584942443242549.213565124235

I get -922337203685.4775391 as output, whereas printf itself gives me -154584942443242560.0000000 (precision for both outputs is 7).

Both aren't exactly the output I was expecting but I'm wondering if you can help me figure out how I can make my idea for rounding work with larger numbers.

My question is basically twofold:

  1. What exactly is happening in this case, both with my code and printf itself, that causes this output? (I'm pretty new to programming, sorry if it's a dumb question)
  2. Do you guys have any tips on how to make my code capable of handling these bigger numbers?

PS I know there are libraries and such to do the rounding but I'm looking for a reinventing-the-wheel type of answer here, just FYI!

You can't round to a particular decimal precision with binary floating point arithmetic. It's not just possible. At small magnitudes, the errors are small enough that you can still get the right answer, but in general it doesn't work.

The only way to round a floating point number as decimal is to do all the arithmetic in decimal. Basically you start with the mantissa, converting it to decimal like an integer, then scale it by powers of 2 (the exponent) using decimal arithmetic. The amount of (decimal) precision you need to keep at each step is roughly (just a bit over) the final decimal precision you want. If you want an exact result, though, it's on the order of the base-2 exponent range (ie very large).

Typically rather than using base 10, implementations will use a base that's some large power of 10, since it's equivalent to work with but much faster. 1000000000 is a nice base because it fits in 32 bits and lets you treat your decimal representation as an array of 32-bit ints (comparable to how BCD lets you treat decimal representations as arrays of 4-bit nibbles).

My implementation in musl is dense but demonstrates this approach near-optimally and may be informative.

  1. What exactly is happening in this case, both with my code and printf itself, that causes this output?

Overflow. Either ft_power(10, precision) exceeds LLONG_MAX and/or value * power > LLONG_MAX .

  1. Do you guys have any tips on how to make my code capable of handling these bigger numbers?

Set aside various int types to do rounding/truncation. Use FP routines like round() , nearby() , etc.

double ft_round(double value, int precision) {
    // Use a re-coded `ft_power()` that computes/returns `double`
    double pwr = ft_power(10, precision);
    return round(value * pwr)/pwr;
}

As well mentioned in this answer , floating point numbers have binary characteristics as well as finite precision. Using only double will extend the range of acceptable behavior. With extreme precision , the value computed with this code be close yet potentially only near the desired result.

Using temporary wider math will extend the acceptable range.

double ft_round(double value, int precision) {
    double pwr = ft_power(10, precision);
    return (double) (roundl((long double) value * pwr)/pwr);
}

I haven't quite figured out whether printf compensates for truncation and rounding errors caused by it but that's another story

See Printf width specifier to maintain precision of floating-point value to print FP with enough precision.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM