Chuyển số thập phân thành số hữu tỉ ??

Liên hệ QC

Lệnh Hồ Đại Hiệp

Độc cô Cửu kiếm
Tham gia
20/10/08
Bài viết
299
Được thích
524
Nhân có bài này Tìm mối quan hệ giữa 2 nhóm số cho trước

Bài toán là : Có một số thập phân cho trước, ta biết rằng nó có thể được biểu diễn chính xác dưới dạng phân số (Số hữu tỉ), và mẫu số của nó sẽ <=10.000.000 (VD vậy thôi)

Vậy có cách nào tìm được chính xác phân số đó ??

VD :26.813/268.250 = 0,099955265610438
 
Nhân có bài này Tìm mối quan hệ giữa 2 nhóm số cho trước

Bài toán là : Có một số thập phân cho trước, ta biết rằng nó có thể được biểu diễn chính xác dưới dạng phân số (Số hữu tỉ), và mẫu số của nó sẽ <=10.000.000 (VD vậy thôi)

Vậy có cách nào tìm được chính xác phân số đó ??

VD :26.813/268.250 = 0,099955265610438
Vậy mời các cao thủ thử với Function sau:
PHP:
Function PhanSo(So as Double) As String
Dim i as Double
For i = 1 to 10000000
If i*So = Round(i*So,0) then
PhanSo = i*So & "/" & i
Exit For
End IF
Next i
End Function
 
Upvote 0
Vậy mời các cao thủ thử với Function sau:
PHP:
Function PhanSo(So as Double) As String
Dim i as Double
For i = 1 to 10000000
If i*So = Round(i*So,0) then
PhanSo = i*So & "/" & i
Exit For
End IF
Next i
End Function

Ta thấy 9899989/8756985 được biểu diễn dưới dạng số của Excel là 1,13052483246231

Nếu như công thức của Ca_dafi tiên sinh thì
= PhanSo(9899989/8756985) = 9899989/8756985

Tuy nhiên PhanSo(1,13052483246231) = ""

vậy giải pháp nào cho trường hợp này và các trường hợp tương tự ?? Tức là ta chỉ biết số thập phân (do excel thể hiện) mà không biết tỷ số nguyên gốc của nó ??

Nếu dùng :
PHP:
Function PhanSo(So As Double) As String
    Application.Volatile (False)
    Dim i As Long
    For i = 1 To 10000000
        If Round(So * i, 7) = Round(So * i, 0) Then
            PhanSo = Format(Round(So * i, 0), "#,##0") & "/" & Format(i, "#,##0")
            Exit Function
        End If
    Next
End Function

Thì

PhanSo(1,13052483246231) = 9899989/8756985

Tuy nhiên UDF này không phải lúc nào cũng chính xác.
 
Upvote 0
Xin góp ý thêm phần cần trên cho hàm For. Vì nếu chỉ có 10000000 nhiêu đây thôi thì hơi bị bí quá.
PHP:
Function PhanSo(So As Double) As String
    Application.Volatile (False)
    Dim i As Long
    For i = 1 To 10 ^ Val(Len(So))
        If Round(So * i, 7) = Round(So * i, 0) Then
            PhanSo = Format(Round(So * i, 0), "#,##0") & "/" & Format(i, "#,##0")
            Exit Function
        End If
    Next
End Function
Thân.
 
Upvote 0
Xin góp ý thêm phần cần trên cho hàm For. Vì nếu chỉ có 10000000 nhiêu đây thôi thì hơi bị bí quá.
PHP:
Function PhanSo(So As Double) As String
    Application.Volatile (False)
    Dim i As Long
    For i = 1 To 10 ^ Val(Len(So))
        If Round(So * i, 7) = Round(So * i, 0) Then
            PhanSo = Format(Round(So * i, 0), "#,##0") & "/" & Format(i, "#,##0")
            Exit Function
        End If
    Next
End Function
Thân.

Áp dụng công thức của tiên sinh : Phân số =9899989/87569859 =0,113052471627252 (Xấp xỉ)
PhanSo(9899989/87569859) = PhanSo(0,113052471627252) = 867.047/7.669.421

Vẫn chưa đúng tiên sinh ạ.

Vấn đề là làm sao vừa đúng, vừa đảm bảo được tốc độ. Nếu không tìm được số đúng 100% thì tìm số đúng nhất.

--CV--
 
Upvote 0
Bài toán này có liên quan đến các vấn đề:
1- Phân số tối giản
2- Bài toán gần đúng (xấp xỉ), làm tròn
do đó, từ biểu thức: a/b = c ; khi phân tích ngược lại chưa chắc đã cho kết quả c = a/b - điều này có nguồn gốc từ các phép làm tròn.
 
Upvote 0
Vâng quynh đài nói vậy thì tại hạ cũng xin đáp trả chiêu này: Code trên của quynh đài nếu dùng số này (0.924596041212827=(14,479,873/15,660,756)) thì không có đáp số luôn chứ chưa nói gì đúng hay sai nữa là. Còn việc nhanh hay chậm, chính xác đến cở nào thì còn phụ thuộc vào cái Round kia nữa. Vậy xem như không tính đi nha. Chỉ xét xem code chạy được số cực đại hay cực tiểu bao nhiêu trước đã. Và code của em vẫn đang chạy rất tốt đấy chứ. Thân.
 
Lần chỉnh sửa cuối:
Upvote 0
Vâng quynh đài nói vậy thì tại hạ cũng xin đáp trả chiêu này:
Code trên của quynh đài nếu dùng số này (0.924596041212827=(14,479,873/15,660,756)) thì không có đáp số luôn chứ chưa nói gì đúng hay sai nữa là.
Còn việc nhanh hay chậm, chính xác đến cở nào thì còn phụ thuộc vào cái Round kia nữa. Vậy xem như không tính đi nha. Chỉ xét xem code chạy được số cực đại hay cực tiểu bao nhiêu trước đã. Và code của em vẫn đang chạy rất tốt đấy chứ.
Thân.

Tại hạ đâu có nói Code của tại hạ là đúng đâu.
Lệnh Hồ Đại Hiệp đã viết:
PhanSo(1,13052483246231) = 9899989/8756985

Tuy nhiên UDF này không phải lúc nào cũng chính xác.

Tại hạ nêu ra chỉ để nói rằng Code của Ca_dafi chưa phải là tối ưu (Vì với số do Excel cung cấp thì không ra kết quả), nhưng cách này thì nếu cho số đúng thì nó dò đúng.

vậy huynh đài thử lại UDFF của huynh đài với số 9899989/87569859 =0,113052471627252 xem thế nào
vì tại hạ thử thì nó cho bằng 867.047/7.669.421

-- Chúc vui--
 
Upvote 0
Thực ra bài toán này chỉ là một phần của bài toán :

Giải phương trình 3 ẩn, nếu nghiệm là phân số thì biểu hiện dưới dạng phân số.

Vì bài của bác BNTT ( Tìm mối quan hệ giữa 2 nhóm số cho trước) có mấy ẩn giải ra bằng các hàm định thức, tuy nhiên đó là số thập phân. Vậy làm cách nào để có thể lấy số chính xác của nghiệm.

Cảm ơn nhiều.

--CV--
 
Upvote 0
Cái này mình chưa chắc là sai đâu vì có thể có những đồng phân giống nhau mà. Giống như việc bạn nhập 2/4 nhưng kết quả lại ra 1/2 vậy. Đó gọi là phân số tối giản bạn à!
a|b|c| 0.113052472|7669421|867047|c=a*b 0.113052472|87569859|9899989|c=a*b 0.113052472|96040837|10857654|c=a*b
Đại khái là như vậy! Thân.
 
Lần chỉnh sửa cuối:
Upvote 0
Cái này mình chưa chắc là sai đâu vì có thể có những đồng phân giống nhau mà.
Giống như việc bạn nhập 2/4 nhưng kết quả lại ra 1/2 vậy. Đó gọi là phân số tối giản bạn à!
a|b|c|
0.113052472|7669421|867047|c=a*b
0.113052472|87569859|9899989|c=a*b
0.113052472|96040837|10857654|c=a*b
Đại khái là như vậy!
Thân.

Hai phân số : 9899989/87569859 867.047/7.669.421

hoàn toàn khác nhau , không thể tối giản được. Vì 9899989/867.047 = 11,4180534619231

Vì vậy tại hạ vẫn mong có một phương pháp khả dĩ tối ưu hơn.

PHP:
If Round(So * i, 7) = Round(So * i, 0) Then

Tham số làm tròn ở hàm Round thứ nhất rất quan trọng, càng lớn thì sự chính xác lại càng cao. Đây chính là một yếu tố để hàm của Ca_dafi trở thành chính xác nhất.

--CV--

 
Upvote 0
Nếu với số 0,113052471627252 mà muốn có kết quả 9899989/87569859 thì bạn dùng 1 trong hai code dưới.
PHP:
Function PhanSo(So As Double) As String
    Application.Volatile (False)
    Dim i As Long
    For i = 1 To 10 ^ Val(Len(So))
        If Round(So * i, 7) = Round(So * i, 0) Then
            lan = lan + 1
            If lan = 9 Then
            PhanSo = Format(Round(So * i, 0), "#,##0") & "/" & Format(i, "#,##0")
            Exit Function
            End If
        End If
    Next
End Function
PHP:
Function PhanSo(So As Double) As String
    Application.Volatile (False)
    Dim i As Long
    For i = 10 ^ Val(Len(So)) To 1 Step -1
        If Round(So * i, 7) = Round(So * i, 0) Then
            lan = lan + 1
            If lan = 2 Then
            PhanSo = Format(Round(So * i, 0), "#,##0") & "/" & Format(i, "#,##0")
            Exit Function
            End If
        End If
    Next
End Function
Không trúng thì không làm quen với bạn đâu! Thật đó.... Heeeee
Thân.
 
Upvote 0
Nếu với số 0,113052471627252 mà muốn có kết quả 9899989/87569859 thì bạn dùng 1 trong hai code dưới.
PHP:
Function PhanSo(So As Double) As String
    Application.Volatile (False)
    Dim i As Long
    For i = 1 To 10 ^ Val(Len(So))
        If Round(So * i, 7) = Round(So * i, 0) Then
            lan = lan + 1
            If lan = 9 Then
            PhanSo = Format(Round(So * i, 0), "#,##0") & "/" & Format(i, "#,##0")
            Exit Function
            End If
        End If
    Next
End Function
PHP:
Function PhanSo(So As Double) As String
    Application.Volatile (False)
    Dim i As Long
    For i = 10 ^ Val(Len(So)) To 1 Step -1
        If Round(So * i, 7) = Round(So * i, 0) Then
            lan = lan + 1
            If lan = 2 Then
            PhanSo = Format(Round(So * i, 0), "#,##0") & "/" & Format(i, "#,##0")
            Exit Function
            End If
        End If
    Next
End Function
Không trúng thì không làm quen với bạn đâu! Thật đó.... Heeeee
Thân.

Vẫn chưa được :
PHP:
Function PhanSo(So As Double) As String
    Application.Volatile (False)
    Dim i As Long
    For i = 1 To 10 ^ Val(Len(So))
        If Round(So * i, 7) = Round(So * i, 0) Then
            lan = lan + 1
            If lan = 9 Then
            PhanSo = Format(Round(So * i, 0), "#,##0") & "/" & Format(i, "#,##0")
            Exit Function
            End If
        End If
    Next
End Function

Với phân số : 1/123.000.000 = 0,000000008130081 thì UDF của tiên sinh = 0/5
Còn của Ca_dafi tiên sinh = 1/123.000.000

xem có cách nào tốt hơn không nhỉ ??
 
Upvote 0
Tại hạ có cảm giác rằng bài này nếu giải bằng nhị phân rất tốt, tuy nhiên lại chưa tìm được hướng ra chính xác.

Còn các cách trên vẫn là "vét cạn". Mong các cao thủ chỉ giáo.

--CV--
 
Upvote 0
Biểu diễn thành phân số

Nhờ Lệnh Hồ Đại Hiệp test giúp code sau:
Mã:
Function PhanSo(c As Double) As String
Const mLong = 2147483647 - 1
Const e = 0.0000000001
Dim a As Long, b As Long
Dim Found As Boolean
    b = 0
    Do While (b < mLong) And (Not Found)
       b = b + 1
       Found = Abs((b * c) - CLng(b * c)) <= e
    Loop
    PhanSo = CLng(b * c) & " / " & (b)
End Function

(tôi đã test với một vài trường hợp thì thấy khá đúng)
 

File đính kèm

Lần chỉnh sửa cuối:
Upvote 0
Cải tiến để hàm nhanh hơn khi mẫu số quá lớn (đối số quá nhỏ)

Mã:
Function PhanSo(d As Double) As String
Const mLong = 2147483647 - 1
Const e = 0.0000000001
Dim a As Double, b As Long, c As Double
Dim Found As Boolean
    If d = 0 Then
        PhanSo = "0"
        Exit Function
    End If
    If d < 1 Then c = 1 / d Else c = d
    b = 0
    Do While (b < mLong) And (Not Found)
       b = b + 1
       a = b * c
       Found = Abs((a) - CLng(a)) <= e
    Loop
    If d > 1 Then PhanSo = CLng(a) & " / " & (b) Else PhanSo = (b) & " / " & CLng(a)
End Function
 
Upvote 0
Cảm ơn bạn đã tham gia. Nhưng thật sự vấn đề này e rằng không có đáp án chính xác đâu. Nó chỉ có thể nằm trong 1 tầm định mức nào đó thôi. Riêng với code của bạn thì có 2 điểm cần lưu ý: +Nếu b > mLong thì code trên vô hiệu. +Nếu tỷ số lệch nằm ngoài e thì sao? Tức là nếu là số này thì sao: 1/12300000000 hoặc lớn hơn nữa vì đây mới 1E-11 à. Hàm Double lên đến 1E-308 lận. Và code này vẫn là code "vét cạn" và bị giới hạn chỉ số trên và chỉ số dưới. Vậy vấn đề này xin ngừng tại đây là được rồi. Càng tìm thì máy làm việc càng lâu và số càng lớn hơn thôi. Đi cùng với nó vẫn là giới hạn không thể vượt qua nổi đó chính là số do biến Double quy định 1E-308. Dù bạn có tính thế nào thì ngài Bill đã dự tính sẳn rồi, làm sao mà qua mặt được chứ.... Thân.
 
Lần chỉnh sửa cuối:
Upvote 0
Trước khi ngừng lại cho em góp vui thêm 1 code nữa đã nhé. Không biết có đúng không nhờ các bác test giúp.
Mã:
Function PhanSo(x As Double) As String
    Dim TuSo As Double, MauSo As Double
    Dim i As Double, Temp
    TuSo = 10 ^ (Len(x & "") - 1) * x
    MauSo = TuSo / x
    Dim bFound As Boolean
    Do While Not bFound And TuSo > 1
        bFound = True
        If WorksheetFunction.Floor(MauSo / TuSo, 1) = MauSo / TuSo Then
            MauSo = MauSo / TuSo
            TuSo = 1
            Exit Do
        Else
            Temp = WorksheetFunction.Ceiling(Sqr(TuSo), 1)
            For i = 2 To Temp
                If WorksheetFunction.Floor(TuSo / i, 1) = TuSo / i Then
                    If WorksheetFunction.Floor(MauSo / i, 1) = MauSo / i Then
                        TuSo = TuSo / i
                        MauSo = MauSo / i
                        bFound = False
                        Exit For
                    End If
                End If
            Next
        End If
    Loop
    PhanSo = TuSo & "/" & MauSo
End Function
 
Lần chỉnh sửa cuối:
Upvote 0
Cảm ơn bác. Bác đã xử lý được tình huống số 1 ở tử số rồi. Dòng "TuSo = 10 ^ (Len(x & "") - 1) * x" không ổn lắm. Và hiện thấy đáp số không chính xác, bác thử với số này xem 15235/1614512455 nó ra đáp số kỳ quá à. Thân.
 
Lần chỉnh sửa cuối:
Upvote 0
Cảm ơn bác. Bác đã xử lý được tình huống số 1 ở tử số rồi.
Dòng "TuSo = 10 ^ (Len(x & "") - 1) * x" không ổn lắm.
Và hiện thấy đáp số không chính xác, bác thử với số này xem 15235/1614512455 nó ra đáp số kỳ quá à.
Thân.
Xin lỗi các bác là dòng đó phải là thế này
Mã:
[COLOR=Black][B]TuSo = 10 ^ (Len(x & "") - InStr(1, x & "", ".")) * x[/B][/COLOR]
Em xin giải thích 1 chút về giải thuật trong đoạn code vừa rồi.
1. Đầu tiên đưa số thập phân x về dạng phân số chính xác bằng công thức
Mã:
    TuSo = 10 ^ (Len(x & "") - InStr(1, x & "", ".")) * x
    MauSo = TuSo / x
Ví dụ: 12.345 sẽ thành 12345/1000
Đây chính là phân số chính xác nhất của số thập phân.
2. Bước tiếp theo là tối giản phân số ban đầu bằng cách: Với 1 cặp tử số và mẫu số, ta cho i chạy từ 2 đến căn bậc 2 của Tử số, nếu cả tử và mẫu đều chia hết cho i thì thực hiện tính lại tử số và mẫu số bằng cách chia cả 2 cho i.
3. Lặp lại bước 2 cho tới khi từ 2 cho đến căn bậc 2 của tử số mà không tìm thấy số i thỏa mãn cả tử và mẫu đều chia hết thì dừng lại.
- Với thuật giải này thì dễ thấy là số vòng lặp sẽ giảm đi rất nhiều do số mà ta xét ở đây liên tục được chia cho 1 số.
(Đến đây em mới nhớ ra là mới xét trường hợp Mẫu chia hết cho Tử mà chưa xét đến Tử chỉ hết cho Mẫu, 1 chút nữa là việc duyệt không nhất thiết phải duyệt với Tử số mà ta sẽ duyệt số nhỏ hơn giữa tử số và mẫu số. Do trước em chỉ test trường hợp số thập phân < 0 nên quên mất ý này)
Xin được cập nhật lại code như sau:
Mã:
Function PhanSo(x As Double) As String
    Dim TuSo As Double, MauSo As Double
    Dim i As Double, Temp
    TuSo = 10 ^ (Len(x & "") - InStr(1, x & "", ".")) * x
    MauSo = TuSo / x
    Dim bFound As Boolean
    Do While Not bFound And TuSo > 1
        bFound = True
        If WorksheetFunction.Floor(MauSo / TuSo, 1) = MauSo / TuSo Then
            MauSo = MauSo / TuSo
            TuSo = 1
            Exit Do
        ElseIf WorksheetFunction.Floor(TuSo / MauSo, 1) = TuSo / MauSo Then
            TuSo = TuSo / MauSo
            MauSo = 1
            Exit Do
        Else
            Temp = WorksheetFunction.Ceiling(Sqr(TuSo), 1)
            If TuSo > MauSo Then Temp = WorksheetFunction.Ceiling(Sqr(MauSo),1)
            For i = 2 To Temp
                If WorksheetFunction.Floor(TuSo / i, 1) = TuSo / i Then
                    If WorksheetFunction.Floor(MauSo / i, 1) = MauSo / i Then
                        TuSo = TuSo / i
                        MauSo = MauSo / i
                        bFound = False
                        Exit For
                    End If
                End If
            Next
        End If
    Loop
    PhanSo = Format(TuSo, "#,##0") & "/" & Format(MauSo, "#,##0")
End Function
Chú ý: Các bác test thì nên test theo hướng xuất phát từ 1 số thập phân thay cho từ 1 phân số. Vì nếu test xuất phát từ 1 phân số thì trong quá trình chia để trả về kết quả có thể excel đã làm tròn mất rồi.
 
Lần chỉnh sửa cuối:
Upvote 0
Web KT

Bài viết mới nhất

Back
Top Bottom