Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / General / April 2007

Tip: Looking for answers? Try searching our database.

question regarding java puzzlers #2

Thread view: 
vishist - 07 Apr 2007 21:02 GMT
Hi,
 With reference to the following code

public class Change {
   public static void main(String args[]) {
      System.out.println(2.00 - 1.10);
   }
}

It prints 0.899999999999999. Now, I'm unable to understand the
explanation given by Joshua Bloch. Can you please explain it in a
different manner.

thanks
vishist.
Arne Vajhøj - 07 Apr 2007 21:19 GMT
>  With reference to the following code
>
[quoted text clipped - 7 lines]
> explanation given by Joshua Bloch. Can you please explain it in a
> different manner.

All real numbers can not be represented exact in a fixed number
of bits because there are an infinite number of them.

Floating point are based on 1/2 1/4 1/8 and not on
1/10 1/100 1/1000, which mean that numbers that are
exact representable in a small number of decimals
are not necessarily exact representable in a computer
floating point.

So just because some math works in decimals does
not mean that the same math works in computer
floating points.

When you use float and double types you should
expect this type of noise.

If you can not live with that chose another
data type (BigDecimal would be obvious).

Arne
Stefan Ram - 07 Apr 2007 21:19 GMT
>Can you please explain it in a different manner.

 You have not stated what detail you want to have explained.

 For example, I might answer:

     »This is printed, because the print-expression
     "System.out.println(2.00 - 1.10)" is part of
     an expression statement. The semantics of the
     expression statement require the expression in
     front of the semicolon ";" to be evaluated, when
     the expression statement is executed - and as a
     side-effect of this evaluation a representation
     of the value is printed.«
vishist - 07 Apr 2007 21:23 GMT
>> Can you please explain it in a different manner.
>
[quoted text clipped - 10 lines]
>       side-effect of this evaluation a representation
>       of the value is printed.«

Hi Stefan,
          Sorry for confusing question. My question was that when the
above statement got executed, it prints 0.899999999999 instead of 0.9.
Joshua Bloch (book author) gave an explanation for this and I'm unable
to understand that explanation. So, wanted to clear that question from a
different perspective.

vishist.
Stefan Ram - 07 Apr 2007 21:44 GMT
>above statement got executed, it prints 0.899999999999 instead of 0.9.

 The floating point values are represented as binary floating
 point values. /Binary/ floating point values can not exactly
 represent all values, even if those value have a short exact
 representation using the decimal system.

 For example, the value of the decimal numeral »0.1« can not be
 represented as a finite binary fraction. So, when it is
 represented, the true representation with infinitely many
 binary digits needs to be abbreviated to finitely many digits.
 The error becomes visible, when such a value then is
 converted back to a decimal representation.

public class Main
{ public static void main( final java.lang.String[] args )
 { java.lang.System.out.println( new java.math.BigDecimal( 0.1 )); }}

0.1000000000000000055511151231257827021181583404541015625

 So, then, the next question to ask would be: Why does the
 following program not print
 »0.1000000000000000055511151231257827021181583404541015625«,
 but »0.1« instead?

public class Main
{ public static void main( final java.lang.String[] args )
 { java.lang.System.out.println( 0.1 ); }}

0.1
Patricia Shanahan - 07 Apr 2007 22:07 GMT
...
>           Sorry for confusing question. My question was that when the
> above statement got executed, it prints 0.899999999999 instead of 0.9.
> Joshua Bloch (book author) gave an explanation for this and I'm unable
> to understand that explanation. So, wanted to clear that question from a
> different perspective.

I understand exactly why the Java program gives the answer it does, and
know several different ways of explaining it, but I don't have the book
whose explanation you don't understand.

Could you give a brief summary of the explanation you don't like, and
what you don't like about it? That would help with picking an
explanation that may work better for you.

Patricia
vishist - 07 Apr 2007 22:26 GMT
> ...
>>           Sorry for confusing question. My question was that when the
[quoted text clipped - 12 lines]
>
> Patricia
"the problem is that the number 1.1 can't be represented exactly as a
double, so it is represented by the closest double value. The program
subtracts this value from 2. Unfortunately, the result of this
calculation is not the closest double value to 0.9. The shortest
representation of the resulting
double value is the hideous number that you see printed." is the
explanation that I got from the book and he went on to elaborate that
"not all decimals can be represented exactly using binary
floating-point."

I'm working on the previous responses for now. As for the following code
public class Main
{ public static void main( final java.lang.String[] args )
  { java.lang.System.out.println( 0.1 ); }}
printing 0.1 instead on
»0.1000000000000000055511151231257827021181583404541015625«, from the
API, it invokes Double.toString(double) and Float.toString(float). So,
the returned value is basically a string representation of the value. I
guess my explanation is correct?

vishist.
Stefan Ram - 07 Apr 2007 22:35 GMT
>"the problem is that the number 1.1 can't be represented exactly as a
>double, so it is represented by the closest double value. The program
[quoted text clipped - 3 lines]
>double value is the hideous number that you see printed." is the
>explanation that I got from the book

 I deem this better than my own explanations I have given on
 the subject so far, because he even manages to get the
 relevancy of the printing algorithm and everything else right
 using only few words.

>{ java.lang.System.out.println( 0.1 ); }}
>printing 0.1 instead on
>»0.1000000000000000055511151231257827021181583404541015625«, from the
>API, it invokes Double.toString(double) and Float.toString(float). So,
>the returned value is basically a string representation of the value. I
>guess my explanation is correct?

 The string representation would be
 »0.1000000000000000055511151231257827021181583404541015625«,
 however. As far as I understand it, Double.toString(double)
 takes special measures to use a short decimal representation
 (such as »0.1«) instead, if the binary representation is the
 binary representation closest (or even second-closest?) to it
 in order to produce »nice« representations.
Patricia Shanahan - 07 Apr 2007 22:58 GMT
...
>   The string representation would be
>   »0.1000000000000000055511151231257827021181583404541015625«,
[quoted text clipped - 3 lines]
>   binary representation closest (or even second-closest?) to it
>   in order to produce »nice« representations.
...

The converted value is the shortest decimal expansion that
Double.parseDouble(String) would round to
0.1000000000000000055511151231257827021181583404541015625.

The default string conversion for float and double maintains
reversibility: If x is a double number (not a NaN) then

x == Double.parseDouble(Double.toString(x))

The converted value can be equidistant between x and one of its
neighbors if the least significant bit of x is even, because the
parseDouble rounding would return x. It can never be closer to some
other double than to x.

Patricia
blmblm@myrealbox.com - 07 Apr 2007 23:23 GMT
> ...
> >   The string representation would be
[quoted text clipped - 9 lines]
> Double.parseDouble(String) would round to
> 0.1000000000000000055511151231257827021181583404541015625.

I had to think a little to come up with a plausible explanation of
why BigDecimal(0.1) should have so many significant digits -- more
than one would normally think of a 64-bit floating-point number as
being able to represent, no?

I'm guessing that the relevant BigDecimal constructor takes the
"double" closest to 0.1 and converts it to decimal with full
precision.  Huh.  I guess it makes a kind of sense, but I understand
why the documentation for BigDecimal recommends against using this
particular constructor.

> The default string conversion for float and double maintains
> reversibility: If x is a double number (not a NaN) then
[quoted text clipped - 5 lines]
> parseDouble rounding would return x. It can never be closer to some
> other double than to x.

Signature

B. L. Massingill
ObDisclaimer:  I don't speak for my employers; they return the favor.

Patricia Shanahan - 08 Apr 2007 00:48 GMT
>> ...
>>>   The string representation would be
[quoted text clipped - 14 lines]
> than one would normally think of a 64-bit floating-point number as
> being able to represent, no?

Although every binary fraction can be expressed as a decimal fraction,
decimal is less efficient than binary at representing binary fractions.

For example, 1/8, which has only one significant bit in binary, is 0.125
in decimal.

> I'm guessing that the relevant BigDecimal constructor takes the
> "double" closest to 0.1 and converts it to decimal with full
> precision.  Huh.  I guess it makes a kind of sense, but I understand
> why the documentation for BigDecimal recommends against using this
> particular constructor.

It's the compiler, not BigDecimal, that does the rounding. 0.1 is a
double literal, and will appear in the bytecode as the corresponding
double bit pattern, value
0.1000000000000000055511151231257827021181583404541015625

Patricia
blmblm@myrealbox.com - 09 Apr 2007 09:29 GMT
> >> ...
> >>>   The string representation would be
[quoted text clipped - 20 lines]
> For example, 1/8, which has only one significant bit in binary, is 0.125
> in decimal.

Yeah ....  Still feels wrong somehow to use that many significant
figures for something that in some sense is an approximation.  I'm
not explaining this especially well, but maybe the idea is coming
across a little?

> > I'm guessing that the relevant BigDecimal constructor takes the
> > "double" closest to 0.1 and converts it to decimal with full
[quoted text clipped - 6 lines]
> double bit pattern, value
> 0.1000000000000000055511151231257827021181583404541015625

I'm not sure what you mean here by rounding, and we might be
talking at cross purposes a little.  It does make sense, now that
you mention it, that the error (difference between actual value and
0.1) comes in when the compiler turns the 0.1 in the source code
into a double.  What I'm thinking, though, is that the BigDecimal
constructor must then be taking that double and turning it into
a decimal representation with more significant figures than seem
reasonable to me -- I mean, I understand where they come from, but
it seems a little wrong-headed to me to use an exact representation
for something that I think is better thought of as an approximation.

Signature

B. L. Massingill
ObDisclaimer:  I don't speak for my employers; they return the favor.

Patricia Shanahan - 09 Apr 2007 11:28 GMT
>>>> ...
>>>>>   The string representation would be
[quoted text clipped - 23 lines]
> not explaining this especially well, but maybe the idea is coming
> across a little?

The point of using BigDecimal for printing the exact values of doubles
is that the conversion from double to BigDecimal is never an
approximation.

> I'm not sure what you mean here by rounding, and we might be
> talking at cross purposes a little. ...

The results of double operations are defined in terms of two aspects:

1. What would be the exact result of this calculation, if we could store it?

2. Pick a representable value to store based on the answer to question 1.

"Rounding" is the process of reducing the nominal exact result to a round
number that can be stored.

In many cases the exact answer is not needed, and cannot be calculated.
The implementation just has to find out enough about it to get the
rounded answer right.

Patricia
Lew - 09 Apr 2007 13:25 GMT
blmblm@myrealbox.com wrote:
>> Yeah ....  Still feels wrong somehow to use that many significant
>> figures for something that in some sense is an approximation.  I'm
>> not explaining this especially well, but maybe the idea is coming
>> across a little?

Every floating point number is an exact representation of something.

Just not necessarily of the number you care about.

Every floating point number of the same type (not counting denorms) has the
same number of significant figures, 52 in the case of double.

Read:
<http://docs.sun.com/source/806-3568/ncg_goldberg.html>

Signature

Lew

blmblm@myrealbox.com - 10 Apr 2007 12:27 GMT
> blmblm@myrealbox.com wrote:
> >> Yeah ....  Still feels wrong somehow to use that many significant
> >> figures for something that in some sense is an approximation.  I'm
> >> not explaining this especially well, but maybe the idea is coming
> >> across a little?

Obviously it's not, and in the process I think I'm starting to sound
more clueless about floating-point representations and arithmetic
than I think I am.  

> Every floating point number is an exact representation of something.
>
> Just not necessarily of the number you care about.

Agreed.  I think the point I was trying to make is that each
floating-point number can be thought of as representing a range of
real numbers that includes its actual value.

> Every floating point number of the same type (not counting denorms) has the
> same number of significant figures, 52 in the case of double.

But those are binary "significant figures" (bits), not the same as
the decimal (base 10) significant figures I'm talking about.

> Read:
> <http://docs.sun.com/source/806-3568/ncg_goldberg.html>

<whine>Do I have to?</whine>  Yeah.  Standard reference, but helpful
to be reminded of, and probably someday I should print yet another
copy and finally actually read the whole thing.

Signature

B. L. Massingill
ObDisclaimer:  I don't speak for my employers; they return the favor.

Chris Smith - 22 Apr 2007 22:07 GMT
> > Every floating point number of the same type (not counting denorms) has the
> > same number of significant figures, 52 in the case of double.
>
> But those are binary "significant figures" (bits), not the same as
> the decimal (base 10) significant figures I'm talking about.

I'd add that "significant figures" is the wrong word here.  A double has
52 bits of precision in the representation of the mantissa, but doubles
don't have significant figures.  If one needs to keep track of
significant figures, then one needs to do it with a data type that does
so.

The point of BugDecimal, on the other hand, is to represent exact values
(or values that are rounded according to precisely defined rules).  Most
of the time, of course, it's incorrect to convert a double to a
BigDecimal; but on the few occasions where that is actually desired, it
is perfectly reasonable to assume that the programmer wants the exact
mathematical value of that double.  Significant figures don't enter into
it; the double has an exact value, and you're asking for it.  If you
want to round the BigDecimal to fewer digits, such as if you know that
the value is only accurate to some smaller number of significant
figures, then setScale does that, and has a version that lets you
specify a rounding mode.

There are a few reasons that it doesn't make sense for BigDecimal to
round by default; one is that no one asked it to round, and there's
simply no other easy way to get an exact decimal value from a double.  
The other, and more important, reason is that if you need to limit
precision, you'll need to limit it to something other than the precision
of the double data type.  If you are attempting to use doubles to
manipulate values that happen to have all or nearly all of the mantissa
bits significant, then you're using the wrong data type anyway.  
Assuming that's not the case, when you convert to a BigDecimal you'll
want to either work with intermediate values at full precision, or
convert them to an appropriate precision for your data, NOT for the data
type you happened to be using to represent them.

Signature

Chris Smith

Mark Thornton - 22 Apr 2007 22:17 GMT
>>> Every floating point number of the same type (not counting denorms) has the
>>> same number of significant figures, 52 in the case of double.
[quoted text clipped - 6 lines]
> significant figures, then one needs to do it with a data type that does
> so.

Isn't it 53 bits for normalised numbers (the leading 1 bit is implicit)?

Mark Thornton
Patricia Shanahan - 22 Apr 2007 22:47 GMT
>>>> Every floating point number of the same type (not counting denorms)
>>>> has the same number of significant figures, 52 in the case of double.
[quoted text clipped - 10 lines]
>
> Mark Thornton

and 52 bits or less for denormalised.

Patricia
blmblm@myrealbox.com - 10 Apr 2007 12:45 GMT
[ snip ]

> >> Although every binary fraction can be expressed as a decimal fraction,
> >> decimal is less efficient than binary at representing binary fractions.
[quoted text clipped - 10 lines]
> is that the conversion from double to BigDecimal is never an
> approximation.

At least we agree about that.  :-)?

> > I'm not sure what you mean here by rounding, and we might be
> > talking at cross purposes a little. ...
[quoted text clipped - 11 lines]
> The implementation just has to find out enough about it to get the
> rounded answer right.

And we agree here ....

Other comments in my other two replies just now (to Chris Uppal
and Lew).  

Signature

B. L. Massingill
ObDisclaimer:  I don't speak for my employers; they return the favor.

Chris Uppal - 09 Apr 2007 14:25 GMT
> What I'm thinking, though, is that the BigDecimal
> constructor must then be taking that double and turning it into
> a decimal representation with more significant figures than seem
> reasonable to me -- I mean, I understand where they come from, but
> it seems a little wrong-headed to me to use an exact representation
> for something that I think is better thought of as an approximation.

This, I think, is the heart of the problem.  You use the word "approximation",
but it's not clear what a value is an approximation /to/.  A big decimal,
seeing a double with exact value
   0.1000000000000000055511151231257827021181583404541015625
has no way of knowing whether that is an "approximation" to
   0.1
or to
   0.100000000000000005551115
or, indeed, to
   0.100000000000000005551115123125782702118158340454101562500000000001
So what is it going to choose ?

That goes double when you consider that BigDecimal's /job/ is the precise
representation of numerical values -- it would be inappropriate for it to make
information-loosing guesses about what the programmer "really meant".  If you
want to convert doubles to BigDecimals using different rules (which is not in
itself unreasonable), then some of the possible rules are pre-packed for you.
For instance, by using Double.toString(double), and the BigDecimal(String)
constructor, you can convert using the
shortest-sequence-of-decimal-digits-which-maps-to-the-same-floating-point-value
rule (as Patricia has mentioned).

I don't really think that the concept of "approximation" is appropriate here.
There is a sense in which floating point computation can be considered to be
"like" precise computation with a certain amount of random noise added, so that
(like real physical measurements), you only ever have an approximate number,
and -- equally importantly -- you don't know what the true number should be.
That picture is fine as an approximation (;-) but it doesn't really reflect the
semantics of floating point arithmetic.  The rules for Java FP are precise and
exact, down to the last bit -- there is no approximation, or error involved at
all[*].  If we programmers want to use floating point values to represent
something other than the specific set of rational numbers defined by the IEEE
format, then it is /our/ responsibility to implement whatever mapping we have
decided upon -- that mapping is not, and cannot be, the responsibility of a
fundamental library.  (Not to say that pre-packed facilities for common
mappings are not handy -- and in fact Java provides such things, but as
supplements to the fundamental operations, not as replacements for them.)

Incidentally, that's one way of resolving the "puzzle" that the value
   0.1000000000000000055511151231257827021181583404541015625
seems to take more bits than are available to represent it.  There is a
specific set of slightly less than 2**64 rational numbers which can be
represented as floating point.  Each of those is represented /exactly/, whereas
the others cannot be represented /at all/.  For instance the number
   0.1000000000000000055511151231257827021181583404541015626
cannot be represented in 64-bit IEEE floating point.  It doesn't take ~180 bits
to represent a 55-digit decimal value because most of those 10**55 values have
no representation.

   -- chris

[*] Actually some slop is allowed in the last bit for some operations under
some conditions, but that doesn't affect the issue here.
blmblm@myrealbox.com - 10 Apr 2007 12:42 GMT
> > What I'm thinking, though, is that the BigDecimal
> > constructor must then be taking that double and turning it into
[quoted text clipped - 14 lines]
>     0.100000000000000005551115123125782702118158340454101562500000000001
> So what is it going to choose ?

Yes, I thought about that, and you may be right that there's really no
sensible choice other than the one made by the BigDecimal constructor.
It still seems subtly wrong to me, but maybe not more so than any
other choice.

> That goes double

! (pun intended?)

> when you consider that BigDecimal's /job/ is the precise
> representation of numerical values -- it would be inappropriate for it to make
[quoted text clipped - 13 lines]
> That picture is fine as an approximation (;-) but it doesn't really reflect the
> semantics of floating point arithmetic.  

I think is close to what's underlying my vague sense of unease --
except for the part about "random noise".  As you say:

> The rules for Java FP are precise and
> exact, down to the last bit -- there is no approximation, or error involved at
> all[*].  

Well, if you add floating-point numbers of different magnitudes,
there is round-off error involved -- possibly a precise and
well-defined error, but error.

> If we programmers want to use floating point values to represent
> something other than the specific set of rational numbers defined by the IEEE
> format,

Rational numbers with some unusual properties under arithmetic
operations, no?  e.g., addition is not always associative.

Given that, I'm inclined to stick with my claim that it's more
sensible to regard floating-point numbers as approximations of
reals than as rationals, even though almost [*] every floating-point
number has associated with it an exact rational value.

[*] Excluding NaN and other "not number" values (+/- Inf?).

But I may still just not be looking at things from the most useful
or reasonable perspective.

> then it is /our/ responsibility to implement whatever mapping we have
> decided upon -- that mapping is not, and cannot be, the responsibility of a
[quoted text clipped - 17 lines]
> [*] Actually some slop is allowed in the last bit for some operations under
> some conditions, but that doesn't affect the issue here.

Signature

B. L. Massingill
ObDisclaimer:  I don't speak for my employers; they return the favor.

Patricia Shanahan - 10 Apr 2007 14:22 GMT
>>> What I'm thinking, though, is that the BigDecimal
>>> constructor must then be taking that double and turning it into
[quoted text clipped - 51 lines]
> there is round-off error involved -- possibly a precise and
> well-defined error, but error.

Indeed, but generally the objective is to keep the error to a minimum.
For the most frequent operations, the error is the absolute minimum
possible. The rule for dealing with rounding from numbers exactly half
way between two representable numbers is designed to be predictable, but
round half down and half up.

>> If we programmers want to use floating point values to represent
>> something other than the specific set of rational numbers defined by the IEEE
[quoted text clipped - 9 lines]
>
> [*] Excluding NaN and other "not number" values (+/- Inf?).

Also -0, a double that is useful for representing numbers with tiny
absolute magnitude, but known negative sign. The mathematical rationals
have a single zero, because arbitrarily tiny numbers have rational
representations.

> But I may still just not be looking at things from the most useful
> or reasonable perspective.

Remember that if you are doing double arithmetic there is no particular
reason to assume that the true value has a short decimal representation.
It is convenient to have a relatively short default conversion to
String, because people prefer short numbers.

Truncating on conversion to BigDecimal would be an arbitrary rounding
error. How many digits would you drop? If the conversion is done exactly
the programmer has control because setScale() can be used to chop off
digits. Generally, rounding error is a BAD THING, not something to do
for the fun of it.

The program at the end of this message calculates sqrt(2) two ways,
using exactly the result of Math.sqrt(2), and rounding it to 17 decimal
places, the most that can be justified for a double of magnitude around 1.0.

The square of the rounded square root is further from 2 than the square
of the raw square root without any rounding.

Raw:
-2.7343234630647692806884916507957232351955020204815893780647684252471663057804107666015625E-16
Rounded: -2.862320485909535225E-16

import java.math.BigDecimal;
import java.math.RoundingMode;

public class SquareRootTest {
  public static void main(String[] args) {
    test(2);
  }

  static void test(int square){
    BigDecimal bigSquare = BigDecimal.valueOf(square);
    double dRoot = Math.sqrt(square);
    BigDecimal bigRoot = new BigDecimal(dRoot);
    BigDecimal roundedBigRoot =
bigRoot.setScale(17,RoundingMode.HALF_EVEN);
    BigDecimal rawError = bigSquare.subtract(bigRoot.multiply(bigRoot));
    BigDecimal roundedError =
bigSquare.subtract(roundedBigRoot.multiply(roundedBigRoot));
    System.out.println("Raw: "+rawError+" Rounded: "+roundedError);
  }
}

Patricia

Patricia
blmblm@myrealbox.com - 10 Apr 2007 16:00 GMT
[ snip ]

> >> The rules for Java FP are precise and
> >> exact, down to the last bit -- there is no approximation, or error
[quoted text clipped - 10 lines]
> way between two representable numbers is designed to be predictable, but
> round half down and half up.

Sure.

> >> If we programmers want to use floating point values to represent
> >> something other than the specific set of rational numbers defined by the IEEE
[quoted text clipped - 14 lines]
> have a single zero, because arbitrarily tiny numbers have rational
> representations.

That too.  (I knew I'd leave something out but was too slack to look
up the IEEE standard to get a complete list .... )

> > But I may still just not be looking at things from the most useful
> > or reasonable perspective.
[quoted text clipped - 9 lines]
> digits. Generally, rounding error is a BAD THING, not something to do
> for the fun of it.

Well, sure, but ....

I would have guessed, without thinking about at any length, that
since each floating-point number floatNum can be thought of as
representing a range of real numbers (a small interval containing
the exact value of the representation), it would be possible to
choose a value decimalNum in that range that could be expressed
in base 10 with a number of significant digits appropriate for the
number of bits of mantissa, and that converting a string representation
of decimalNum to floating point would yield floatNum again.  Your
experiment below, though, suggests that maybe this isn't possible,
or that's it's trickier than it seems.

> The program at the end of this message calculates sqrt(2) two ways,
> using exactly the result of Math.sqrt(2), and rounding it to 17 decimal
> places, the most that can be justified for a double of magnitude around 1.0.

I'm speculating that you chose 17 because it's one more than the 16
that (according to a quick Google search) represents the approximate
number of decimal significant figures a double can represent.  True?

> The square of the rounded square root is further from 2 than the square
> of the raw square root without any rounding.
>
> Raw:
> -2.7343234630647692806884916507957232351955020204815893780647684252471663057804107666015625E-16
> Rounded: -2.862320485909535225E-16

So obviously (?) rounding to 17 places doesn't give the kind of
reversible float-to-short-decimal conversion I tried to describe
earlier.  I wonder whether this means that it just isn't possible,
or whether 17 is not the right number of digits to keep.  

Huh.  I don't really have the time and inclination to think this
through carefully, so if I'm starting to try the experts' patience,
I hope they/you will bail out.

> import java.math.BigDecimal;
> import java.math.RoundingMode;
[quoted text clipped - 16 lines]
>    }
> }

Signature

B. L. Massingill
ObDisclaimer:  I don't speak for my employers; they return the favor.

Patricia Shanahan - 10 Apr 2007 16:44 GMT
> I'm speculating that you chose 17 because it's one more than the 16
> that (according to a quick Google search) represents the approximate
> number of decimal significant figures a double can represent.  True?

Yes, I wanted to err on the side of keeping data.

>> The square of the rounded square root is further from 2 than the square
>> of the raw square root without any rounding.
[quoted text clipped - 7 lines]
> earlier.  I wonder whether this means that it just isn't possible,
> or whether 17 is not the right number of digits to keep.  

BigDecimal does have a valueOf(double) method that uses the Double
toString representation of the double. That should be reversible. The
number of digits depends on the double.

Patricia
blmblm@myrealbox.com - 14 Apr 2007 11:08 GMT
> > I'm speculating that you chose 17 because it's one more than the 16
> > that (according to a quick Google search) represents the approximate
> > number of decimal significant figures a double can represent.  True?
>
> Yes, I wanted to err on the side of keeping data.

(Sorry about the delay in responding -- I started this subthread
and don't want to just abandon it midstream, but apparently my
interest in thinking hard about the issues is limited .... )

I guess I'm wondering whether you erred far enough -- that is,
whether it would have been more appropriate to use 18.  But I'm
too slack to do the kind of careful analysis that would be needed
to convince myself one way or another.  <shrug>, I guess.

> >> The square of the rounded square root is further from 2 than the square
> >> of the raw square root without any rounding.
[quoted text clipped - 12 lines]
> toString representation of the double. That should be reversible. The
> number of digits depends on the double.

And in fact using valueOf(double) in your test program gives a more
accurate answer.  Below, for the record, is my adaptation of your
test program, allowing one to specify the number of significant
figures and comparing using the BigDecimal(double) constructor with
valueOf(double), and its output.

I'm not sure I understand any of this very well, but I'm not quite
curious enough ....  Best to let it drop now maybe.

import java.math.BigDecimal;
import java.math.RoundingMode;

public class SquareRootTestBLMBLM {
  public static void main(String[] args) {
    testWithConstructor(2, 17);
    testWithConstructor(2, 18);
    testWithValueOf(2, 17);
    testWithValueOf(2, 18);
  }

  static void testWithConstructor(int square, int digits){
    BigDecimal bigSquare = BigDecimal.valueOf(square);
    double dRoot = Math.sqrt(square);
    BigDecimal bigRoot = new BigDecimal(dRoot);
    BigDecimal roundedBigRoot = bigRoot.setScale(digits,RoundingMode.HALF_EVEN);
    BigDecimal rawError = bigSquare.subtract(bigRoot.multiply(bigRoot));
    BigDecimal roundedError =
        bigSquare.subtract(roundedBigRoot.multiply(roundedBigRoot));
    System.out.println("\nUsing BigDecimal constructor and " + digits +
            " digits:");
    System.out.println("Root:  " + bigRoot);
    System.out.println("Raw error: " + rawError);
    System.out.println("Rounded error: " + roundedError);
  }

  static void testWithValueOf(int square, int digits){
    BigDecimal bigSquare = BigDecimal.valueOf(square);
    double dRoot = Math.sqrt(square);
    BigDecimal bigRoot = BigDecimal.valueOf(dRoot);
    BigDecimal roundedBigRoot = bigRoot.setScale(digits,RoundingMode.HALF_EVEN);
    BigDecimal rawError = bigSquare.subtract(bigRoot.multiply(bigRoot));
    BigDecimal roundedError =
        bigSquare.subtract(roundedBigRoot.multiply(roundedBigRoot));
    System.out.println("\nUsing BigDecimal valueOf and " + digits +
            " digits:");
    System.out.println("Root:  " + bigRoot);
    System.out.println("Raw error: " + rawError);
    System.out.println("Rounded error: " + roundedError);
  }
}

Output:

Using BigDecimal constructor and 17 digits:
Root:  1.4142135623730951454746218587388284504413604736328125
Raw error: -2.7343234630647692806884916507957232351955020204815893780647684252471663057804107666015625E-16
Rounded error: -2.862320485909535225E-16

Using BigDecimal constructor and 18 digits:
Root:  1.4142135623730951454746218587388284504413604736328125
Raw error: -2.7343234630647692806884916507957232351955020204815893780647684252471663057804107666015625E-16
Rounded error: -2.72089912967222571025E-16

Using BigDecimal valueOf and 17 digits:
Root:  1.4142135623730951
Raw error: -1.4481069235364401E-16
Rounded error: -1.448106923536440100E-16

Using BigDecimal valueOf and 18 digits:
Root:  1.4142135623730951
Raw error: -1.4481069235364401E-16
Rounded error: -1.44810692353644010000E-16

(Now that I think about it, it probably doesn't make sense to
round the result of using valueOf(double).  Oh well.)

Signature

B. L. Massingill
ObDisclaimer:  I don't speak for my employers; they return the favor.

Chris Smith - 23 Apr 2007 19:50 GMT
> I would have guessed [...] it would be possible to
> choose a value decimalNum in that range that could be expressed
> in base 10 with a number of significant digits appropriate for the
> number of bits of mantissa, and that converting a string representation
> of decimalNum to floating point would yield floatNum again.

It is possible to define such a reversible conversion.  In fact, the
methods Double.toString and Double.parseDouble are exactly such a pair
of reversible transformations between double and String, with no loss of
information.  The fact that the conversion is reversible doesn't mean it
is correct, or that it's a good idea to use it for intermediate results.  
Adding one to integers is also a reversible transformation, but it still
gives you a wrong answer.

In essence, there are several distinct, but similar, scenarios where you
want to convert something to decimal.  Different techniques are
appropriate for different scenarios.

Scenario I: You need a lossless conversion back and forth between double
and a decimal type that doesn't lose information.  This is what
Double.toString and Double.parseDouble are designed for.  BigDecimal's
valueOf(double) also does it.

Scenario II: You need a conversion to decimal for values that are read
from a user or from some device that gives input in short decimal
numbers.  If the values are stored in the double data type (which is
only appropriate if their original precision is much less than the
precision of the double data type), and you later want to convert them
to BigDecimal or String, then BigDecimal.valueOf or Double.toString are
the way to go, because the bias toward short decimal representations
actually helps you recover the intended value.

Scenario III: You are working with values from a source that's not
biased to provide round numbers in decimal, and you need to communicate
them to another source that won't be aware of your internal data types
so that a completely reversible conversion isn't feasible.

This is the tougher case.  There are trade-offs between communication
bandwidth/size and accuracy, but on average it's better for accuracy to
use the full decimal value of your double -- that is, new BigDecimal
(val), or if you need a string, new BigDecimal(val).toString().  The
reason this is better "on average" instead of "all the time" is that as
you noted, the double already contained binary rounding error.  It's
possible that you'll get lucky, and the decimal rounding error will be
in the opposite direction from the binary rounding error, so that they
cancel each other out -- but smart money is on the rounding error
accumulating instead.

If you want to make appropriate trade-offs between size and accuracy,
you can do it explicitly using setScale.

You have appropriate choices for all of these scenarios.  If you're
uncomfortable with the behavior of BigDecimal when it's constructed with
a double parameter, that's understandable, because converting double to
BigDecimal is certainly not a common need at all, and most often happens
in scenario 2 where rounding the number in reversible ways is often the
appropriate choice; in fact, I've only ever used new BigDecimal(double)
for demonstrating things on newsgroups.  But it does have a specific
well-defined behavior that can be valuable, and other valuable behaviors
can be achieved in other ways.

> Your
> experiment below, though, suggests that maybe this isn't possible,
> or that's it's trickier than it seems.

Patricia's experiment suggests that rounding to the nearest even decimal
number is likely to harm the accuracy of a resulting calculation when
you didn't have any reason to expect the right answer to have a short
decimal representation to begin with.  It doesn't guarantee that
reversing the rounding is impossible (in fact, it is possible, as you
could see by using the doubleValue method on the result).

Actually, Patricia's result was somewhat fortuitous.  By rounding to 17
places, she got an answer that was further off.  If she'd rounded to 16
or 18 places (or used BigDecimal.valueOf), she'd have gotten something
closer.  That's a fluke of the test case she used, though.  Here's a
modified test that demonstrates that on average, using BigDecimal's
valueOf, which does as you expected, provides poorer accuracy on square
roots of integers, versus new BigDecimal.  In other words, if you
introduce this new decimal rounding inaccuracies after the already-
suffered binary rounding inaccuracies, you will occasionally luck out
and round in the correct direction; but on average, you'll end up
considerably worse off.

public class Test
{
   public static void main(String[] args)
   {
       BigDecimal error = BigDecimal.ZERO;

       for (int square = 1; square < 500; square++)
       {
           BigDecimal bigSquare = BigDecimal.valueOf(square);
           double dRoot = Math.sqrt(square);
           BigDecimal bigRoot = new BigDecimal(dRoot);
           BigDecimal roundedBigRoot = BigDecimal.valueOf(dRoot);
           BigDecimal rawError = bigSquare.subtract(
               bigRoot.multiply(bigRoot));
           BigDecimal roundedError = bigSquare.subtract(roundedBigRoot
               .multiply(roundedBigRoot));

           error = error.add(rawError.abs());
           error = error.subtract(roundedError.abs());

           System.out.println("Error: " + error);
       }
   }
}

Although the first few integers end up closer with the rounded version,
the error eventually does more harm than good.  By the time you reach
500, the error is greater by something on the order of 10^-12 when
rounding the second time.

Signature

Chris Smith

Chris Uppal - 10 Apr 2007 14:23 GMT
> > The rules for Java FP are precise and
> > exact, down to the last bit -- there is no approximation, or error
[quoted text clipped - 3 lines]
> there is round-off error involved -- possibly a precise and
> well-defined error, but error.

Not really.  The rules are precise, and as long as the implementation follows
the rules then there is -- by definition -- no error involved.   Any more than
there is any roundoff /error/ in:
   (int)(3.14159)

> > If we programmers want to use floating point values to represent
> > something other than the specific set of rational numbers defined by
> > the IEEE format,
>
> Rational numbers with some unusual properties under arithmetic
> operations, no?  e.g., addition is not always associative.

I prefer to think of it as that the definition of floating point operators is
not what one might naively expect, and is no more than /similar/ to the
definitions of the corresponding operations on the reals (or rationals).  That
awkward fact is a large part of the reason why floating point numbers should be
avoided except under somewhat special circumstances.  (Such as: precision not
required; FP-sophisticated programmer available; program correctness optional
;-), and so on.)

> Given that, I'm inclined to stick with my claim that it's more
> sensible to regard floating-point numbers as approximations of
[quoted text clipped - 5 lines]
> But I may still just not be looking at things from the most useful
> or reasonable perspective.

I'm certainly not claiming that it's not a /useful/ perspective, but it isn't
the /only/ perspective, and it isn't the most /correct/ perspective.  It may
well be an ideal perspective from which to think about some properties of some
expressions of some algorithms (especially if all the numbers have similar
absolute magnitudes, or where the divergences from "pure" arithmetic can be
modelled as noise), but it's worthwhile having a different perspective
available into which you can "flip" either as a check, or when the other seems
to be failing altogether.

   -- chris
blmblm@myrealbox.com - 10 Apr 2007 15:25 GMT
> > > The rules for Java FP are precise and
> > > exact, down to the last bit -- there is no approximation, or error
[quoted text clipped - 8 lines]
> there is any roundoff /error/ in:
>     (int)(3.14159)

I think we're using "error" to mean different things here.

> > > If we programmers want to use floating point values to represent
> > > something other than the specific set of rational numbers defined by
[quoted text clipped - 6 lines]
> not what one might naively expect, and is no more than /similar/ to the
> definitions of the corresponding operations on the reals (or rationals).  

I think I'm starting to get your point here.  ("Finally"?)

> That
> awkward fact is a large part of the reason why floating point numbers should be
> avoided except under somewhat special circumstances.  (Such as: precision not
> required; FP-sophisticated programmer available; program correctness optional
> ;-), and so on.)

Pretty much agreed.  I think if you're doing the kind of "number
crunching" that seems to make up a lot of scientific computing, you
probably can't get acceptable performance without using floating
point.  But to get results that are meaningful, *someone* should
have the kind of expertise needed to understand how the limitations
of floating point affect the calculations.

> > Given that, I'm inclined to stick with my claim that it's more
> > sensible to regard floating-point numbers as approximations of
[quoted text clipped - 14 lines]
> available into which you can "flip" either as a check, or when the other seems
> to be failing altogether.

Signature

B. L. Massingill
ObDisclaimer:  I don't speak for my employers; they return the favor.

Greg R. Broderick - 08 Apr 2007 18:56 GMT
vishist <vishistm@gmail.com> wrote in news:4617fdcb$0$90271$14726298
@news.sunsite.dk:

> Hi Stefan,
>            Sorry for confusing question. My question was that when the
> above statement got executed, it prints 0.899999999999 instead of 0.9.
> Joshua Bloch (book author) gave an explanation for this and I'm unable
> to understand that explanation. So, wanted to clear that question from a
> different perspective.

What have you done, on your own to attempt to understand this?

Have you typed Joshua Bloch's code into your editor, compiled and run it?  If  
not, then why not?  

Have you stepped through the code in a debugger?  If not, then why haven't
you?

You will find that the amount of help that you receive in response to your
Usenet queries is directly proportional to the amount of your own effort that
you've demonstrated in an attempt to solve the problem.

c.f. <http://www.catb.org/~esr/faqs/smart-questions.html>

Cheers!
Signature

---------------------------------------------------------------------
Greg R. Broderick            gregb+usenet20004@blackholio.dyndns.org

A. Top posters.
Q. What is the most annoying thing on Usenet?
---------------------------------------------------------------------

vishist - 09 Apr 2007 04:55 GMT
> vishist <vishistm@gmail.com> wrote in news:4617fdcb$0$90271$14726298
> @news.sunsite.dk:
[quoted text clipped - 21 lines]
>
> Cheers!
I ran the code and got the output. But I didn't run it through the
debugger. I guess that is where I made the mistake. I will go through
the smart-questions and will try to be much more sensible in posting
questions.

thanks
Faton Berisha - 09 Apr 2007 10:54 GMT
> > vishist <vishi...@gmail.com> wrote in news:4617fdcb$0$90271$14726298
> > @news.sunsite.dk:
[quoted text clipped - 28 lines]
>
> thanks

I think that your question was sensible enough.

If you want to to go beyond the explanation you cited from the book,
then you will have to learn about binary representation of integers
first, and then about binary floating point representation of
fractional numbers.

If you're not ready to get into details (mathematical ones, as well),
then I think you could just draw some useful conclusions. For example,
how may iterations you think the following loop does? (A trial might
surprise you.)

   double d = 0.0;
   while ( d != 1.0 ) {
       d += 1.0 / 13;
       System.out.println(d);
   }

A conclusion: it usually is not a good idea to compare doubles in a
loop (or a conditional) test.

Faton Berisha


Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2009 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.