将分数转换为括号中重复小数位的字符串

Convert fraction to string with repeating decimal places in brackets

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

一个例子:

我现在的代码在问题的最后,但是如果有不重复的 重复的小数位,它就会失败。这是一个示例 运行,包括调试输出(注释 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)

这并没有回答您的实际问题 ("why isn't my code working?"),但它也许对您有用。几个月前我写了一些代码来做你现在想做的事情。在这里。

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 的因素。

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

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

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

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

求因式分解的方法有很多种,但我们真正想知道的是,这个数是否有除 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)

现在剩下要做的就是找出我们在 non-terminating/repeating 分数的情况下所做的事情。

现在这是超级数论,所以我会把算法留给你,让你决定是否要在 mathforum.org or wolfram alpha

上找到更多信息

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

在我寻找高效算法的过程中,我在 https://softwareengineering.stackexchange.com/ 上找到了这个 post,应该会有帮助。

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

10^s=10^(s+t) (modn)。 “

所以我们需要做的就是找到 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/ and a similar-ish question

阅读有关该主题的有趣内容

您的代码只需要做一些小的改动(见下面的评论):

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)

主要思路是找出小数位。在 order word 中,小数点“.”的位置

当一个数除以 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 向右推。这不难找出哪些人更努力。然后我们知道重复部分从哪里开始。

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