Rounding very large doubles
Author Message Rounding very large doubles

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:
> 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.

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:

> 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:

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:

> > 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.

--

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:
> 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:
> 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,
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:
> 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:
}

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

 Page 1 of 1 [ 10 post ]

Relevant Pages