Từ 1 tới 5 trên tổng số 5 kết quả

Đề tài: [Khác]Bàn luận về Cấu trúc số thực, sai số hệ thống và việc làm tròn số

  1. #1
    Ngày gia nhập
    10 2006
    Nơi ở
    Hà Nội
    Bài viết
    146

    Mặc định [Khác]Bàn luận về Cấu trúc số thực, sai số hệ thống và việc làm tròn số

    Mình lang thang và có đọc được bài này rất hay, copy về cho cđ mình cùng đọc. (Bài này tác giả là MeoMeo - CLBVB)

    Thấy dân IT (kể cả sinh viên IT) im lặng trước nhiều câu hỏi về số thực, cách làm tròn số thực, mình không vui lắm, nên đặt ra topic này để cùng thảo luận (trả lời từng thắc mắc thì nhiều quá và dễ bị lặp lại câu hỏi). Hy vọng các bạn không “im ru bà rù” như ở các topic trước của mình (Cấu trúc BMP, giai thừa mở rộng).
    Khái niệm “số thực” ở đây chỉ bao gồm kiểu Single (4 bytes) và kiểu Double (8 bytes) của VB, không bao gồm kiểu Real của Pascal (6 bytes, ai quan tâm thì trình bày sau).
    1. Cấu trúc
    Cả hai kiểu số thực này được xây dựng theo chuẩn IEEE:
    x = (dấu) 2^n (1+a) với 1 > a >= 0, tức là gồm 1 bit dấu (cao nhất), các bit bậc nhị phân cao nhất (n biểu diễn qua 11 bit đối với Double hoặc 8 bit đối với Single) và các bit biểu diễn giá trị còn lại (a qua 52 bit đối với Double hoặc 23 bit đối với Single).
    2. Sai số hệ thống và cách tạo thành các bytes
    Như vậy, nếu số a không thể biểu diễn chính xác được qua một số hữu hạn (cụ thể là 52 và 23) các bit thì nó được làm tròn, gây ra sai số hệ thống, sai số này là cộng trừ 2^(-53) đối với Double và 2^(-24) đối với Single.
    Sai số như thế là rất nhỏ, và mình xin nói trước rằng đó không phải là lý do khiến hàm Round cho kết quả sai. Cái này lại là một loại sai số hệ thống khác, khi hiển thị theo hệ thập phân. (1 chữ số thẩp phân tiêu tốn hơn 3 bít nhị phân, 15 chữ số hết gần 49 bit, còn dư hơn 3 bit nhưng không đủ cho 1 chữ số nữa). Minh sẽ đưa ra phương án Round sau cùng.
    2.1. Tính n và a
    Để biểu diễn cặp (n,a), số Double và số Single, ta có thể dùng kiểu biến sau :
    [vb]
    Type thanhphan
    bac As Integer
    triso As Double
    End Type
    Type ieee64
    b(1 To 8) As Byte
    End Type
    Type ieee32
    b(1 To 4) As Byte
    End Type
    [/vb]
    Xác định n và a theo số thực r bằng hàm sau :
    [vb]
    Function expo(r As Double) As thanhphan 'Tìm lũy thừa cao nhất
    Dim n%, t#
    t = Abs(r)
    If t = 0 Then Exit Function ' Khỏi xét trong hàm này
    n = 0 'Với 2 < n <= 1 thì 2^0 là cao nhất, không cần While... sau đây
    While t >= 2 ' Nếu t = 2^1 trở lên thì làm cho đến < 2
    n = n + 1
    t = t / 2
    Wend
    While t < 1 ' Nếu t < 2^0 thì làm cho đến >= 1
    n = n - 1
    t = t * 2
    Wend
    expo.bac = n
    expo.triso = t - 1 'Trị số sau dấu phẩy
    End Function
    [/vb]
    (còn tiếp)

  2. #2
    Ngày gia nhập
    10 2006
    Nơi ở
    Hà Nội
    Bài viết
    146

    2.2. Cách tạo các bytesKhi làm tròn số a thành 52 bit, ta có thể dùng kiểu “cắt” (hàm Int) hay kiểu “quy tròn” (hàm Round). Vì rằng dùng Int tổng quát hơn, có thể thay thế Round (Ví dụ Round(1.x, 0) có thể thay bằng Int(1.x + 0.5)), nên mình sẽ dùng hàm Int.
    [vb]
    Function createDouble(r As Double) As ieee64
    'Gồm 1 bit dấu, 11 bit bậc nhị phân n, 52 bit trị số lẻ
    Dim ii%, tt#
    Dim bb As Byte, tp As thanhphan
    With createDouble
    For ii = 1 To 8
    .b(ii) = 0
    Next ii
    End With
    ‘ Với số 0 n = -(vô cực), nhưng được gán = 0
    If r = 0# Then Exit Function
    tp = expo(r)
    ‘*** Chỗ này phải chèn thêm một đoạn nếu dùng kiểu “quy tròn”
    'Quy ước tịnh tiến n lên 1023 đơn vị sau đây để n > 0
    ‘ và như vậy, nếu không muốn exit khi gặp số Double 0#,
    ‘ ta có thể thay lệnh “If r = 0# Then Exit Function” ở trên
    ‘ bằng lệnh “If r = 0# Then tp.bac = -1023”
    tp.bac = tp.bac + 1023

    'Byte thứ 8 gồm 1 bit dấu và 7 bit cao của bậc n
    bb = tp.bac \ 16 'Trong 11 bit, lấy 7, còn 4 bit thấp tính sau
    If r < 0 Then bb = bb + 128 ' dấu "-" ở bit cao nhất
    createDouble.b(8) = bb

    'Byte thứ 7 gồm 4 bit còn lại của bậc và 4 bit cao của trị số a
    tt = tt + tp.triso
    bb = Int(tt * 16)
    tt = tt * 16 - bb 'Phần dư tiếp theo
    bb = bb + (tp.bac Mod 16) * 16
    createDouble.b(7) = bb

    'Sau đây là các bytes tiếp theo. Chú ý: bình thường là 6,
    ‘nhưng có thể tăng nếu làm cho mình cách đọc Double riêng
    For ii = 6 To 1 Step -1
    bb = Int(tt * 256)
    tt = tt * 256 - bb
    createDouble.b(ii) = bb
    Next ii
    ‘*** Kiểu “cắt” hay kiểu “quy tròn” thì vấn đề nằm ở
    ‘ bước cuối của vòng lặp (byte số 1). Như trên là “cắt”,
    ‘ còn trong file gửi kèm là “quy tròn”. Sở dĩ phải thêm
    ‘ 1 đoạn vào phần đầu (nếu muốn “quy tròn”) là do
    ‘ không muốn điều chỉnh các bytes trước nếu gặp
    ‘ trường hợp byte số 1 sau khi quy tròn bị “vượt khung”
    End Function
    [/vb]
    Đoạn thêm 0.5 chữ số cuối để Int tương đương Round vào (chỗ cần chèn nêu trên) như sau:
    [vb]
    tt = 1
    For ii = 1 To 53
    tt = tt / 2
    Next ii
    If tp.triso + tt >= 1 Then
    tp.bac = tp.bac + 1024
    For ii = 1 To 8
    createDouble.b(ii) = 0
    Next ii
    Exit Function
    End If
    [/vb]
    (còn tiếp)
    http://caulacbovb.net/forum/viewtopi...eae7f65189e723

  3. #3
    Ngày gia nhập
    10 2006
    Nơi ở
    Hà Nội
    Bài viết
    146

    2.3. Khắc phục lỗi trong việc làm tròn số
    Việc làm tròn số thực trong khuôn khổ VB hay Excel (đều của Microsoft) là việc khó hiểu, có lẽ là chỉ có Microsoft mới trả lời được.
    Đối với kiểu Double, có thể thử trong 1 project bất kỳ bằng cách gõ

    x# = 1.23456789012345

    Đến “5” là vừa đủ 15 chữ số, nếu gõ thêm “5” thì bị cắt, nếu gõ thêm 6 thì cũng bị cắt, nhưng “5” cũ biến thành “6”.

    Kiểu Single lại tệ hại theo hướng khác (có lẽ vì 7 chữ số thập phân cần 23.2535 bit, lớn hơn 23 mà nó có). Thử bằng CommandButton lệnh sau:
    [vb]
    Private Sub Command1_Click()
    x! = 1.23456749 ‘Dấu “!” = As Single
    MsgBox x!
    End Sub
    [/vb]
    Khi chạy lệnh, bạn sẽ thấy kết quả là 1.234568, mặc dù sau “7” là “4” không tới “5”. Có vẻ như VB làm tròn số Single từng bước từ phải qua trái ? Như vậy, mẹo làm tròn số Double của taykhongbatgiac (trung chuyển qua Single nếu có thể) là có cơ sở. Nhưng với số Double ngoài phạm vi Single và với chính số Single thì làm thế nào ?
    Tại sao bit cuối cùng có giá trị rất nhỏ mà thường số Double có sai số lớn thế (thí dụ, 1.4465 được hiểu là 1.44649999999995) ? Đó là vì khi đổi ra hệ thập phân, ta phải “cắt đuôi” khá nhiều. 4 bit của byte 7 chỉ phải chia cho 16, byte 6 phải chia cho 16*256, byte 5 chia cho 16*256*256, vv… bắt đầu từ byte 5 trở xuống khi chuyển phải cắt bớt !
    Tóm lại là phải dùng mẹo thôi, không nhờ gì được các bit và byte. Để ý 2 yếu tố sau:
    a) Rắc rối chính là chữ số 5 cuối cùng mà ta lại muốn làm tròn đến chữ số trước nó.
    b) Số 0.5 bao giờ cũng được hiển thị đúng giá trị vì nó là lũy thừa của 2: 0.5 = 2^(-1).
    Từ hai nhận xét trên, ta có thể xây dựng hàm làm tròn MyRound như sau:
    [vb]
    Function MyRound(x As Double, n As Integer) as Double
    x = x * 10 ^ n ‘nếu x quá lớn, gần “đụng trần” số Double thì không được
    x = x + 0.5
    x = int(x)
    MyRound = x / 10 ^ n
    End Function
    [/vb]

  4. #4
    Ngày gia nhập
    10 2006
    Nơi ở
    Hà Nội
    Bài viết
    146

    Trong này các ví dụ minh họa đều viết bằng VB tuy nhiên, nếu chúng ta bỏ qua khía cạnh ngôn ngữ mà chỉ hiểu về thuật toán thôi mới là cái cốt yếu của vấn đề mà mình muốn bàn luận.

  5. #5
    Ngày gia nhập
    08 2006
    Nơi ở
    Hải Phòng
    Bài viết
    218

    Để em đọc hết rồi chuyển nó sang C cho mọi người dễ hiểu

Các đề tài tương tự

  1. Di chuyển hình tròn chạy theo biên một hình tròn khác như thế nào?
    Gửi bởi ktxc15 trong diễn đàn Thắc mắc lập trình C#
    Trả lời: 17
    Bài viết cuối: 16-03-2012, 12:01 PM
  2. Bài tập C nhập vào 1 tháng sau đó đưa ra kết luận tháng đó có bao nhiêu ngày (giải bằng cấu trúc Switch)
    Gửi bởi vungtroicuabo trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 12
    Bài viết cuối: 19-06-2011, 06:11 PM
  3. Database Ý tưởng làm tròn tiền trong phần mềm quản lý(Làm tròn tiền thanh toán cho khách)
    Gửi bởi cchangkhongayngo trong diễn đàn Thắc mắc lập trình C#
    Trả lời: 3
    Bài viết cuối: 12-06-2011, 08:09 AM
  4. Trả lời: 22
    Bài viết cuối: 22-03-2011, 10:15 AM

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn