将小数转换为字符串,并在括号

时间:2016-04-10 12:08:43

标签: python math number-formatting fractions

我想在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)

4 个答案:

答案 0 :(得分:2)

我认为错误的是你应该只检查先前看到的小数位数是否是循环长度的数字,并且在此长度之前就已经看到了。

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

让我们尝试设法找到分数的十进制表示以及如何知道何时会有重复的小数。

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

有许多方法可以找到因子分解,但我们真正想知道的是,这个数字是否具有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

我让它变得更加多态,所以如果你想改变基数,你可以改变它。 (你需要的就是那个基数的因素,例如在你检查2和7而不是2和5的基数14中)

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

现在这是超级数理论填充的,所以我将为您留下算法,让您决定是否要在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

答案 1 :(得分:1)

这不能解答您的实际问题("为什么我的代码无法工作?")但也许它对您有用。几个月前,我写了一些代码来做你现在正在尝试做的事情。在这里。

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

理论上,您可以使用从小数到小数的相同方法。主要障碍是将任意分数变成某种形式的某种形式并不是完全无足轻重的(某些数字)/(某些数量的9)"。如果您的原始分母可以被2或5整除,则它不能均匀地划分任何 9-repunit。因此,很多form的工作都是关于去除那些因此无法除以999 ... 9的因素。

答案 2 :(得分:1)

您的代码只需要进行一些小的更改(请参阅下面的评论):

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 :(得分:0)

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

当一个数除以 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,然后用作新的诺姆并再次除法。这个过程一直持续下去。如果数字再次出现。循环到此结束。