【问题标题】:Convert fraction to string with repeating decimal places in brackets将分数转换为括号中重复小数位的字符串
【发布时间】:2016-07-31 11:35:19
【问题描述】:

我想在 Python 3 中编写一个函数,它将以分子和分母给出的分数转换为十进制数的字符串表示形式,但括号中的小数位重复。

一个例子:

  • convert(1, 4) 应该输出 "0.25"
  • convert(1, 3) 应该输出 "0.(3)" 而不是 "0.3333333333"
  • convert(7, 11) 应该输出 "0.(63)" 而不是 "0.6363636364"
  • convert(29. 12) 应该输出 "2.41(6)" 而不是 "2.4166666667"

我当前的代码在问题的末尾,但如果有不重复的 重复小数位,它会失败。这是一个包含调试输出的示例运行(注释 print 调用):

----> 29 / 12
5
appended 4
2
appended 1
8
index 2 ['29', 2, 8] result ['2.', '4', '(', '1']
repeating 8
['2.', '4', '(', '1', ')']

我在这里做错了什么?


我的代码:

def convert(numerator, denominator):
    #print("---->", numerator, "/", denominator)
    result = [str(numerator//denominator) + "."]
    subresults = [str(numerator)]
    numerator %= denominator
    while numerator != 0:
        #print(numerator)
        numerator *= 10
        result_digit, numerator = divmod(numerator, denominator)
        if numerator not in subresults:
            subresults.append(numerator)
            result.append(str(result_digit))
            #print("appended", result_digit)
        else:
            result.insert(subresults.index(numerator), "(")
            #print("index", subresults.index(numerator), subresults, "result", result)
            result.append(")")
            #print("repeating", numerator)
            break
    #print(result)
    return "".join(result)

【问题讨论】:

    标签: python math number-formatting fractions


    【解决方案1】:

    我认为错误在于您应该只检查以前看到的小数位数是否是循环长度的数字,并且它是在这个长度之前看到的。

    我认为最好的方法是使用一些好的数学。

    让我们尝试设计一种方法来查找分数的小数表示以及如何知道何时会有重复小数。

    了解分数是否会终止(或重复)的最佳方法是查看分母的因式分解(难题)。

    求因式分解的方法有很多,但我们真正想知道的是,这个数是否有除 2 或 5 以外的质因数。为什么?那么十进制扩展只是一些数字a / 10 * b。也许 1/2 = .5 = 5/10。 1/20 = .05 = 5/100。等等

    所以 10 的因数是 2 和 5,所以我们想知道它是否还有除 2 和 5 以外的其他因数。完美,很简单,只要继续除以 2,直到它不再能被 2 整除,比对 5 做同样的事情。或者反过来。

    在我们开始做一些严肃的工作之前,首先我们可能想知道它是否可以被 2 或 5 整除。

    def div_by_a_or_b( a, b, number):
        return not ( number % a ) or not ( number % b )
    

    然后我们将所有的五除以所有的二并检查数字是否为1

    def powers_of_only_2_or_5(number):
        numbers_to_check = [ 2, 5 ]
        for n in numbers_to_check:
            while not number % n: # while it is still divisible by n
                number = number // n # divide it by n
        return number == 1 # if it is 1 then it was only divisble by the numbers in numbers_to_check
    

    我把它变得更加多态,所以如果你想改变基础,你可以改变它。 (您所需要的只是该基数的因子,例如在基数 14 中,您检查 2 和 7 而不是 2 和 5)

    现在剩下要做的就是找出我们在非终止/重复分数的情况下要做什么。

    现在这是超级数论,所以我将把算法留给你,让你决定是否要在mathforum.orgwolfram alpha 上了解更多信息

    现在我们可以很容易地判断一个分数是否会终止,如果不是,那么它的重复数字循环的长度是多少。现在剩下要做的就是找到循环或它将从多少位开始。

    在寻找有效算法的过程中,我在https://softwareengineering.stackexchange.com/ 上发现了这篇文章,应该会有所帮助。

    some great insight - "当一个 (m,n)=1 的有理数 m/n 展开时,周期从 s 项开始,长度为 t,其中 s 和 t 是满足的最小数

    10^s=10^(s+t) (mod n)。 "

    所以我们需要做的就是找到 s 和 t:

    def length_of_cycle(denominator):
        mods = {}
        for i in range(denominator):
            key = 10**i % denominator
            if key in mods:
                return [ mods[key], i ]
            else:
                mods[ key ] = i
    

    让我们生成展开的数字

    def expasionGenerator( numerator, denominator ):
        while numerator:
            yield numerator // denominator
            numerator = ( numerator % denominator ) * 10
    

    现在要小心使用它,因为它会在重复扩展中创建一个无限循环(应该如此)。

    现在我认为我们拥有编写函数的所有工具:

    def the_expansion( numerator, denominator ):   
    # will return a list of two elements, the first is the expansion 
    # the second is the repeating digits afterwards
    # the first element's first 
        integer_part = [ numerator // denominator ]
        numerator %= denominator
        if div_by_a_or_b( 2, 5, denominator ) and powers_of_only_2_or_5( denominator ):
            return [ integer_part, [ n for n in expasionGenerator( numerator, denominator ) ][1:], [0] ]
        # if it is not, then it is repeating
        from itertools import islice
        length_of_cycle = cycleLength( denominator )
        generator = expasionGenerator( numerator*10, denominator ) 
        # multiply by 10 since we want to skip the parts before the decimal place
        list_of_expansion = [ n for n in islice(generator, length_of_cycle[0]) ] 
        list_of_repeating = [ n for n in islice(generator, length_of_cycle[1]) ]
        return [ integer_part, list_of_expansion, list_of_repeating ] 
    

    现在剩下的就是打印它,这应该不会太糟糕。我将首先构建一个将数字列表转换为字符串的函数:

    def listOfNumbersToString(the_list):
        string = ""
        for n in the_list:
            string += str(n)
        return string
    

    然后创建转换函数:

    def convert(numerator, denominator):
        expansion = the_expansion(numerator,denominator)
        expansion = [ listOfNumbersToString(ex) for ex in expansion ]
        return expansion[0] + "." + expansion[1] + "(" + expansion[2] + ")"
    

    http://thestarman.pcministry.com/ 和一个类似的问题on stackoverflow 上阅读有关该主题的有趣文章

    【讨论】:

      【解决方案2】:

      您的代码只需要一些小改动(参见下面的 cmets):

      def convert(numerator, denominator):
          #print("---->", numerator, "/", denominator)
          result = [str(numerator//denominator) + "."]
          subresults = [numerator % denominator]          ### changed ###
          numerator %= denominator
          while numerator != 0:
              #print(numerator)
              numerator *= 10
              result_digit, numerator = divmod(numerator, denominator)
              result.append(str(result_digit))             ### moved before if-statement
      
              if numerator not in subresults:
                  subresults.append(numerator)
                  #print("appended", result_digit)
      
              else:
                  result.insert(subresults.index(numerator) + 1, "(")   ### added '+ 1'
                  #print("index", subresults.index(numerator), subresults, "result", result)
                  result.append(")")
                  #print("repeating", numerator)
                  break
          #print(result)
          return "".join(result)
      

      【讨论】:

        【解决方案3】:

        这并不能回答您的实际问题(“为什么我的代码不起作用?”),但它可能对您还是有用的。几个月前,我写了一些代码来做你现在想做的事情。在这里。

        import itertools
        
        #finds the first number in the sequence (9, 99, 999, 9999, ...) that is divisible by x.
        def first_divisible_repunit(x):
            assert x%2 != 0 and x%5 != 0
            for i in itertools.count(1):
                repunit = int("9"*i)
                if repunit % x == 0:
                    return repunit
        
        #return information about the decimal representation of a rational number.
        def form(numerator, denominator):    
            shift = 0
            for x in (10,2,5):
                while denominator % x == 0:
                    denominator //= x
                    numerator *= (10//x)
                    shift += 1
            base = numerator // denominator
            numerator = numerator % denominator
            repunit = first_divisible_repunit(denominator)
            repeat_part = numerator * (repunit // denominator)
            repeat_size = len(str(repunit))
            decimal_part = base % (10**shift)
            integer_part = base // (10**shift)
            return integer_part, decimal_part, shift, repeat_part, repeat_size
        
        def printable_form(n,d):
            integer_part, decimal_part, shift, repeat_part, repeat_size = form(n,d)
            s = str(integer_part)
            if not (decimal_part or repeat_part):
                return s
            s = s + "."
            if decimal_part or shift:
                s = s + "{:0{}}".format(decimal_part, shift)
            if repeat_part:
                s = s + "({:0{}})".format(repeat_part, repeat_size)
            return s
        
        test_cases = [
            (1,4),
            (1,3),
            (7,11),
            (29, 12),
            (1, 9),
            (2, 3),
            (9, 11),
            (7, 12),
            (1, 81),
            (22, 7),
            (11, 23),
            (1,97),
            (5,6),
        ]
        
        for n,d in test_cases:
            print("{} / {} == {}".format(n, d, printable_form(n,d)))
        

        结果:

        1 / 4 == 0.25
        1 / 3 == 0.(3)
        7 / 11 == 0.(63)
        29 / 12 == 2.41(6)
        1 / 9 == 0.(1)
        2 / 3 == 0.(6)
        9 / 11 == 0.(81)
        7 / 12 == 0.58(3)
        1 / 81 == 0.(012345679)
        22 / 7 == 3.(142857)
        11 / 23 == 0.(4782608695652173913043)
        1 / 97 == 0.(0103092783505154639175257
        73195876288659793814432989690721649484
        536082474226804123711340206185567)
        5 / 6 == 0.8(3)
        

        我完全忘记了它是如何工作的……我想我是在尝试逆向工程来查找数字的小数形式,因为它是重复的小数,这比反过来要容易得多。例如:

        x = 3.(142857)
        1000000*x = 3142857.(142857)
        999999*x = 1000000*x - x 
        999999*x = 3142857.(142857) - 3.(142857)
        999999*x = 3142854
        x = 3142854 / 999999
        x = 22 / 7
        

        理论上,您可以使用从分数到小数的相同方法。主要障碍是,将任意分数转换为“(某个数字)/(某个数量的九)”形式的东西并非完全微不足道。如果您的原始分母可以被 2 或 5 整除,则它不能将 any 9-repunit 整除。所以form 的很多工作都是为了消除那些原本无法除以 999...9 的因素。

        【讨论】:

        • 检查你的程序是否有test_cases = [(3,12)]
        • 让我们看看...当我在 Python 2.7 中运行它时,它会按预期提供0.25。在 3.X 中,我得到 0.0.25.0。那是个问题。我会看看是否可以采用与版本无关的方法。
        • 您只需将第 16 行和第 17 行中的 / 更改为 // :)
        • 是的,同意。我在其他地方使用// 的事实表明我从一开始就尝试使其与 Python 3 兼容。奇怪的是我没有在任何地方都应用它。
        【解决方案4】:

        主要思想是找出小数位。换句话说,在哪里放置小数'。'

        当一个数字除以 2 或 5 时,没有循环小数。 1/2 = 0.5,1/5 = 0.2。只有那些不是 2 或不是 5。例如。 3、7、11。6 怎么样?事实上,6 是 2x3,其中由于因子 3 出现循环小数。1/6 = 1/2 - 1/3 = 非循环部分 + 循环部分。

        再举一个例子 1/56。 56=8x7=2^3x7。请注意,1/56 = 1/7 - 1/8 = 1/7 - 1/2^3。有2个部分。前面部分是 1/7,它是重复出现的 0.(142857),而后面部分是 1/2^3 = 0.125 不重复出现。但是,1/56 = 0.017(857142)。 1/7 在 '.' 之后重复出现。 1/56 的重复部分是小数点后 3 位。这是因为 0.125 有 3 个小数位,并且直到 3 个小数位后才重复出现。当我们知道循环部分从哪里开始时,使用长除法来找出循环的最后一位数字就不难了。

        5 的情况类似。任何分数的形式都可以是 = a/2^m + b/5^n + 重复部分。循环部分被向右推 a/2^m 或 b/5^n。这不难找出哪些更努力。然后我们知道重复部分从哪里开始。

        为了找到循环小数,我们使用长除法。由于 long divison 将得到余数,将余数乘以 10,然后用作新的 nomerator 并再次除法。这个过程一直在继续。如果数字再次出现。循环到此结束。

        【讨论】:

          猜你喜欢
          • 2016-05-23
          • 1970-01-01
          • 2015-01-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-10-02
          • 2019-01-24
          相关资源
          最近更新 更多