1

I was given a task to find a way to make a function to turn floats into fractions as accurate as possible, and by sheer brute forcing, i created a function like this

def isclose(a, b, tolerance):
    return abs(a-b) <= tolerance


def fraction(a, factor=0, tol=0.01):
    while True:
        factor += 1
        a_rounded = int(round(a*factor))
        if isclose(a*factor, a_rounded, tol):
            break
    if factor == 1:
        return a_rounded
    else:
        return "{}/{}".format(a_rounded, factor)

Is there a more efficient way to do it, without having to rely on external modules? I can't use modules, as I'm trying to implement this to micropython, which doesn't have the fractions library.

  • IEEE-754 floating-point numbers (other than special encodings like NaNs and infinities) are already fractions, where the denominator is a power of two (for binary floating-point) or power of ten (for decimal floating point). – njuffa Aug 21 '20 at 22:10
  • 1
    It's straightforward to do this using [continued fractions](https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations). – Steve Summit Mar 15 '23 at 11:50

3 Answers3

0

The algorithm I'm using is to take the decimal, put it over a power of 10 (3.25 turns into 25/100), multiply the whole number by the new denominator (3 * 100), add that onto the decimal (300 + 25), and put it over the denominator (325/100).

If you don't need to simplify, this will suffice:

def float_to_frac(flt):
    integer, decimal = str(flt).split(".")
    denominator = 10 ** len(decimal)
    numerator = int(integer) * denominator + int(decimal)
    return f"{numerator}/{denominator}"

If you need to simplify, the only way I know of is through brute force:

def float_to_frac(flt):
    integer, decimal = str(flt).split(".")
    denominator = 10 ** len(decimal)
    numerator = int(integer) * denominator + int(decimal)
    for factor in range(2, min(numerator, denominator) + 1):
        if (numerator // factor == numerator / factor) and (denominator // factor == denominator / factor):
            numerator /= factor
            denominator /= factor
    return f"{numerator}/{denominator}"
Elan-R
  • 484
  • 3
  • 6
  • the brute force method seems less accurate then mine, for example 0.3 is 3/10 is right, but with 0.33, 1/3 which mine generates is more accurate than 33/100 – Eren Yaegar Aug 21 '20 at 17:47
  • But 1/3 isn't the same as 0.33. 1/3 is 0.33333..., which is less accurate than 33/100 which is exactly 0.33. – Elan-R Aug 21 '20 at 17:52
  • even with 0.333333333333333, your program hangs and produces 3333333333.../1000000000... – Eren Yaegar Aug 21 '20 at 17:57
  • Yes, that's what it's meant to do. Turn a rational float into a fraction. – Elan-R Aug 21 '20 at 18:05
0

If you can’t use python libraries, you might be able to use c libraries with micropython:

https://github.com/lvgl/lv_binding_micropython

See the section regarding “Binding other C Libraries”

There’s a bit of code here as well, with a well regarded solution should you be able to implement the c code:

How to convert floats to human-readable fractions?

Mr No
  • 113
  • 1
  • 10
0

You can use the as_integer_ratio() method, released after Python 3.8.

def float_to_fraction1(f):
    "Converts a float to a fraction."""
    return f.as_integer_ratio()

Example:

>>> print(float_to_fraction1(3.14))
(7070651414971679, 2251799813685248)

>>> print(float_to_fraction1(2.26))
(1272266894732165, 562949953421312)

>>> print(float_to_fraction1(.5))
(1, 2)

>>> print(float_to_fraction1(.3333))
(5998794703657501, 18014398509481984) 

But if the micropython after two years still does not support fractions, then you can use the following function:

from math import gcd


def float_to_fraction2(f: str) -> tuple:
    """Converts a float to a fraction."""

    int_part, frac_part = f.split(".")
    denominator = 10 ** len(frac_part)
    numerator = int(int_part) * denominator + int(frac_part)
    divisor = gcd(numerator, denominator)
    return (numerator // divisor, denominator // divisor)

Example:

>>> print(float_to_fraction2('0.5'))
(1, 2)

>>> print(float_to_fraction2('3.14'))
(157, 50)

>>> print(float_to_fraction2('2.718'))
(1359, 500)

>>> print(float_to_fraction2('2.26'))
(113, 50)

>>> print(float_to_fraction2('0.333'))
(333, 1000)

Note that the argument should be string, and I didn't choose str() to convert a float to string because it has limitations in float decimal places.

Reza K Ghazi
  • 347
  • 2
  • 9