Rounding very large doubles
Author 
Message 
AG #1 / 10

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 


Rob #2 / 10

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 


Douglas A. Gwy #3 / 10

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


HansBernhard Broeke #4 / 10

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 nonatomic 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 nonzero 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 


Thad Smit #5 / 10

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


jacob navi #6 / 10

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 lccwin32 under the windows OS. Note that I changed "double" by "long double". lccwin32 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 lccwin32 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 


jacob navi #7 / 10

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 


HansBernhard Broeke #8 / 10

Rounding very large doubles
[...] Quote: > The logic is wrong. 9.0E+13 is not exactly expressible in most > floatingpoint representations.
It's not really _that_ bad. If I may take the liberty to assume that "most" floatingpoint 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 floatingpoint 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 


AG #9 / 10

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 


HansBernhard Broeke #10 / 10

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


