Masalah pembulatan VB.net Math.Round - hasil berbeda untuk input yang sama

Kami mengalami masalah dengan metode Math.Round VB.NET di mana kami mendapatkan hasil yang berbeda untuk masukan yang sama. Masalahnya dapat direproduksi dan diilustrasikan dengan baik menggunakan potongan kode singkat yang berisi fungsi rata-rata bergerak sederhana:

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

Menjalankan kode ini akan menghasilkan output berikut (culture nl-NL):

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

Kami bertanya-tanya mengapa keluarannya berbeda untuk 2 panggilan yang tampaknya identik ke Math.Round. Seharusnya tidak ada masalah presisi karena nilai yang digunakan memiliki digit lebih sedikit daripada 15-16 digit yang sesuai dengan tipe data Double.

Penelusuran di web terutama menghasilkan jawaban atas masalah pembulatan yang berkaitan dengan MidpointRounding, yang tampaknya tidak ada hubungannya.

Menantikan balasan apa pun.


person Johan Fitié    schedule 10.04.2017    source sumber
comment
Ketika saya menguji di atas fungsi Anda ExponentialMovingAverage menghasilkan output 20.419850000000004   -  person OSKM    schedule 10.04.2017
comment
Anda tidak mendapatkan hasil yang berbeda dari masukan yang sama. Math.Round() bersifat deterministik. Anda mendapatkan hasil berbeda dari masukan yang terlihat sama. Float yang mendasarinya berada di basis 2. Artefak menampilkan angka basis 2 di basis 10 adalah terkadang angka yang berbeda menampilkan angka yang sama. Tampaknya VB.net sayangnya tidak memiliki frexp() bawaan yang memungkinkan Anda dengan mudah memeriksa pola bit yang mendasarinya, tetapi hal seperti ini seharusnya mudah diterjemahkan ke vb jika Anda penasaran: stackoverflow.com/q/389993/4996248   -  person John Coleman    schedule 10.04.2017


Jawaban (1)


Saya menduga ini karena, secara default, Math.Round menggunakan ToEven sebagai pendekatan pembulatannya. Jika Anda beralih ke ini:

    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))

maka hasilnya sesuai dengan yang anda harapkan. Nilai kode keras Anda persis 20,41985 jadi ToEven akan mengubahnya menjadi 20,4198. Saya menduga hasil perhitungan Anda jauh lebih besar dari 20,41985 (seperti yang disebutkan di atas, saya juga mendapatkan 20,419850000000004 sebagai hasilnya) dan dibulatkan ke atas.

Lihat dokumen MidPointRounding

person OldBoyCoder    schedule 10.04.2017