[.NET] ปัญหาราวๆ กับตัวเลขจอมแสบ

หากได้ทำงานเกี่ยวกับระบบด้วยการเงินแล้วเนี่ย ตัวเลขสำคัญมากนะครับ ปัญหาของผมเกิดจากการพัฒนาระบบงานบน .NET ด้วย C# แล้วต้องไปเทียบตัวเลขกับ ระบบงานที่เป็น VB6 แล้วปรากฏว่า มันหายไป 0.01 เลขปริศนาที่หายไป แต่ฝ่ายบัญชีไม่ยอมมมมมม ถ้าหายไปสัก 50 ล้าน มันยังหาได้ง่ายกว่าเลย

แล้วใครหละที่เป็นโจร ?

จริงแล้วมันเป็นปัญหาที่สะสมมานานตั้งแต่ของ VB6 ที่มีการปัดทศนิยมเศษ 5 แปลกๆ ทำให้ทีมที่ทำระบบเดิมในยุคนั้นมีการ Custom Function ขึ้นมาแก้ปัญหาจุจิกนี้ โดยเฉพาะครับ ซึ่งมีแนวคิดว่าเจอ 5 ปัดขึ้น สำหรับแนวคิดนี้นิยมใช้ในการบัญชี การเงินครับ

'=================================================================================
'Function   : Round5Up
'Purpose    : Return rounded Number.
'Direction  : This function solve round function in VB about odd and even value.
'=================================================================================
Public Function Round5Up(ByVal pvarNum As Variant, ByVal plngPos As Long) As Variant
On Error GoTo LineFail
Dim lstrNum         As String
Dim lstrDecimal     As String
Dim llngB4Decimal   As Long
Dim i               As Long
    lstrNum = Trim(CStr(CDec(pvarNum)))
    For i = 1 To Len(lstrNum)
        If Mid(lstrNum, i, 1) = "." Then
            llngB4Decimal = i
            lstrDecimal = Mid(lstrNum, i + 1, Len(lstrNum) - i)
            If plngPos + 1 > Len(lstrDecimal) Then
                GoTo LineFail
            End If
            If Mid(lstrDecimal, plngPos + 1, 1) = "5" Then
                Round5Up = Round(Mid(lstrNum, 1, llngB4Decimal + plngPos) + "6", plngPos)
            Else
                Round5Up = Round(CDec(lstrNum), plngPos) 
            End If
            GoTo LineExit
        End If
    Next i
    
    If i - 1 = Len(lstrNum) Then
        GoTo LineFail
    End If

LineExit:
    Round5Up = CDec()
    Exit Function
LineFail:
    If Not IsNumeric(lstrNum) Then
        Round5Up = 0
    Else
        Round5Up = CDec(lstrNum)
    End If
End Function
  • หลังจากดู Legacy Code เรามาดูอัลกอริทึมที่ VB6 ใช้ดีกว่าครับ เจ้า VB6 ใช้อัลกอริทึมในการ Rounding คือ Banker's Rounding สาระสำคัญ คือ ต่ำกว่า 4 ปัดลง มากกว่า 6 ปัดขึ้น ปัญหา คือ เลข 5 เนี่ยแหละ ต้องดูตัวเลขข้างหน้า 5 หากเป็นเลขคี่ ก็ให้ปัดขึ้น หากไม่ใช่ก็ให้ปัดเศษทิ้ง (นายแบงค์นี่น่าเลือดจริงๆครับ)
  • ระบบเดิมที่ผมไป Cross Check ใช้ Function Round5up ที่แก้ปัญหานี้เรียบร้อยแล้ว

ขอเสริม อีกนิดนึง คือ ในคอม ถ้าเราป้อน 1.50 มันไม่ได้เก็บ 1.50 ลงไปนะครับ มันแปลงเป็นเลขฐาน 2 เนี่ยแหละ โดยอาจจะเป็น 1.4999999999999 แล้วมาใช้อัลกอริทึมในการ Rounding อีกทีนะครับ (เดี๋ยวไปเขียน Blog แยกไปอธิบายอีกทีครับ)

จริงๆ .NET กับ VBA ก็ใช้วิธี Banker's Rounding ในการ Round นะครับ

หลายคนคงคิดว่าปัญหาทศนิยม Diff คงไม่จบแล้ว แต่จริง Microsoft .Net Framework ได้แก้ปัญานี้เรียบร้อยแล้ว ตั้งแต่ .NET Framework 2.0 แล้วครับ โดยเพิ่ม Enum อีกตัวนึงให้กับ Function Math.round ชิ้อ MidPointRounding โดยมี Config 2 ตัว ดังนี้

  • AwayFromZero : ถ้าเจอ 5 ให้ปัดขึ้น หรือปัดหนีออกจากศูนย์ เหมือนกับ Function Round5up ใน VB6
  • ToEven : ปัดไปหาเลขคู่

มาลองตัวอย่างการ Round ดีกว่า

Math.Round(3.145,2)                                  //3.14
Math.Round(3.155,2)                                  //3.16
//ลองเพิ่ม MidPointRounding.AwayFromZero 
Math.Round(3.145, 2, MidPointRounding.AwayFromZero)  //3.15
Math.Round(3.155, 2, MidPointRounding.AwayFromZero)  //3.16

หวังว่าบทความนี้จะช่วยผู้อ่านหลายๆคนที่ประสบปัญหากับเรื่องเศษทศนิยม Diff นะครับ


Discover more from naiwaen@DebuggingSoft

Subscribe to get the latest posts sent to your email.