Как я могу преобразовать десятичную дробь? - PullRequest
8 голосов
/ 01 апреля 2009

Как преобразовать неопределенное десятичное число (т. Е. 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

Ответы [ 13 ]

18 голосов
/ 01 апреля 2009

Поскольку .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 и так далее. Поэтому попробуйте просто найти шаблон и установить знаменатель на соответствующее число. Конечно, вам нужно остановиться после нескольких цифр, потому что вы никогда не узнаете, повторяется ли шаблон навсегда или просто много раз. И вы получите ошибку округления в последней цифре, так что вам все еще нужна какая-то умная логика.

6 голосов
/ 17 мая 2009

Это работает только в Excel-VBA, но так как вы пометили его как "VBA", я предложу это. В Excel есть собственный формат «дроби», к которому вы можете получить доступ через «Формат ячеек» (или ctrl-1, если хотите). Этот конкретный числовой формат специфичен для Excel и поэтому не работает с функцией VBA.Format. Он делает , однако работает с формулой Excel TEXT (). (Что является эквивалентом VBA.Format для Excel. Доступ к нему можно получить следующим образом:

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

Чтобы отобразить более одной цифры (пример 5/12), просто увеличьте количество знаков вопроса.

2 голосов
/ 01 апреля 2009

Этот сайт , кажется, имеет действительно хорошую реализацию этого в JavaScript.

2 голосов
/ 01 апреля 2009

Google для "десятичной дроби", и вы получите около тысячи результатов.

Мне действительно нравится этот , потому что он простой, имеет исходный код (в RPL, похожий на Forth, ~ 25 строк) и довольно быстрый (он написан для работы на 4-битной, 4 МГц процессор). Документы говорят:

В книге Г. Кристалла под названием Учебник алгебры , 1-й издание в 1889 году, в части II, глава 32, эта улучшенная продолжение фракции алгоритм представлен и доказан. Как ни странно, Chrystal говорит об этом, как будто это были древние знания.

1 голос
/ 02 апреля 2009

В Python есть хорошая подпрограмма в модуле фракций. Вот рабочая часть, которая преобразует н / д в ближайшее приближение N / D, где D <= некоторое максимальное значение. например если вы хотите найти ближайшую дробь к 0,347, пусть n = 347, d = 1000 и max_denominator будут равны 100, и вы получите (17, 49), которое настолько близко, насколько вы можете получить для знаменателей, меньших или равных 100. Оператор '//' является целочисленным делением, поэтому 2 // 3 дает 0, то есть a // b = int (a / b). </p>

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
1 голос
/ 01 апреля 2009

Похоже на 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, и я могу настроить его.

РЕДАКТИРОВАТЬ: Извините за выдуманный синтаксис подсветки. Я не могу понять, почему это все не так. Если кто-то знает, как я могу сделать это лучше, пожалуйста, дайте мне знать.

1 голос
/ 01 апреля 2009

В общем, будет проще, если вы найдете повторяющуюся часть рационального числа. Если вы не можете найти это, вам будет тяжело. Скажем, число, если 8.45735735735 ...

Ответ: 8 + 45/100 + 735/999/100 = 8 1523 / 3330.

Всего 8. Добавьте 45/100, то есть .45, часть перед повторяющейся частью.

Повторяющаяся часть - 735/999. В общем, примите повторяющуюся часть. Сделайте это числителем. Знаменатель равен 10 ^ (количество повторяющихся цифр) - 1.

Возьмите повторяющуюся часть и сдвиньте ее на соответствующее количество цифр. В данном случае два, что означает деление на 100, то есть 735/999/100.

Как только вы разберетесь с этими частями, вам просто понадобится код, который добавляет и уменьшает дроби, используя наибольшие общие дроби ...

1 голос
/ 01 апреля 2009

Вы можете приблизить его. По сути, циклически перебирайте все числители и знаменатели, пока не достигнете дроби, близкой к той, которую вы хотите.

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

Это медленный и грубый алгоритм, но вы должны получить общее представление.

1 голос
/ 01 апреля 2009

Я бы умножил на 10000000 (или что угодно, в зависимости от точности), а затем упростил бы полученную дробь (т.е. n * 10000000/10000000)

0 голосов
/ 10 февраля 2016

Я знаю, что это старая тема, но я столкнулся с этой проблемой в Word VBA. Есть так много ограничений из-за 8-битного (16-значного) округления, а также из-за того, что Word VBA преобразует десятичные дроби в научную нотацию и т. Д., Но после работы над всеми этими проблемами у меня есть хорошая функция, которой я хотел бы поделиться: несколько дополнительных функций, которые вы можете найти полезными.

Стратегия соответствует тому, что написал Дэниел Бакнер. В принципе: 1) решить, является ли это десятичным символом в конце или нет 2) Если да, просто установите десятичный хвост / 10 ^ n и уменьшите дробь.
3-й) Если это не заканчивается, попробуйте найти повторяющийся шаблон, включая случаи, когда повторение не начинается сразу

Перед тем, как опубликовать функцию, вот несколько моих наблюдений за рисками и ограничениями, а также некоторые заметки, которые могут помочь вам понять мой подход.

Риски, ограничения, объяснения:

-> Необязательный параметр "denom" позволяет указать знаменатель дроби, если вы хотите, чтобы он был округлен. то есть для дюймов вы можете использовать 16-е. Фракции все равно будут сокращены, поэтому 3.746 -> 3 12/16 -> 3 3/4

-> Необязательный параметр «builddup», установленный в True, будет строить дробь с помощью редактора формул, вводя текст прямо в активный документ. Если вы предпочитаете, чтобы функция просто возвращала плоское строковое представление дроби, чтобы вы могли сохранить ее программно и т. Д., Установите для этого значение значение False.

-> Десятичная дробь может завершиться после нескольких повторений ... эта функция будет предполагать бесконечное повторение.

-> Тип переменной Double меняет целую цифру на десятичную, позволяя всего 16 цифр (по моим наблюдениям в любом случае!). Эта функция предполагает, что если число использует все 16 доступных цифр, то оно должно быть повторяющимся десятичным числом. Большое число, такое как 123456789876.25, будет ошибочно принято за повторяющееся десятичное число, а затем будет возвращено как десятичное число при невозможности найти шаблон.

-> Чтобы выразить действительно большой конечный десятичный знак из 10 ^ n, VB может обрабатывать только 10 ^ 8. Я округляю порядковое число до 8 знаков после запятой, возможно, теряя некоторую точность.

-> Для математического анализа преобразования повторяющихся шаблонов в дроби отметьте эту ссылку

-> Использовать Евклидов Алгоритм для уменьшения дроби

Хорошо, вот оно, написанное как макрос 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...