1

We're running into an issue with VB.NET's Math.Round method where we're getting different results for the same input. The problem is reproducible and best illustrated using this short piece of code containing a simple moving average function:

Public Class bug

 Public Shared Function ExponentialMovingAverage(Values() As Double) As Double
   Dim Weight, TotalWeight As Double
   ExponentialMovingAverage = 0
   Weight = 1
   TotalWeight = 0
   For Each Value In Values
    ExponentialMovingAverage += Value*Weight
    TotalWeight += Weight
    Weight /= 1-2/(Values.Length+1)
   Next
   ExponentialMovingAverage /= TotalWeight
   Return ExponentialMovingAverage
 End Function

 Public Shared Sub Main
   Dim v As Double = 20.41985000000000000000
   Console.WriteLine( _
    ExponentialMovingAverage({77.474, 1.4018}).ToString("0.00000000000000000000") & " --> " & _
    Math.Round(ExponentialMovingAverage({77.474, 1.4018}), 4) & vbCrLf & _
    v.ToString("0.00000000000000000000") & " --> " & _
    Math.Round(v, 4))
 End Sub

End Class

Running this code gives the following output (culture nl-NL):

20,41985000000000000000 --> 20,4199
20,41985000000000000000 --> 20,4198

We're wondering why the output is different for 2 seemingly identical calls to Math.Round. There should not be any precision problems as the value used has less digits than the 15-16 digits that should fit in a Double data type.

Searching the web mainly returns answers to rounding problems having to do with MidpointRounding, which seems unrelated.

Looking forward to any replies.

  • 1
    When I test the above your function `ExponentialMovingAverage` produces an output of 20.419850000000004 – OSKM Apr 10 '17 at 11:12
  • 2
    You *don't* have different results from the same input. `Math.Round()` is deterministic. You have different results from inputs which *look* the same. The underlying floats are in base 2. An artifact of displaying base 2 numbers in base 10 is that occasionally different numbers display the same. It seems that VB.net unfortunately lacks a built-in `frexp()` which lets you easily inspect the underlying bit-patterns, but something like this should be easy to translate to vb if you are curious: http://stackoverflow.com/q/389993/4996248 – John Coleman Apr 10 '17 at 12:31

1 Answers1

0

I suspect it is because, by default, Math.Round is using ToEven as its rounding approach. If you switch to this:

    Console.WriteLine( _
        ExponentialMovingAverage({77.474, 1.4018}).ToString("0.00000000000000000000") & " --> " & _
        Math.Round(ExponentialMovingAverage({77.474, 1.4018}), 4, MidpointRounding.AwayFromZero) & vbCrLf & _
        v.ToString("0.00000000000000000000") & " --> " & _
        Math.Round(v, 4, MidpointRounding.AwayFromZero))

then the result is as you expect. Your hard coded value is exactly 20.41985 so ToEven will turn it into 20.4198. I suspect your calculated result is very fractionally larger than 20.41985 (as stated above I also get 20.419850000000004 as the result) and so is rounded up.

Ref MidPointRounding docs

OldBoyCoder
  • 876
  • 6
  • 16