Division inconsistent between Debug/Release builds

J

John C Kirk

[This is a repost of a message that I sent to
microsoft.public.dotnet.vb.general on 7th September 2004; it looks
like this group is more active than that one, so there may be MVPs who
can read this and pass a bug report on to Microsoft if appropriate.]

I've come across an odd situation, where doing a floating point
division produces different results for the same numbers. Basically,
there are 4 ways to run this application:

A) Debug build, inside the IDE
B) Debug build, outside the IDE (e.g. launched from Explorer)
C) Release build, inside the IDE
D) Release build, outside the IDE

Using methods A-C produces one result, and using method D produces a
different result.

The minimal code to reproduce this problem follows. Create a standard
Windows application, one form, one button, then copy this code into
Form1:

***

Private Function DivideDouble(ByVal sngX As Single) As Double
Dim sngY As Single

sngY = sngX / 1000

Return 1 / sngY

End Function

Private Function DivideSingle(ByVal sngX As Single) As Single
Dim sngY As Single

sngY = sngX / 1000

Return 1 / sngY

End Function

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e
As System.EventArgs) Handles Button1.Click
Dim sngZ As Single
Dim dblZ As Double

sngZ = DivideSingle(15)
dblZ = DivideDouble(15)

MessageBox.Show("Z (single) = " & sngZ.ToString() & ", " &
vbNewLine & _
"Z (double) = " & dblZ.ToString())

End Sub

***

The idea is that this calculation involves 3 numbers, X/Y/Z. Y=X/1000,
and Z=1/Y.

If you run it with X=15 (as above), then methods A-C produce:
"Z (single) = 66.66667,
Z (double) = 66.6666681567828"
whereas method D produces:
"Z (single) = 66.66666,
Z (double) = 66.6666666666667"

If you run it with X=25, methods A-C produce:
"Z (single) = 40,
Z (double) = 39.9999994039536"
whereas method D produces:
"Z (single) = 40,
Z (double) = 40"

There are various workarounds for this, e.g. putting the calculations
into the button click event rather than calling separate functions, or
using doubles everywhere rather than singles. The truncating/rounding
seems slightly odd, but I can live with that. My main concern is that
this produces different results in the different cases, which has
caused me trouble with my testing.

Any thoughts? I'm using VB.NET 2003 with the .NET framework v1.1. My
machine has SP1 for the framework, but this also occurs on machines
that don't have the service pack.

John
 
G

Gerald Hernandez

John...

John C Kirk said:
[This is a repost of a message that I sent to
microsoft.public.dotnet.vb.general on 7th September 2004; it looks
like this group is more active than that one, so there may be MVPs who
can read this and pass a bug report on to Microsoft if appropriate.]

I've come across an odd situation, where doing a floating point
division produces different results for the same numbers. Basically,
there are 4 ways to run this application:

A) Debug build, inside the IDE
B) Debug build, outside the IDE (e.g. launched from Explorer)
C) Release build, inside the IDE
D) Release build, outside the IDE

Using methods A-C produces one result, and using method D produces a
different result.

The minimal code to reproduce this problem follows. Create a standard
Windows application, one form, one button, then copy this code into
Form1:

***

Private Function DivideDouble(ByVal sngX As Single) As Double
Dim sngY As Single

sngY = sngX / 1000

Return 1 / sngY

End Function

Private Function DivideSingle(ByVal sngX As Single) As Single
Dim sngY As Single

sngY = sngX / 1000

Return 1 / sngY

End Function

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e
As System.EventArgs) Handles Button1.Click
Dim sngZ As Single
Dim dblZ As Double

sngZ = DivideSingle(15)
dblZ = DivideDouble(15)

MessageBox.Show("Z (single) = " & sngZ.ToString() & ", " &
vbNewLine & _
"Z (double) = " & dblZ.ToString())

End Sub

***

The idea is that this calculation involves 3 numbers, X/Y/Z. Y=X/1000,
and Z=1/Y.

If you run it with X=15 (as above), then methods A-C produce:
"Z (single) = 66.66667,
Z (double) = 66.6666681567828"
whereas method D produces:
"Z (single) = 66.66666,
Z (double) = 66.6666666666667"

If you run it with X=25, methods A-C produce:
"Z (single) = 40,
Z (double) = 39.9999994039536"
whereas method D produces:
"Z (single) = 40,
Z (double) = 40"

There are various workarounds for this, e.g. putting the calculations
into the button click event rather than calling separate functions, or
using doubles everywhere rather than singles. The truncating/rounding
seems slightly odd, but I can live with that. My main concern is that
this produces different results in the different cases, which has
caused me trouble with my testing.

Any thoughts? I'm using VB.NET 2003 with the .NET framework v1.1. My
machine has SP1 for the framework, but this also occurs on machines
that don't have the service pack.

John

In addition to Mattias' suggestion on understanding what might be going
on...
I would also recommend explicitly defining what you want it to do.
By this, I mean explicitly define the variable types.
Given:
Private Function DivideDouble(ByVal sngX As Single) As Double
Dim sngY As Single

sngY = sngX / 1000

Return 1 / sngY

End Function

You are inputting a Single and want out a Double.
However, sngY is a Single, and 1000 is an Integer due to no decoration.
Also, 1 / sngY is an Integer / Single.
Yes, these will be cast to an appropriate datatype when they are evaluated,
however you are leaving it up to the compiler and/or chance as to what that
would be. They might all be cast to doubles, but you don't know for sure.
Additionally, they might also be rounded to accomodate the least precision.

In this case, I would change it to look like the following:

Private Function DivideDouble(ByVal sngX As Single) As Double
Dim dblY As Double
Dim dblX As Double

dblX = Convert.ToDouble(sngX)
dblY = dblX / 1000.0#

Return 1.0# / dblY

End Function

Yes, it is more verbose than necessary for clarity.
Given the particular end result, you could eliminate the reciprocal step by
reversing the original division.

Return 1000.0# / Convert.ToDouble(sngX)

It might not change the results you are seeing, but by taking out the
additional division, there is one less place for a rounding error to be
introduced.

As far as the difference between modes, this is entirely possible.
You have various amounts of precision available. I am not certain what
exactly is going on in each scenario, but it does not surprise me they are
different. This is common, even expected, behaviour when dealing with
floating point variables.

Gerald
 
J

John C. Kirk

In addition to Mattias' suggestion on understanding what might be going
on...
I would also recommend explicitly defining what you want it to do.

Thanks to both of you for the advice on that. I've found a workaround, so
that aspect isn't a major problem, it's more the different behaviour that
was bothering me.
As far as the difference between modes, this is entirely possible.
You have various amounts of precision available. I am not certain what
exactly is going on in each scenario, but it does not surprise me they are
different. This is common, even expected, behaviour when dealing with
floating point variables.

I know that Debug and Release modes actually generate different MSIL, so I
can accept them giving different results (although it's annoying). But is
there any advantage at all to running in Release mode inside the IDE? It
doesn't help you for testing, since you won't necessarily get the same
results as end users. And it won't help you to isolate errors, since you
can't break into the source code. The only advantage I can see is that it's
easier to click the "run" toolbar button than to browse for the file in
Explorer and double-click on it there, but surely that's just a question of
how the toolbar button code is implemented?

John
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top