Win a copy of Re-engineering Legacy Software this week in the Refactoring forum
or Docker in Action in the Cloud/Virtualization forum!

# What actual Double.toString(double d) does?

Gasan Guseynov
Ranch Hand
Posts: 67
Hi, folks.

at first it prints maximum 20 characters
at second if I write
d = 1231230.10000203093000002876;
System.out.println("Double.toString(d) " + Double.toString(d));
output is "Double.toString(d) 1231230.1000020308"
but if I write
d = 1231230.10000203003000002876;
System.out.println("Double.toString(d) " + Double.toString(d));
output is "Double.toString(d) 1231230.1000020301"

Black magic isn't it? Didn't found any explaination of this in javadoc. Clear minds, please explain.

P.S. I use jdk 1.6.0_12.

Thank you.

Henry Wong
author
Marshal
Posts: 20892
75
Black magic isn't it?

I'm not sure what "black magic" you are referring to here. It just looks like you are trying to assign a double with more precision than it is capable of holding, and it got lost. I bet you would get similar results just by printing the double -- you don't need to convert it to a string first.

Henry

Campbell Ritchie
Sheriff
Posts: 48404
56
Try converting it to a BigDecimal, then you can see how many decimal places of imprecision you have got

There is something more helpful in our FAQ (look at no 20).

Gasan Guseynov
Ranch Hand
Posts: 67
well, maybe another question.

All this happens because I had this:

double d = 0.1
BigDecimal(d);

which was return not 0.1 in BigDecimal

and then I wondered, why

BigDecimal(Double.toString(d))

works perfectly. I mean that
Double.toString(d) returns "0.1"
but actual value of d is 0.1000000000000000055511151231257827021181583404541015625
what I'm trying to say is that just before the place where non-zero value happens, toString() truncates the remainder part.

I just, don't understand the logic of all this happenings.

Gasan Guseynov
Ranch Hand
Posts: 67
well, what exactly I'm trying to ask is that absolutely safe to write the code like

double d;
//d = 0.1; (e.g) setting value to d
BigDecimal bd = new BigDecimal(Double.toString(d));

double d;
//d = 0.1; (e.g) setting value to d
BigDecimal bd = new BigDecimal(d);
bd = bd.setScale(5, BigDecimal.ROUND_DOWN); // rounding and scale exact values are set here only for example

And if I can use first method without cautions, why?

Campbell Ritchie
Sheriff
Posts: 48404
56
None of those methods looks reliable. Try new BigDecimal("0.1"); Did you find the FAQ link I quoted earlier?

Gasan Guseynov
Ranch Hand
Posts: 67
yes, I mean, that the value is passed to this method. I can't just write "0.1", because value could be 1, 2, 3, 1.2, or any other.

salvin francis
Bartender
Posts: 1263
10
Its Black magic.

Extreme dark, most wizards will never ever dare to reveal.

In thy simplest of thee terms. you tried to fit an elephant into a matchbox, and in doing so,
thou hadth wispered the witch's charm. This turned the elephant into a tiny mouse.

hence when you opened thee matchbox to reveal the elephant, you happened to find a mouse.

d >> elephant
Double.toString(d) >> witch's charm ( never utter it loud )
1231230.1000020301 >> mouse

I hope in thy quest for knowledge, thou have gained the meaning of the above ..

Gasan Guseynov
Ranch Hand
Posts: 67
well, actually, I red many faqs about that, not exactly that what you're pointed to.

Gasan Guseynov
Ranch Hand
Posts: 67
Campbell Ritchie wrote:None of those methods looks reliable. Try new BigDecimal("0.1"); Did you find the FAQ link I quoted earlier?

actually, third link in 20th faq section is broken.

Campbell Ritchie
Sheriff
Posts: 48404
56
You should know by now that any floating-point values carry the risk of slight imprecision; when you use Double#toString or the BigDecimal constructor, you can demonstrate those imprecisions. That is why you get values ending with 99999.... or 0000.... You always risk getting those imprecisions. The only way to avoid them is to stick to integer arithmethic and BigDecimal, never using double or float at all. In fact, of those values you quoted, you can probably get 1, 2, 3 represented exactly in a double, never 1.2, but 1.25, 1.125 and 1.5 will probably show correctly too.

Doubles are designed for engineers. When I was an undergraduate they joked that an engineer is somebody whom you ask what 2 times 2 means. He gets his slide rule out, works it out, says, "3.99 . . . oh, that's 4 near as makes no difference."
And mention of slide rules dates me. So people who don't mind there being no difference between 3.99 and 4 can happily use floating-point arithmetic. Anybody who expects 4 and isn't happy with 4.000000000000045935098395 or 3.999999999999998374659120375 must avoid doubles and floats.

And take no notice of people writing about black magic

salvin francis
Bartender
Posts: 1263
10

Campbell Ritchie
Sheriff
Posts: 48404
56
The nearest I can find to the broken link is this search on the IBM website. Try results nos 4 5 6 and 7.

Unless you are rounding, you cannot expect a nice simple fractional number from doubles or floats. The BigDecimal simply shows the errors more clearly. If you go through your Java installation folder, you will find a file called src.zip. Unzip that, find the java folder, the lang folder, the Double.java file, and inspect that with a text editor. That will show you how the toString method is written, but I suspect it won't show you what you want to know.

Gasan Guseynov
Ranch Hand
Posts: 67
well, probably, I'm lack of very basic knowledge of all this numbers to build solid knowledge basis about how all this works in java and how to use the meanings that java provide to operate on them. If someone advise me what I shall read (from very simple to complex, preferably), I'll be very appreciate.

Gasan Guseynov
Ranch Hand
Posts: 67
what I found that I think I can rely on, is to use BigDecimal.ROUND_HALF_UP, when I know exactly what number of decimal points I should provide.

d = 1.19;
dc = new BigDecimal(d);
dc = dc.setScale(5, BigDecimal.ROUND_HALF_UP);

(here, I know, that I should guarantee 5 dp precision).

Thanks.

Ulf Dittmer
Rancher
Posts: 42967
73
Gasan Gouseinov wrote:actually, third link in 20th faq section is broken.

Campbell Ritchie
Sheriff
Posts: 48404
56
new BigDecimal(123456789012.3456789).setScale(5, BigDecimal.ROUND_HALF_UP)?
new BigDecimal("123456789012.3456789").setScale(5, BigDecimal.ROUND_HALF_UP)?

That won't guarantee 5 places' precision. Try it. You will very probably get 5 places' precision if you confine your values to a small range, but it is by no means guaranteed.

Gasan Guseynov
Ranch Hand
Posts: 67
you're right. Just can't understand it. It's seems to be too complex to understand by an ordinary mind. But luckily I have a small numers (or I don't but want to believe that I do

Campbell Ritchie
Sheriff
Posts: 48404
56
There's nothing complicated about it. A double is usually precise to 15 significant figures (sig-fig), may be precise at 16 sig-fig, and is definitely imprecise at 17 sig-fig. All I did was provide a number which even when scaled to 5 decimal places had 17 significant figures in. So an error was bound to occur.
The String constructor for BigDecimal can take any value entered, even millions of digits.

You are trying to represent 0/1 (1 / 10) in terms of ½ 1/4 1/8 1/16 etc etc. Try as you will, you will never get an exact representation. Like working out 1 / 3 in decimal. It is 0.3, 0.33, 0.333, 0.3333, 0.33333, 0.33333333333333333333333333333333333333333333333333333333333333333, but all those figures differ from the true value of 1 / 3. When you pass the double value 0.1 to the BigDecimal constructor, it can accurately translate the ½ 1/4 1/8 etc to decimal, which has an error of ...55511151231257827021181583404541015625 in. If you take that error and keep doubling it you eventually get to 0.1.

Try thisPass 0.1 as a command-line argument, multiply the error by 10, and you find that after 54 doublings, you get to 1: that means the error beginning with 555 is 0.1*2^-54. Remember a double has effectively 53 bits' precision. So that is 1/10 of ½ bit at the very last position. If you pass a small whole number, eg 1, you find no error at all, and there is no error for 0.75 because ¾ can be exactly represented in binary (0.11b).

And thank you for fixing the link, Ulf.