Java Forum / General / April 2007
question regarding java puzzlers #2
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 MagazinesGet 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 ...
|
|
|