tags:

views:

767

answers:

8

Hello,

The question is, why do these code snippets give different results?

private void InitializeOther()
{
  double d1, d2, d3;
  int i1;

  d1 = 4.271343859532459e+18;
  d2 = 4621333065.0;
  i1 = 5;

  d3 = (i1 * d1) - Utils.Sqr(d2);
  MessageBox.Show(d3.ToString());
}

and

procedure TForm1.InitializeOther;
var d1, d2, d3 : Double;
    i1 : Integer;
begin
    d1:=4.271343859532459e+18;
    d2:=4621333065.0;
    i1:=5;

    d3:=i1*d1-Sqr(d2);
    ShowMessage(FloatToStr(d3));
end;

The Delphi code gives me 816, while the c# code gives me 0. Using a calculator, I get 775. Can anybody please give me a detailed explanation?

Many thanks!

+5  A: 

This is certainly not an explanation of this exact situation but it will help to explain the problem.

What Every Computer Scientist Should Know About Floating-Point Arithmetic

jellomonkey
+9  A: 

Note that you're at the limits of the precision of the Double data type here, which means that calculations here won't be accurate.

Example:

d1 = 4.271343859532459e+18

which can be said to be the same as:

d1 = 4271343859532459000

and so:

d1 * i1 = 21356719297662295000

in reality, the value in .NET will be more like this:

2.1356719297662296E+19

Note the rounding there. Hence, at this level, you're not getting the right answers.

Lasse V. Karlsen
A: 

I think that this is an error caused by the limited precision (Above all, because using doubles instead of integers). Perhaps d1 isn't the same after the assignment. d2*d2 will surely be different than the correct value as it's bigger than 2^32.

As 5*d1 is even bigger than 2^64, even using 64-bit integers won't help. You'd have to use bignums or a 128-bit integer class to get the correct result.

schnaader
Calculations will be performed in floating point domain, range of integer types doesn't even come into play here.
mghie
I know, I only wanted to make clear that even using integers won't help
schnaader
+3  A: 

Any calculation such as this is going to lead to dramas with typical floating point arithmetic. The larger the difference in scaling of the numbers, the bigger the chance of an accuracy problem.

http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems gives a good overview.

JDU
+4  A: 

A C# double has at most 16 digits of precision. Taking 4.271343859532459e+18 and multiply by 5 will give a number of 19 digits. You want to have a number with only 3 digits as a result. Double cannot do this.

In C#, the Decimal type can handle this example -- if you know to use the 123M format to initialize the Decimal values.

    Decimal d1, d2, d3; 
    int i1; 
    d1 = 4.271343859532459e+18M;
    d2 = 4621333065.0M;
    i1 = 5; 
    d3 = (i1 * d1) - (d2*d2); 

    MessageBox.Show(d3.ToString());

This gives 775.00 which is the correct answer.

Jack Bolding
Be aware that though decimal has much better precision (and uses more memory) than double, it also has much lower range (±1.0 × 10^−28 to ±7.9 × 10^28).
Brian
@Brian thanks for fixing the Decimal initializations. I don't use them often enough to know the xxxM syntax.
Jack Bolding
Thanks for pointing this out to me! Decimals might be the answer in my situation, although the much narrower range and the possible performance overhead are making me think twice before using them. As this is rather a corner case in my app, I may be able to factor down the input doubles to stop this kind of calculation occuring.
Shunyata Kharg
+11  A: 

Delphi stores intermediate values as Extended (an 80-bit floating point type). This expression is Extended:

i1*d1-Sqr(d2);

The same may not be true of C# (I don't know). The extra precision could be making a difference.

dangph
That's exactly what's going on. According to the Delphi helpfile, the .NET framework can only go up to double precision. Delphi uses Extended by default.
Mason Wheeler
Thank you, that explains the difference in behaviour.
Shunyata Kharg
Be careful too with calculations like this when the 8087 precision is involved. I have had two instances of different values between console and gui applications from the same code. Somewhere the 8087 control word was being changed. If in doubt, and it matters, set it explicitly. FWIW.
Richard A
A: 

Basically, as other people have pointed out, double-precision isn't precise enough for the scale of the computation you're trying to do. Delphi uses "extended precision" by default, which adds another 16 bits over Double to allow for more precise computation. The .NET framework doesn't have an extended-precision data type.

Not sure what type your calculator is using, but it's apparently doing something different from both Delphi and C#.

Mason Wheeler
A: 

As commented by the others, the double isn't precise enough for your computation. The decimal is a good alternative eventhough someone pointed out that it would be rounded it is not.

In C#, the Decimal type cannot handle this example easily either since 4.271343859532459e+18 will be rounded to 4271343859532460000.

This is not the case. The answer if you use decimal will be correct. But as he said the range is different.

Khan