Strange BUG in teh Framework?

  • Thread starter Thread starter David
  • Start date Start date
D

David

hello.
when doing the simple following computation, the value put into the variable
numMinusOne is NOT the same as what the computation is showed to be in the
Watch window!!
here is the code:
Dim xSng As Single = 6547.972
Dim yInt As Integer = 8000
Dim num As Integer = CInt(Math.Floor(xSng * yInt))
Dim numMinusOne As Integer = CInt(Math.Floor(xSng * yInt) - 1)

After running these 4 lines, numMinusOne will be:52383776
while the Watch window will claim that the value of :
CInt(Math.Floor(xSng * yInt) - 1) = 52383775

BTW, the Watch window is correct.

how come the numMinusOne does not get the correct value?
 
David said:
when doing the simple following computation, the value put into the variable
numMinusOne is NOT the same as what the computation is showed to be in the
Watch window!!
here is the code:
Dim xSng As Single = 6547.972
Dim yInt As Integer = 8000
Dim num As Integer = CInt(Math.Floor(xSng * yInt))
Dim numMinusOne As Integer = CInt(Math.Floor(xSng * yInt) - 1)

After running these 4 lines, numMinusOne will be:52383776
while the Watch window will claim that the value of :
CInt(Math.Floor(xSng * yInt) - 1) = 52383775

BTW, the Watch window is correct.

how come the numMinusOne does not get the correct value?

See http://www.pobox.com/~skeet/csharp/floatingpoint.html

You're actually calculating 6547.97216796875 * 8000 and then taking the
floor of that.

The watch window is probably doing computation in a slightly different
way - e.g. using a Double instead of a Single.
 
You're actually calculating 6547.97216796875 * 8000 and then taking the
floor of that.

hemm... why is that? Dim xSng As Single = 6547.972
means I want to use... 6547.972, not 6547.97216796875 . are you telling me
that the best representation for 6547.972 is 6547.97216796875 ??
 
I also think that it has to do with .NET, and not a general floating point
issue. because of the following:
Dim xSng As Single = 6547.972
Dim yInt As Integer = 8000
Dim d As Double = xSng * yInt
Dim s As Single = xSng * yInt

xSng * yInt should be : 52383776.0 . I still don't see how this can be any
different, even after reading the problems with floating point. in any case,
it either can or can not store 6547.972 as 6547.972.
how can it be that
d = 52383777.34375
s = 52383776.0
the runtime multiplies the numbers differently according to the left side of
the equation? this is against anything I know about order of operations.
if not, if the runtime does the same calculation, then why would the storing
of the same result give such a different number in d and in s ? such a
casting can make a small difference, not a 1.3x difference....
 
csmba said:
I also think that it has to do with .NET, and not a general floating point
issue. because of the following:
Dim xSng As Single = 6547.972
Dim yInt As Integer = 8000
Dim d As Double = xSng * yInt
Dim s As Single = xSng * yInt

xSng * yInt should be : 52383776.0 . I still don't see how this can be
any
different, even after reading the problems with floating point. in any
case,
it either can or can not store 6547.972 as 6547.972.
how can it be that
d = 52383777.34375
s = 52383776.0
the runtime multiplies the numbers differently according to the left side
of
the equation? this is against anything I know about order of operations.

No it's not. You are forgetting about the data type conversion operations.
tRemember that you usually only have arithmetic operations defined as binary
operations taking two inputs of the same type and returning that same type.
There is no multiplication operator coded to take an Integer and a Single
and return a Double. If there were, it would probably give the right
answer.

Look at it this way. How would you compute:

Dim d as Double = xSng * yInt

There are two possible ways, and both have problems.

First you could do it like this

A: Dim d as Double = CDbl(CSng(xSng * CSng(yInt)))

Or like this

B: Dim d as Double = CDbl(xSng) * CDbl(yInt)

For some values of xSng and yInt A will be more accurate, but for some
values the result of xSng * yInt won't even fit in a Single. If you use A
there, then you won't even get an answer!

EG

Dim xSng As Single = Single.MaxValue
Dim yInt As Integer = 8000

So the compiler chooses B as the default order of operations. If you don't
like that order, force your own.

David
 
I also think that it has to do with .NET, and not a general floating point
issue. because of the following:
Dim xSng As Single = 6547.972
Dim yInt As Integer = 8000
Dim d As Double = xSng * yInt

What happens when you do
Dim d As Double = xSng * Cdbl(yInt)

Dim s As Single = xSng * yInt

and
Dim s As Single= xSng * CSng(yInt) ?
 
thanks David. what you say is true... so the compiler decided to cast it to
double for the valid reason you mentioned...

so the only technical thing I guess I was missing was:
how come casting 6547.972 to double gives me a LESS accurate number? I
guessed that double is MORE accurate then single. however, a simple program
shows that all my problems are caused by the simple fact that:
Dim xSng As Single = 6547.972
dim d as double = CDbl(xSng)
--> gives that d's value is : 6547.97216796875

so that little cast from single to double causes a very significant
difference in value. I didn't expect a double to not be able to keep a value
when it comes from a single (float)...
did you know that this can happen?

Hernan.
 
as David.B pointed out, the issue is how the compiler decides to cast the
variables... it turns out that as long as it does it in Single, it works. if
it does it in Doubles, it will not.
looking into it, it all boils down to the fact that :
Dim xSng As Single = 6547.972
dim d as double = CDbl(xSng)
--> gives that d's value is : 6547.97216796875
in short, for some reason Double can not represent 6547.972, but adds that
167... to it which kills me in the calculation.
so to your question,:
Dim d As Double = xSng * Cdbl(yInt) d= 52383777.34375
Dim s As Single= xSng * CSng(yInt) ?
d=52383776.0

However, this still makes little sense, since a DOUBLE can easily save
6547.972 as in :
Dim xDbl As Double = 6547.972
check out xDbl value, it is 6547.972

so I still have no idea why this happens.
 
BTW, I just checked, and a double CAN save the value 6547.972 just fine. as
in
Dim xDbl As Double= 6547.972
check out xDbl value, it is 6547.972 and not 6547.972167...
so I still don't understand why the problem happens just when casting...
 
Hi David,

I agree with Jay's suggestion.
Since floating point number is less accuracy, so our value is approximate
but not exact equal, that is to say the float point number operation and
convert will cause error.


If we look into the assembly code as below.(We may open the Disassembly
window in VS.NET IDE)

--- D:\NewsGroup\VB.NET\Console\TestAppCol\Module1.vb
--------------------------
Module Module1

Sub Main()
00000000 push ebp
00000001 mov ebp,esp
00000003 sub esp,0Ch
00000006 fldz
00000008 fstp qword ptr [ebp-8]
0000000b mov dword ptr [ebp-0Ch],0
00000012 nop
Dim xSng As Single = 6547.972
00000013 mov dword ptr [ebp-0Ch],45CC9FC7h
Dim d As Double = 6547.972
0000001a mov dword ptr [ebp-8],0D4FDF3B6h
00000021 mov dword ptr [ebp-4],40B993F8h
d = CDbl(xSng)
00000028 fld dword ptr [ebp-0Ch]
0000002b fstp qword ptr [ebp-8]
End Sub
0000002e nop
0000002f mov esp,ebp
00000031 pop ebp
00000032 ret

the 6547.972 stored in memory will be presented as below
Single: 45cc9fc7 0 10001011 10011001001111111000111
Double: 40B993F8D4FDF3B6 0 10000001011
1001100100111111100011010100111111011111001110110110

While after we convert the Single to double, the 6547.972 will be
represented as below.
40b993f8e0000000 0 10000001011
1001100100111111100011100000000000000000000000000000

In all
0 10001011 10011001001111111000111
0 10000001011 1001100100111111100011010100111111011111001110110110
0 10000001011 1001100100111111100011100000000000000000000000000000
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~The
error occurred here.

Here is the difference between 6547.972(double) and 6547.972(convert from
single to double).

NOTE:
The float point number stored in computer is an approximate number.(refer
the 6547.972's single and double represent in memory)
We all know the count of real numbers is infinite. There will not be such
mapping between real number to 64bit float point number.
e.g.
We need to use integer (1,2,3,4....) to represent (1.3,2.4,3.5,4.6.....)
So we can use 1 to represent 1.3, because the error is least.
but for 3.5 I think the 3 and 4 is all available.

About the float point number's memory present, you may refer to the article
Jay provides.
http://www.yoda.arachsys.com/csharp/floatingpoint.html

Hope this helps.

Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
csmba said:
hemm... why is that? Dim xSng As Single = 6547.972
means I want to use... 6547.972, not 6547.97216796875 . are you telling me
that the best representation for 6547.972 is 6547.97216796875 ??

Yes. If you can find a closer floating point representation in 32 bit
IEEE 754, please provide it.
 
csmba said:
as David.B pointed out, the issue is how the compiler decides to cast the
variables... it turns out that as long as it does it in Single, it works. if
it does it in Doubles, it will not.
looking into it, it all boils down to the fact that :
Dim xSng As Single = 6547.972
dim d as double = CDbl(xSng)
--> gives that d's value is : 6547.97216796875
in short, for some reason Double can not represent 6547.972, but adds that
167... to it which kills me in the calculation.
so to your question,:

d=52383776.0

However, this still makes little sense, since a DOUBLE can easily save
6547.972 as in :
Dim xDbl As Double = 6547.972
check out xDbl value, it is 6547.972

so I still have no idea why this happens.

Rather than take each of your posts in turn, here's what's going on:

1) 6547.972 is *not* exactly representable as either a double or a
single precision floating point value. Just because it shows it as
6547.972 in the debugger doesn't mean that's the exact value - it just
means that the debugger is doing a bit of rounding on the display.
Trust my DoubleConverter here (which is downloadable - see the page I
referred you to before). Don't rely on what the debugger or framework
displays - there's no simple way of getting the framework to display
the exact value without something like DoubleConverter.

2) The framework is free to perform calculations at a higher precision
than the operands, and that's why you're seeing the difference you are.
(Chances are that the operation is actually being carried out in 80
bits, not 64.) It's not that the single version is being more accurate
- it's being *less* accurate, but in a way that happens to be in the
opposite direction from the way that the original numbers are
inaccurately stored, so the two inaccuracies happen to cancel out.

3) If this is mucking your application up, you need to fix your
application so it doesn't rely on floating point values being
infinitely precise. They're not, and they're not going to be. There's
no bug here - what you're seeing *is* a perfectly normal set of
floating point results. If anything, you're actually being lucky - the
double result is the exact value of the multiplication of the two exact
values involved (8000 and 6547.97216796875). That certainly isn't
always the case.
 
"Peter Huang" said:
Hi David,

I agree with Jay's suggestion.
Since floating point number is less accuracy, so our value is approximate
but not exact equal, that is to say the float point number operation and
convert will cause error.


If we look into the assembly code as below.(We may open the Disassembly
window in VS.NET IDE)
.. . .

The real problem is that this:
0 10001011 10011001001111111000111

Is a perfectly usable approximation of 6547.972. The rules for single
precision fp arithmetic guarantee that the result of using this as an an
aproximation of 6547.972 and doing some arithmetic with the approximation
will result only in a small error.

This is also a usable approximation of 6547.972, using double precision
arithmetic.
0 10000001011 1001100100111111100011010100111111011111001110110110

However, this is a horrible approximation of 6547.972. It seems to be just
as close as the single precision variable, but it's doing double precision
arithmetic, and the error can accumulate shockingly.
0 10000001011 1001100100111111100011100000000000000000000000000000

All that precision (all those zeros) get filled up with junk and you end up
with the wrong answer.


The lesson is to not mix singles and doubles.

Real > Single > arithmetic > Single > Real

is ok.

Real > Single > Double > arithmetic > Double > Single > Real

is not.

David
 
The lesson is to not mix singles and doubles.

Real > Single > arithmetic > Single > Real

is ok.

No. The lesson is not to assume that floating point arithmetic will be
accurate (in the sense the OP expected) in the first case.

Just because the error happened to accumulate more this way this time
(and doing everything in a lower form of arithmetic gave a closer to
"ideal" answer due to one set of errors cancelling the other out)
doesn't mean it always works that way.
 
Jon Skeet said:
No. The lesson is not to assume that floating point arithmetic will be
accurate (in the sense the OP expected) in the first case.

Yes you can assume that floating point arithmetic will be accurate. To use
fp at all you must manage the inaccuracy. Sticking with just singles or
just doubles results in an answer very close to 52383776. Mixing them
results in an answer less close.

Just because the error happened to accumulate more this way this time
(and doing everything in a lower form of arithmetic gave a closer to
"ideal" answer due to one set of errors cancelling the other out)
doesn't mean it always works that way.

It's not an accident that single precision multiplication gives a more
accurate answer here. Single precision floating point multiplication is
designed to manage the accumulation of error on a certian scale. Double
precision floating point multiplication is designed to manage the
accumulation of error on a much different scale. Converting a single to a
double and then doing double precision arithmetic reduces the accuracy since
the double precision arithmetic assumes that all those zeros are
significant, whereas single precision arithmetic knows better.

David
 
Yes you can assume that floating point arithmetic will be accurate.

Yes - but not in the way that the OP expected.
To use fp at all you must manage the inaccuracy. Sticking with just
singles or just doubles results in an answer very close to 52383776.
Mixing them results in an answer less close.

In this particular case.
It's not an accident that single precision multiplication gives a more
accurate answer here.

I still believe it's a coincidence.
Single precision floating point multiplication is
designed to manage the accumulation of error on a certian scale.

I don't believe that's true at all. The result of a multiplying two
exact single values is just the closest single value to the true
product of the values. There's no particular design there.
Double
precision floating point multiplication is designed to manage the
accumulation of error on a much different scale. Converting a single to a
double and then doing double precision arithmetic reduces the accuracy since
the double precision arithmetic assumes that all those zeros are
significant, whereas single precision arithmetic knows better.

It doesn't "know better" at all, IMO.

It's not that the multiplication is *done* in single precision, after
all - it's done in double precision (or more likely 80 bits), and then
truncated to the nearest single. There's no "single precision
arithemetic" going on - there's plain arithmetic, and conversions.
Don't forget that by the time any code gets involved, the original
value that the OP had in mind (6547.972) is nowhere to be seen.

Do you want me to try to find an example where the double result *is*
more accurate than the single result? Would that satisfy you? It'd take
a bit of experimentation, but I'm sure I can find an example.
 
Jon Skeet said:
Yes - but not in the way that the OP expected.


In this particular case.


I still believe it's a coincidence.


I don't believe that's true at all. The result of a multiplying two
exact single values is just the closest single value to the true
product of the values. There's no particular design there.

Exactly! But when you convert to double, you haven't really increased your
number of significant digits. And you need to round the result down to the
correct number of significant digits.

Doubles can differentiate between 52383776.0 and 52383777.34375. Singles
cannot. Since the inputs only really have single precision, the result
should be rounded accordingly before being converted to a double precision
value. If the value is left in a Double and not rounded down to 52383776.0,
the error can accumulate in subsequent operations.

David
 
<snip>

I've got an example:

using System;

public class Test
{
static void Main()
{
float f = 123.23356f;
double d = f;

float p1 = f*8000;
double p2 = d*8000;

Console.WriteLine (DoubleConverter.ToExactString(p1));
Console.WriteLine (DoubleConverter.ToExactString(p2));
}
}

That prints out:
985868.5
985868.46923828125

The ideal result (123.23356*8000) is 985868.48. This is closer to the
result given by the single converted to the double before the
multiplication than it is to the one which is just the single
multiplied by 8000.

In other words:

real -> single -> double -> arithmetic -> double -> real
is giving a better result than
real -> single -> arithmetic -> single -> real

Now admittedly there isn't the extra double -> single conversion you
mentioned in an earlier post, but I don't think you'll actually find
any cases where

real -> single -> arithmetic -> single -> real
is any different to
real -> single -> double -> arithmetic -> double -> single -> real
because I believe the latter is what the former ends up being performed
as anyway.
 
Exactly! But when you convert to double, you haven't really increased your
number of significant digits. And you need to round the result down to the
correct number of significant digits.

Rounding down doesn't always improve things though - that's my point.
Doubles can differentiate between 52383776.0 and 52383777.34375. Singles
cannot. Since the inputs only really have single precision, the result
should be rounded accordingly before being converted to a double precision
value. If the value is left in a Double and not rounded down to 52383776.0,
the error can accumulate in subsequent operations.

But the error may actually be closer to the originally intended value -
as shown in another post.
 
Back
Top