如何将小数转换为分数?

时间:2009-04-01 16:57:21

标签: vba

如何将不定小数(即.333333333 ...)转换为字符串分数表示(即“1/3”)。我正在使用VBA,以下是我使用的代码(我在“b = a Mod b”行时遇到溢出错误:

Function GetFraction(ByVal Num As Double) As String

    If Num = 0# Then
        GetFraction = "None"
    Else
        Dim WholeNumber As Integer
        Dim DecimalNumber As Double
        Dim Numerator As Double
        Dim Denomenator As Double
        Dim a, b, t As Double

        WholeNumber = Fix(Num)
        DecimalNumber = Num - Fix(Num)
        Numerator = DecimalNumber * 10 ^ (Len(CStr(DecimalNumber)) - 2)
        Denomenator = 10 ^ (Len(CStr(DecimalNumber)) - 2)
        If Numerator = 0 Then
            GetFraction = WholeNumber
        Else
            a = Numerator
            b = Denomenator
            t = 0

            While b <> 0
                t = b
                b = a Mod b
                a = t
            Wend
            If WholeNumber = 0 Then
                GetFraction = CStr(Numerator / a) & "/" & CStr(Denomenator / a)
            Else
                GetFraction = CStr(WholeNumber) & " " & CStr(Numerator / a) & "/" & CStr(Denomenator / a)
            End If
        End If
    End If
End Function

14 个答案:

答案 0 :(得分:18)

如果你没有添加一些聪明的“非舍入”逻辑,那么.333333333不是1/3,你将永远不会得到1/3而是333333333/1000000000。

这是一个处理数字的解决方案,我记得从学校开始,它是周期性的十进制表示法。

数字0. abcd abcd ...等于abcd / 9999。所以0. 2357 2357 ......完全等于2357/9999。因为你的模式很长,所以要花很多9个。 0. 1 1111 ...等于1 / 9,0 12 1212 ...等于12/99,依此类推。因此,尝试只搜索模式并将分母设置为相应的数字。当然,你必须在一些数字之后停下来,因为你永远不会知道这个模式是永久重复还是只重复多次。并且您将在最后一位数字中遇到舍入误差,因此您仍需要一些聪明的逻辑。

答案 1 :(得分:6)

这仅适用于Excel-VBA,但由于您将其标记为“VBA”,我会建议它。 Excel具有自定义“分数”格式,您可以通过“格式化单元格”(或者如果您愿意,可以使用ctrl-1)访问该格式。此特定数字格式是Excel特定的,因此不适用于VBA.Format函数。它 然后使用Excel公式TEXT()。 (这与Excel相当于VBA.Format。可以像So:

那样访问
Sub Example()    
    MsgBox Excel.WorksheetFunction.Text(.3333,"# ?/?")
End Sub

要显示多个数字(例5/12),只需显示问号数。

答案 2 :(得分:2)

谷歌的“十进制分数”,你将得到大量的结果。

我真的很喜欢this one,因为它很简单,有源代码(在RPL中,类似于Forth,~25行),并且非常快(它编写为在4位,4MHz CPU上运行) 。文档说:

  

由G. Chrystal编写的一本名为代数教科书的书   在1889年的第二部分第32章中,这改进了连续分数   算法被提出并证明。奇怪的是,Chrystal说它就像它一样   是古老的知识。

答案 3 :(得分:2)

This site似乎在JavaScript中有一个很好的实现。

答案 4 :(得分:1)

我会乘以10000000(或任何你想要的,具体取决于精度),然后简化得到的分数(即n * 10000000/10000000)

答案 5 :(得分:1)

你可以近似它。基本上循环遍历所有分子和分母,直到达到接近你想要的分数。

int num = 1;
int den = 1;
double limit == 0.1;
double fraction = num / den;

while(den < 1000000 ) // some arbitrary large denominator
{
    den = den + 1;    
    for(num = 0; num <= den; num++)
    {
        fraction = num / den;
        if(fraction < n + limit && fraction > n - limit)
             return (num + "/" + den);
    }
}

这很慢并且是一种蛮力算法,但你应该得到一般的想法。

答案 6 :(得分:1)

一般来说,如果找到有理数的重复部分会更容易。如果你找不到,那你将度过难关。让我们说一下如果是8.45735735735 ...

答案是8 + 45/100 + 735/999/100 = 8 1523/3330。

整数是8。 添加45/100 - 这是.45,重复部分之前的部分。

重复部分是735/999。一般来说,采取重复的部分。把它作为分子。分母是10 ^(重复数字的数量) - 1.

取重复部分并将其移动适当的位数。在这种情况下,两个,即除以100,即735/999/100。

一旦你想出这些部分,你只需要一些代码,使用最常见的分数来增加和减少分数......

答案 7 :(得分:1)

与CookieOfFortune类似,但它在VB中并没有使用那么多暴力。

Dim tolerance As Double = 0.1   'Fraction has to be at least this close'
Dim decimalValue As Double = 0.125  'Original value to convert'
Dim highestDenominator = 100   'Highest denominator you`re willing to accept'

For denominator As Integer = 2 To highestDenominator - 1
    'Find the closest numerator'
    Dim numerator As Integer = Math.Round(denominator * decimalValue)

    'Check if the fraction`s close enough'
    If Abs(numerator / denominator - decimalValue) <= tolerance Then
        Return numerator & "/" & denominator
    End If
Next

'Didn't find one.  Use the highest possible denominator'
Return Math.Round(denominator * decimalValue) & "/" & highestDenominator

...如果需要考虑大于1的值,请告诉我,我可以调整它。

编辑:抱歉,语法高亮显示。我无法弄清楚为什么一切都错了。如果有人知道如何让它变得更好,请告诉我。

答案 8 :(得分:1)

Python在其分数模块中有一个很好的例程。这是将n / d转换为最接近的近似N / D的工作部分,其中D <=某个最大值。例如如果你想找到最接近0.347的分数,设n = 347,d = 1000,max_denominator为100,你将获得(17,49),它与你的分母小于或等于100的距离最近。 '//'运算符是整数除法,因此2 // 3给出0,即// b = int(a / b)。

def approxFrac(n,d,max_denominator):

    #give a representation of n/d as N/D where D<=max_denominator
    #from python 2.6 fractions.py
    #
    # reduce by gcd and only run algorithm if d>maxdenominator
    g, b = n, d
    while b:
        g, b = b, g%b
    n, d = n/g, d/g
    if d <= max_denominator:
        return (n,d)
    nn, dd = n, d
    p0, q0, p1, q1 = 0, 1, 1, 0
    while True:
        a = nn//dd
        q2 = q0+a*q1
        if q2 > max_denominator:
            break
        p0, q0, p1, q1 = p1, q1, p0+a*p1, q2
        nn, dd = dd, nn-a*dd

    k = (max_denominator-q0)//q1
    bound1 = (p0+k*p1, q0+k*q1)
    bound2 = (p1, q1)
    if abs(bound2[0]*d - bound2[1]*n) <= abs(bound1[0]*d - bound1[1]*n):
        return bound2
    else:
        return bound1

答案 9 :(得分:0)

1 / .3333333333 = 3因为1/3 = .3333333333333,所以无论你得到什么数字,

double x = 1 / yourDecimal; int y = Math.Ceil(x);

现在显示“1 /”+ y

答案 10 :(得分:0)

它并不总是可溶的,因为并非所有小数都是分数(例如PI或e)。

另外,在转换之前,你必须将你的小数舍入到一定长度。

答案 11 :(得分:0)

我知道这是一个旧线程,但我在Word VBA中遇到了这个问题。由于8位(16位)舍入,以及Word VBA将小数写入科学记数法等,因此有很多限制。但在解决了所有这些问题后,我有一个很好的功能我想分享它提供了一些您可能会发现有用的额外功能。

该战略与丹尼尔·巴克纳所写的一致。基本上: 1)确定它是否是终止小数 2)如果是,只需设置小数尾/ 10 ^ n并减少分数 3)如果它没有终止,试着找一个重复模式,包括重复不立即开始的情况

在发布该功能之前,以下是我对风险和限制的一些观察,以及可能有助于您理解我的方法的一些注释。

风险,限制,解释:

- &GT;可选参数&#34; denom&#34;允许您指定分数的分母,如果您喜欢它四舍五入。即对于英寸,你可能需要使用16。然而,分数仍将减少,因此3.746 - > 3 12/16 - &gt; 3 3/4

- &GT;可选参数&#34; builddup&#34;设置为True将使用公式编辑器构建分数,在活动文档中键入文本。如果您希望函数只返回分数的扁平字符串表示,那么您可以以编程方式存储它等。将此设置为False。

- &GT;十进制可以在一堆重复之后终止......这个函数将假定无限重复。

- &GT;变量类型Double换掉十进制数字的整数位数,总共只允许16位数(从我的观察结果来看!)。此函数假定如果一个数字使用所有16​​个可用数字,则它必须是重复小数。如123456789876.25这样的大数字会被误认为是重复的小数,然后在找不到模式时以十进制数的形式返回。

- &GT;为了表达10 ^ n中真正大的终止小数,VB似乎只能处理10 ^ 8。我将origninal数字四舍五入到小数点后8位,可能会失去一些准确性。

- &GT;对于将重复模式转换为分数的数学运算,请检查this link

- &GT;使用Euclidean Algorithm减少分数

好的,这里写的是Word宏:

Function as_fraction(number_, Optional denom As Integer = -1, Optional buildup As Boolean = True) As String
    'Selection.TypeText Text:="Received: " & CStr(number_) & vbCrLf
    Dim number As Double
    Dim repeat_digits As Integer, delay_digits As Integer, E_position As Integer, exponent As Integer
    Dim tail_string_test As String, tail_string_original As String, num_removed As String, tail_string_removed As String, removed As String, num As String, output As String
    output = "" 'string variable to build into the fraction answer
    number = CDbl(number_)
    'Get rid of scientific notation since this makes the string longer, fooling the function length = digits
    If InStr(CStr(number_), "E+") > 0 Then 'no gigantic numbers! Return that scientific notation junk
        output = CStr(number_)
        GoTo all_done
    End If

    E_position = InStr(CStr(number), "E") 'E- since postives were handled
    If E_position > 0 Then
        exponent = Abs(CInt(Mid(CStr(number), E_position + 1)))
        num = Mid(CStr(number_), 1, E_position) 'axe the exponent
        decimalposition = InStr(num, ".") 'note the decimal position
        For i_move = 1 To exponent
            'move the decimal over, and insert a zero if the start of the number is reached
            If InStr(num, "-") > 0 And decimalposition = 3 Then 'negative sign in front
               num = "-0." & Mid(num, InStr(num, ".") - 1, 1) & Mid(num, InStr(num, ".") + 1) 'insert a zero after the negative
            ElseIf decimalposition = 2 Then
               num = "0." & Mid(num, InStr(num, ".") - 1, 1) & Mid(num, InStr(num, ".") + 1) 'insert in front
            Else 'move the decimal over, there are digits left
               num = Mid(num, 1, decimalposition - 2) & "." & Mid(num, decimalposition - 1, 1) & Mid(num, decimalposition + 1)
               decimalposition = decimalposition - 1
            End If
        Next
    Else
        num = CStr(number_)
    End If
    'trim the digits to 15, since VB rounds the last digit which ruins the pattern. i.e. 0.5555555555555556  etc.
    If Len(num) >= 16 Then
        num = Mid(num, 1, 15)
    End If
    number = CDbl(num) 'num is a string representation of the decimal number, just to avoid cstr() everywhere
    'Selection.TypeText Text:="number = " & CStr(number) & vbCrLf

    'is it a whole number?
    If Fix(number) = number Then 'whole number
        output = CStr(number)
        GoTo all_done
    End If

    decimalposition = InStr(CStr(num), ".")
    'Selection.TypeText Text:="Attempting to find a fraction equivalent for " & num & vbCrLf
    'is it a repeating decimal? It will have 16 digits
    If denom = -1 And Len(num) >= 15 Then 'repeating decimal, unspecified denominator
        tail_string_original = Mid(num, decimalposition + 1) 'digits after the decimal
        delay_digits = -1 'the number of decimal place values removed from the tail, in case the repetition is delayed. i.e. 0.567777777...
        Do 'loop through start points for the repeating digits
            delay_digits = delay_digits + 1
            If delay_digits >= Fix(Len(tail_string_original) / 2) Then
                'Selection.TypeText Text:="Tried all starting points for the pattern, up to half way through the tail.  None was found.  I'll treat it as a terminating decimal." & vbCrLf
                GoTo treat_as_terminating
            End If
            num_removed = Mid(num, 1, decimalposition) & Mid(num, decimalposition + 1 + delay_digits) 'original number with decimal values removed
            tail_string_removed = Mid(num_removed, InStr(CStr(num_removed), ".") + 1)
            repeat_digits = 0 'exponent on 10 for moving the decimal place over
            'Selection.TypeText Text:="Searching " & num_removed & " for a pattern:" & vbCrLf
            Do
                repeat_digits = repeat_digits + 1
                If repeat_digits = Len(tail_string_removed) - 1 Or repeat_digits >= 9 Then 'try removing a digit, incase the pattern is delayed
                    Exit Do
                End If
                tail_string_test = Mid(num_removed, decimalposition + 1 + repeat_digits)
                'Selection.TypeText Text:=vbTab & "Comparing " & Mid(tail_string_removed, 1, Len(tail_string_removed) - repeat_digits) & " to " & tail_string_test & vbCrLf
                If Mid(tail_string_removed, 1, Len(tail_string_removed) - repeat_digits) = tail_string_test Then
                    'Selection.TypeText Text:=num & ", " & Mid(tail_string_removed, 1, Len(tail_string_removed) - repeat_digits) & " vs " & tail_string_test & vbCrLf
                    GoTo foundpattern
                End If
            Loop

        Loop 'next starting point for pattern

foundpattern:
        If delay_digits = 0 Then 'found pattern right away
            numerator = CLng(Mid(CStr(number), decimalposition + 1 + delay_digits, CInt(repeat_digits)))

            'generate the denominator nines, same number of digits as the numerator
            bottom = ""
            For i_loop = 1 To repeat_digits
                bottom = bottom & "9"
            Next
            denominator = CLng(bottom)
        Else 'there were numbers before the pattern began
            numerator = CLng(Mid(num, decimalposition + 1, delay_digits + repeat_digits)) - CLng(Mid(num, decimalposition + 1, delay_digits))
            'i.e. x = 2.73232323232...  delay_digits = 1, repeat_digits = 2, so numerator = 732 - 7 = 725
            bottom = ""
            For i_loop = 1 To repeat_digits
                bottom = bottom & "9"
            Next
            For i_loop = 1 To delay_digits
                bottom = bottom & "0"
            Next
            denominator = CLng(bottom)
            'i.e. 990...  725/990 = 145/198 = 0.7323232...
        End If



    Else ' terminating decimal
treat_as_terminating:
       'grab just the decimal trail
       If denom = -1 Then
            number = Math.Round(number, 8) 'reduce to fewer decimal places to avoid overload
             'is it a whole number now?
            If Fix(number) = number Then 'whole number
                output = CStr(number)
                GoTo all_done
            End If
            num = CStr(number)
            numerator = CLng(Mid(num, decimalposition + 1))
            denominator = 10 ^ (Len(num) - InStr(num, "."))
       Else 'express as a fraction rounded to the nearest denom'th reduced
            numerator1 = CDbl("0" & Mid(CStr(num), decimalposition))
            numerator = CInt(Math.Round(numerator1 * denom))
            denominator = CInt(denom)
       End If
    End If

    'reduce the fraction if possible using Euclidean Algorithm
    a = CLng(numerator)
    b = CLng(denominator)
    Dim t As Long
    Do While b <> 0
        t = b
        b = a Mod b
        a = t
    Loop
    gcd_ = a

    numerator = numerator / gcd_
    denominator = denominator / gcd_
    whole_part = CLng(Mid(num, 1, decimalposition - 1))


    'only write a whole number if the number is absolutely greater than zero, or will round to be so.
    If whole_part <> 0 Or (whole_part = 0 And numerator = denominator) Then
        'case where fraction rounds to whole
        If numerator = denominator Then
            'increase the whole by 1 absolutely
            whole_part = (whole_part / Abs(whole_part)) * (Abs(whole_part) + 1)
        End If
        output = CStr(whole_part) & " "

    End If

    'if fraction rounded to a whole, it is already included in the whole number
    If numerator <> 0 And numerator <> denominator Then
        'negative sign may have been missed, if whole number was -0
        If whole_part = 0 And number_ < 0 Then
            numerator = -numerator
        End If
        output = output & CStr(numerator) & "/" & CStr(denominator) & " "

    End If
    If whole_part = 0 And numerator = 0 Then
        output = "0"
    End If
all_done:
    If buildup = True Then 'build up the equation with a pretty fraction at the current selection range
        Dim objRange As Range
        Dim objEq As OMath
        Dim AC As OMathAutoCorrectEntry
        Application.OMathAutoCorrect.UseOutsideOMath = True
        Set objRange = Selection.Range
        objRange.Text = output
        For Each AC In Application.OMathAutoCorrect.Entries
            With objRange
                If InStr(.Text, AC.Name) > 0 Then
                    .Text = Replace(.Text, AC.Name, AC.Value)
                End If
            End With
        Next AC
        Set objRange = Selection.OMaths.Add(objRange)
        Set objEq = objRange.OMaths(1)
        objEq.buildup

        'Place the cursor at the end of the equation, outside of the OMaths object
        objRange.OMaths(1).Range.Select
        Selection.Collapse direction:=wdCollapseEnd
        Selection.MoveRight Unit:=wdCharacter, count:=1
        as_fraction = "" 'just a dummy return to make the function happy
    Else 'just return a flat string value
        as_fraction = output
    End If
End Function

答案 12 :(得分:0)

我在此链接上分享了一个答案:https://stackoverflow.com/a/57517128/11933717

它也是一个迭代函数,但是与在嵌套循环中查找分子和分母不同,它仅测试分子,因此应该更快。

这是它的工作方式:

它假定基于用户输入x,您想要找到2个整数n / m。

  • n / m = x,表示
  • n / x应该给出一个几乎整数m

说一个需要找到x = 2.428571的分数。将int 2放置以备后用,算法将从设置n和x开始并迭代n:

// n / x       = m ( we need m to be an integer )
// n = 1 ; x = .428571 ;
   1 / .428571 = 2.333335 (not close to an integer, n++)
   2 / .428571 = 4.666671 (not close to an integer, n++)
   3 / .428571 = 7.000007

在此时n = 3,我们认为m = 7.000007是整数足够了-基于程序员决定的某种精度-并回复用户

2.428571 =   2  + 3/7 
         = 14/7 + 3/7
         = 17/7

答案 13 :(得分:-1)

你试过这个routine吗?