Rounding very large doubles 
Author Message
 Rounding very large doubles

How about this logic for rounding.
If I have a number 90,000,000,000,000.00 and round to 2 decimal places, it
will not cause any fuzz because double only have approx. 15 digits. Adding
0.005 will cause lost of precision in one of the digits before the decimal.

double rnd(double d_Value, long l_rndplaces)
{
   long long ll_Value;

   ll_Value = d_Value * pow(10, l_rndplaces+1);

   if (ll_Value > 0)
     ll_Value = ll_Value + 5;
   else
     ll_Value = ll_Value - 5;

   ll_Value = ll_Value/10;

   return(ll_Value * pow(10, -l_rndplaces));

Quote:
}

--



Fri, 24 Sep 2004 12:35:06 GMT  
 Rounding very large doubles

Quote:
> How about this logic for rounding.
> If I have a number 90,000,000,000,000.00 and round to 2 decimal places, it
> will not cause any fuzz because double only have approx. 15 digits. Adding
> 0.005 will cause lost of precision in one of the digits before the

decimal.

That depends on your floating point representation, and on how your system
(eg floating point hardware or software emulation) implements basic
operations.

Formally, you don't actually lose precision by adding two numbers together.
Precision is a property of floating point representations, and affects how
close
two values can be before they're rounded to the same thing.  What you're
really
describing is a change in the printed output.  Given that floating point
values are
not stored internally in base 10, the change you see depends on your
implementation.   If the next value above 9E13 that can be represented in
your floating point format is substantially greater than 9E13+0.005, you can
expect the result to be 9E13.  Otherwise, I'd expect the result to be the
next value above 9E13 that can be represented in your floating point format.
That will probably not be exactly equal to 9E13+0.005.

Quote:

> double rnd(double d_Value, long l_rndplaces)
> {
>    long long ll_Value;

>    ll_Value = d_Value * pow(10, l_rndplaces+1);

>    if (ll_Value > 0)
>      ll_Value = ll_Value + 5;
>    else
>      ll_Value = ll_Value - 5;

>    ll_Value = ll_Value/10;

>    return(ll_Value * pow(10, -l_rndplaces));
> }

The problem with all the above, is that it depends on what sort of values
you
really care about printing out accurately.  It also assumes implicitly that
you
have no rounding effects caused by multiplying by powers of 10 (which is
not true with most (all?) real world floating point formats, as behind the
scenes they work in binary).  Lastly, there is no real guarantee that a long
long
can handle the same range of values as a floating point variable, let
alone what you get by multiplying by a power of 10.

In general, if you care about this sort of thing (and most numerical
analysts
don't because they simply work within machine precision) then you will
need to design your software so that all possible values you have will
print out as you want them.  That is not exactly a trivial exercise, and
doesn't
give much more than aesthetic value in practice.
--



Sat, 25 Sep 2004 02:42:07 GMT  
 Rounding very large doubles

Quote:

> How about this logic for rounding.
> If I have a number 90,000,000,000,000.00 and round to 2 decimal
> places, it will not cause any fuzz because double only have approx.
> 15 digits. Adding 0.005 will cause lost of precision in one of the
> digits before the decimal.

The logic is wrong. 9.0E+13 is not exactly expressible in most
floating-point representations.  Whatever approximation to that
number you have managed to somehow produce will appear rounded
depending on the format you use to print it.  Doing your own
rounding correctly take considerable care; the code example you
gave will overflow unnecessarily, among other defects.
--



Sat, 25 Sep 2004 02:42:12 GMT  
 Rounding very large doubles

Quote:

> How about this logic for rounding.

The problem is not in this rounding operation.  It's in what you
expect its input to be capable to do for you.

Quote:
> If I have a number 90,000,000,000,000.00 and round to 2 decimal places, it
> will not cause any fuzz because double only have approx. 15 digits. Adding
> 0.005 will cause lost of precision in one of the digits before the decimal.

You're too close to the border of integers representable by doubles
for any routine like this to be able to work, in that range of inputs.
If you really wont to work with large numbers of dollars, with cent
accuracy, you should use 64bit ints that count cents, not doubles that
count dollars.  Or use non-atomic datatypes to represent amounts.

Quote:
> double rnd(double d_Value, long l_rndplaces)
> {
>    long long ll_Value;
>    ll_Value = d_Value * pow(10, l_rndplaces+1);

For any input values in the vicinity of your 90e12, and rndplaces = 2,
with a non-zero fraction, this operation will result in massive
rounding problems.  Those have already occured long before this
routine was called, so you're in GIGO mode of operation: garbage in,
garbage out.

On just about every modern hardware, floating point numbers generally
have problems representing decimal fractions.  As they saying has it:

        In computing, 10 times 0.1 is hardly ever 1.0

And if there are 14 nonzero digits in front of that 0.1, this only
becomes worse.
--

Even if all the snow were burnt, ashes would remain.
--



Sat, 25 Sep 2004 02:42:38 GMT  
 Rounding very large doubles

Quote:


> > How about this logic for rounding.
> > If I have a number 90,000,000,000,000.00 and round to 2 decimal
> > places, it will not cause any fuzz because double only have approx.
> > 15 digits. Adding 0.005 will cause lost of precision in one of the
> > digits before the decimal.

> The logic is wrong. 9.0E+13 is not exactly expressible in most
> floating-point representations.  

9.0E+13 requires 34 bits of mantissa to be exact in a binary format,
such as IEEE 754 and relatives, which I assume is the most popular fp
format now.  The IEEE double format has 53 bits of mantissa, more than
enough to exactly represent 9.0E+13.  It won't exactly fit in an IEEE
754 single format, however, but the OP was addressing doubles.

Thad
--



Sun, 26 Sep 2004 04:46:10 GMT  
 Rounding very large doubles
This code:

#include <math.h>
long double rnd(double d_Value, long l_rndplaces)
{
   long long ll_Value;

   ll_Value = d_Value * powl(10, l_rndplaces+1);

   if (ll_Value > 0)
     ll_Value = ll_Value + 5;
   else
     ll_Value = ll_Value - 5;

   ll_Value = ll_Value/10;

   return((long double)ll_Value * powl(10, -l_rndplaces));

Quote:
}

int main(void)
{
        long double d = 90000000000000.00L;
        long double rounded_d = rnd(d,2);
        xprintf("%28.4Lf\n",rounded_d);
        return 0;

Quote:
}

produces
         90000000000000.0000
when compiled with lcc-win32 under the windows OS. Note that I changed
"double" by "long double".

lcc-win32 uses full precision (64 bits) when in long double mode, and the
CPU is in 64 bit mode when executing floating point code.

Your function discovered a bug in my compiler system!  I was missing the
powl function, that returns a long double as per C99 standard.

That corrected, your code makes the following operation at the end of the
rnd function:

    ll_Value*powl(10,-lrndplaces)

powl will return a long double less than 1, that multiplied by a long long
will be cast first into a long long to make the multiplication. This makes
it instantly zero, by definition of an integer cast. Your function always
returned zero.

This problem doesn't appear using doubles because in lcc-win32 the long long
times double makes a cast of the long long into double, THEN makes the
multiplication. (Is that correct by the way? I do not know exactly). A cast
makes the whole thing clearer.

Other compilers and environments vary wildly, and, as stated in other
messages here, this code is highly sensitive to rounding errors. But your
algorithm is not unsound. It is numerically not the best of course, and it
requires full FPU precision to work. Remember that some compilers,
specifically Microsoft's, work in double precision (53 bits) instead of the
full 64 bits precision of the long double, actually long double is accepted
but is equivalent to double in their implementation.

Making long double equivalent of double is accepted by the standard, you
can't rely on all compilers having really implemented full precision.


Quote:
> How about this logic for rounding.
> If I have a number 90,000,000,000,000.00 and round to 2 decimal places, it
> will not cause any fuzz because double only have approx. 15 digits. Adding
> 0.005 will cause lost of precision in one of the digits before the
decimal.

> double rnd(double d_Value, long l_rndplaces)
> {
>    long long ll_Value;

>    ll_Value = d_Value * pow(10, l_rndplaces+1);

>    if (ll_Value > 0)
>      ll_Value = ll_Value + 5;
>    else
>      ll_Value = ll_Value - 5;

>    ll_Value = ll_Value/10;

>    return(ll_Value * pow(10, -l_rndplaces));
> }
> --


--



Sun, 26 Sep 2004 04:46:13 GMT  
 Rounding very large doubles

Quote:
> How about this logic for rounding.
> If I have a number 90,000,000,000,000.00 and round to 2 decimal places, it
> will not cause any fuzz because double only have approx. 15 digits. Adding
> 0.005 will cause lost of precision in one of the digits before the
decimal.

> double rnd(double d_Value, long l_rndplaces)
> {
>    long long ll_Value;

>    ll_Value = d_Value * pow(10, l_rndplaces+1);

>    if (ll_Value > 0)
>      ll_Value = ll_Value + 5;
>    else
>      ll_Value = ll_Value - 5;

>    ll_Value = ll_Value/10;

>    return(ll_Value * pow(10, -l_rndplaces));
> }
> --

Post Scriptum to my previous message.
1:
MSVC does produce the correct result, even in double precision without any
long doubles. (Version 6.2)
2:
gcc produces the same correct result too. (mingw gcc 2.95.2)
3:
borland (bcc 5.5) produces correct results too.
All this under windows.
4:
Under Linux RedHat, the same code above produces with gcc (egcs 2.91.66
19990314) the same correct result.


#include <math.h>
long double rnd(double d_Value, long l_rndplaces)
{
   long long ll_Value;

   ll_Value = d_Value * pow(10.0, l_rndplaces+1);
   if (ll_Value > 0)     ll_Value = ll_Value + 5;
   else  ll_Value = ll_Value - 5;
   ll_Value = ll_Value/10;
   return(ll_Value * pow(10.0, -l_rndplaces));

Quote:
}

int main(void)
{
        double d = 90000000000000.00L;
        double rounded_d = rnd(d,2);
        printf("%28.4f\n",rounded_d);
        return 0;
Quote:
}


         90000000000000.0000
/home/root

I would say that code is fairly portable and quite OK... It will produce the
same results in 6 different compilers under two different OSes
--



Sun, 26 Sep 2004 04:46:21 GMT  
 Rounding very large doubles

[...]

Quote:
> The logic is wrong. 9.0E+13 is not exactly expressible in most
> floating-point representations.  

It's not really _that_ bad.  If I may take the liberty to assume that
"most" floating-point representations these days will have IEEE double
precision numbers under the hood of C's "double" datatype, then 9.E13
is well within the range of exactly representable integer values.
That ranges goes all the way up to FLT_RADIX^DBL_MANT_DIG, or 2^53,
which happens to be about 9.007e15. That's a comfortable 2 decades
above 9.0e13.

OTOH, this makes very clear that the number he calculates in the first
steps of his rounding routine, (9e13+0.01)*100, or 9e15+1, is indeed
perilously close to the abyss.  Close enough that all kinds of
rounding problems will hit, and not only in the rounding routine
itself, but almost certainly in other steps before it was called, too.
You just can't work on dollar amounts of this size in floating-point
and still expect the cents to behave even remotely sensibly.

I.e. most likely the input is garbage, and so no amount of cleverness
will avoid garbage to be the output.

--

Even if all the snow were burnt, ashes would remain.
--



Sun, 26 Sep 2004 04:46:30 GMT  
 Rounding very large doubles
Remember that my goal is to have 90E12 to come out as 90E12 after the
rounding. To round to 5th decimal point, I would have to scale the number by
additional 4 digits, get the integer part using modf, and then scale it back
by 4 places. To scale by 4 digits requires at least 16 digits of precision.
Therefore it causes a much bigger lost of precision. I felt that by using
"long long", I will reduce the amount of lost precision. Does this make
sense?


Quote:
> How about this logic for rounding.
> If I have a number 90,000,000,000,000.00 and round to 2 decimal places, it
> will not cause any fuzz because double only have approx. 15 digits. Adding
> 0.005 will cause lost of precision in one of the digits before the
decimal.

> double rnd(double d_Value, long l_rndplaces)
> {
>    long long ll_Value;

>    ll_Value = d_Value * pow(10, l_rndplaces+1);

>    if (ll_Value > 0)
>      ll_Value = ll_Value + 5;
>    else
>      ll_Value = ll_Value - 5;

>    ll_Value = ll_Value/10;

>    return(ll_Value * pow(10, -l_rndplaces));
> }
> --


--



Mon, 27 Sep 2004 14:51:26 GMT  
 Rounding very large doubles

Quote:

> Remember that my goal is to have 90E12 to come out as 90E12 after the
> rounding.

I think we understood your goal pretty well.  But that doesn't make
that goal more sensible, see?  Assuming typical machine data formats,
which in itself already is kind of a no-no for a newsgroup supposed to
deal with the platform _in_dependent aspects of C, the flaw is in the
expectation that what you want to do really can be done in the first
place.

Quote:
> I felt that by using "long long", I will reduce the amount of lost
> precision.  Does this make sense?

No.  Because the precision will be lost anyway, in any particular
example where it is in actual danger of being lost.  Actually, your
code is worse than the straightforward

{
double shift = pow(10, l_rndplaces);

return (floor(d_Value * shift + 0.5) / shift);

Quote:
}

This needs only one additional *bit* of headroom to still work,
instead of a whole decimal digit.  For your original example of
rouding to two digits, your code will actually fail because it
computes

        90e12 * pow(10, 2+1)
        = 9e13 * 1e3
        = 9e16

And that's almost ten times as large as the largest integer value
exactly representable in an IEEE standard double precision number.

Another implementation closer to your line of thought would be

{
  double shift = pow(10, l_rndplaces);
  long long ll = d_Value * 2 * shift;

  ll += 1;

  ll /= 2;

  return ll * shift;

Quote:
}      

--

Even if all the snow were burnt, ashes would remain.
--



Fri, 01 Oct 2004 03:41:36 GMT  
 
 [ 10 post ] 

 Relevant Pages 

1. how to round a float or double in C++

2. rounding doubles

3. Rounding Double

4. 'double' rounding errors

5. How to round off a double?

6. Rounding of floats and doubles

7. Rounding a double

8. How to round a double?

9. rounding floats and doubles

10. Convert double to string without rounding

11. Rounding a double

12. Rounding routine for doubles

 

 
Powered by phpBB® Forum Software