fractions.Fraction limit numerator? - python

fraction.Fraction.limit_denominator exists, and finds the closest fraction that has a denominator smaller than a given maximum. But there is no obvious way to limit the numerator.
What is the best way to also limit the numerator? (I.e. to find the closest fraction that has both numerator and denominator smaller than a given maximum.)
(Goal: I need to limit the numerator also to 2**32, because TIFF files store fractions as two unsigned 32-bit integers.)
A crude approach (that probably does not find the truly closest fraction):
from fractions import Fraction
def limit_32bit_rational(f: float) -> Fraction:
ndigits = 15
while True:
r = Fraction.from_float(f).limit_denominator(1000000)
if r.numerator < 2**32:
return r
f = round(f, ndigits)
ndigits -= 1
Is there a better way to find the closest fraction that has nominator and denominator less than 2**32?

You can use the float.as_integer_ratio() method to get the numerator and denominator of any float:
f = 2345.245624
numerator, denominator = (f).as_integer_ratio()
print("Numerator", numerator)
Output:
2578624833578781
EDIT:
You can try using an if statement to check if the numerator or denominator is greater than 2 ** 32; if so, multiply each of them by a scale and round them to the nearest whole numbers:
def limit_32bit_rational(f):
numerator, denominator = (f).as_integer_ratio()
max_num = 2 ** 32
if numerator > max_num or denominator > max_num:
scale = max_num / max(numerator, denominator)
return round(numerator * scale), round(denominator * scale)
return numerator, denominator

You could also flip the fraction and call limit_denominator to limit the numerator and flip the result back again:
def limit_32bit_rational(f: float) -> Fraction:
tmp = Fraction(f).limit_denominator(0xFFFF_FFFF)
if tmp.numerator <= 0xFFFF_FFFF:
# makes sure we don't flip if numerator is 0
return tmp
else:
# flip numerator with denominator to limit the inputs numerator
tmp = Fraction(numerator=tmp.denominator, denominator=tmp.numerator).limit_denominator(0xFFFF_FFFF)
return Fraction(numerator=tmp.denominator, denominator=tmp.numerator)

Related

In Python, how to reduce the precision of a float to a given step size?

I have a float that can have arbitrary precision, and a minimum step size that indicates the minimum amount of this number can be increased / decreased by:
num = 3.56891211101
min_step = 0.005
I'd like to have a function that takes this num and the step_size and rounds the num to that given min_step. So in this case the result would be 3.570.
I attempted this:
num = 3.56891211101
min_step = 0.005
def myround(x, base):
return base * round(x / base)
x = myround(num, min_step)
print(x)
>>> 3.5700000000000003
...it's close, but not quite. I would like the output to be the same as if:
y = 3.570
print(y)
>>> 3.57
What's a simple way of accomplishing this?
I'm on Python 3.8
Most Python implementations (including the CPython reference implementation) use IEE 754 floating point numbers. As a result, they are not accurate for decimal values.
The canonic way is to use the decimal module:
from decimal import Decimal, Context
num = 3.56891211101
c = Context(prec=3)
x= c.create_decimal(num)
print(x)
gives as expected
3.57
I solved it with:
def myround(x, base):
decimal_places = str(base)[::-1].find('.')
precise = base * round(x / base)
return round(precise, decimal_places)
x = myround(num, min_step)
print(x)
>>> 3.57
y = 3.570
print(y)
>>> 3.57
Hope its helpful to others.

Project Euler Problem 26 Python String Limits in Fraction Numbers

Project Euler Problem 26:
A unit fraction contains 1 in the numerator. The decimal representation of the unit fractions with denominators 2 to 10 are given:
1/2 = 0.5
1/3 = 0.(3)
1/4 = 0.25
1/5 = 0.2
1/6 = 0.1(6)
1/7 = 0.(142857)
1/8 = 0.125
1/9 = 0.(1)
1/10 = 0.1
Where 0.1(6) means 0.166666..., and has a 1-digit recurring cycle. It can be seen that 1/7 has a 6-digit recurring cycle.
Find the value of d < 1000 for which 1/d contains the longest recurring cycle in its decimal fraction part.
Although I have already provided the correct answer to the problem (983), I still think that the code could have developed even further as I think the code I wrote could be wrong if the value of d can go over 1000.
I think the code might be wrong because the string limit of the fraction is 20 and what if there is a fraction that has over 20 recurring cycle?
I have tried using format() to increasing the string limit of the fraction, but I realise that the digits after the 20th string are not any of the repeating recurring numbers.
import time
import math
timeStart = time.time()
prime_numbers = []
def is_prime(n):
for i in range(2, int(math.sqrt(n)+1)):
if n % i == 0:
return False
return True
def numbers(n):
for number in range(2, n):
if is_prime(number):
prime_numbers.append(number)
def main():
limit = 1000
longest_recurring_cycle = 0
longest_value = 0
numbers(limit)
for d in prime_numbers:
fraction = str(1/d)
count = 1
if len(fraction) > 15:
for index, recurring_cycle in enumerate(fraction[3:10]):
if recurring_cycle == fraction[2] and recurring_cycle ==
fraction[index + 3 + count]:
break
elif count >= longest_recurring_cycle:
longest_recurring_cycle = count
longest_value = d
count += 1
print(longest_value)
print(time.time() - timeStart, "seconds")
main()
tltr I want to find a way to increase the string limit of the fraction that produces the right number.
I would recommend simulating long division. That is, the digits of 1/7 are 10//7=1, remainder is 10 - 1*7 = 3. Next decimal is 30//7 = 4. Remainder is 2. And so on, until the remainder is equal to 1 again. Then count the length of the cycle.
def cycle(denom,num=1):
digit = None
decimals=[]
while digit not in decimals:
decimals += [digit]
digit = num * 10 // denom
remainder = num * 10 - digit * denom
num = remainder
print(digit)
return len(decimals) - decimals.index(digit)
cycle(3)
Here is my code and i got the answer with in a second.I used the simple long division method and considered only remainders.Note that this code doesn't valid when the numerator isn't equal to one.When it is,the function should be develop.
def count_repeat(number):
_list = [10]
while ((_list[-1]) % number)*10 not in _list:
_list.append((_list[-1] % number)*10)
return len(_list)-_list.index((_list[-1] % number)*10)
repeat_lenth = 0
relevent_i = 2
for i in range(2, 1000):
if count_repeat(i) >= repeat_lenth:
repeat_lenth = count_repeat(i)
relevent_i = i
print(f"{i}-{repeat_lenth}")
print(relevent_i)
def repeating_cycle(denom :int, num :int=1)->int:
'''We will do long division method but we will be tracking the reminder throught the division
The reason for doing it is sometime the the number in quotient might repeat even though it is part of
full cycle. e.g. 1/17 = 0.(0588235294117647) if we were to only look at quotient then 8 is repeted just at the 4th place but our
actual cycle is of 16 digits.And this is very well known in maths'''
reminders = []
rems = None
while rems not in reminders:
reminders.append(rems)
num *= 10
rems = num % denom
# reminders.append(rems)
reminders.pop(0)
return len(reminders)
Well I show someone's answer and that person was keeping track of digits in quotient which might not always get the answer e.g. 1/17 = 0.(058823...) and we see the third 8 is directly repeated but the cycle does not repeat there, infact it repeats after 16 digits, and I that is why it is better to keep track of reminders instead of digits in quotient.

my code ends with OverflowError when I have large inputs

One of my homework questions, and I am only allowed to import pi.
The question asks to compute the cosine function by Taylor series, which I have done so far. The outputs I get are correct however when k gets larger than 90, I get OverflowError: int too large to convert to float.
from math import pi
def myCos(angle,k):
total=0
for i in range(k):
total += (((-1)**(i))*((angle*pi/180)**(2*(i))))/(fact(2*(i)))
return total
def fact(n):
if n == 0:
return 1
else:
return fact(n-1)*n
In order to get full marks for this question, the code has to accept k > 100.
i.e
myCos(45,5)
0.7071068056832942
myCos(45,60)
0.7071067811865475
myCos(45,90)
total += (((-1)**(i))*((angle*pi/180)**(2*(i))))/(fact(2*(i)))
OverflowError: int too large to convert to float
Can someone please enlighten me on this?
Your main division in the summation exceeded the range of type float. To do that division, you have to convert the denominator to type float. fact(2*85) is larger than the maximum float value.
Do the operations in a difference order. For instance:
numer = (angle*pi/180)**(2*(i))
for denom in range(1, 2*i + 1):
numer /= denom
Now numer is a reasonable (?) representation of the value desired. If you need better reliability, "chunk" the denominator values and divide by the product of, say, groups of 10 denom values.
def myCos(angle,k):
total=0
for i in range(k):
numer = (angle*pi/180)**(2*(i))
for denom in range(2, 2*i+1):
numer /= denom
total += (-1) ** i * numer
return total
print(myCos(45,5))
print(myCos(45,60))
print(myCos(45,90))
Output:
0.7071068056832942
0.7071067811865475
0.7071067811865475

Float to Fraction conversion : recognizing cyclic decimals

I am currently implementing a Fraction class for training my OOP skills, and I have got to a problem... In my class, you are able to do math operations between:
Fraction & Fraction
Integer & Fraction
Fraction & Integer
And I want to add both:
Fraction & Float
Float & Fraction
Since when I work with integers, what I do is to make them a Fraction with denominator 1, I want to, when operating with float, creating a Fraction that represents that given float. And that is where I have my problem.
Firstly, the minimum code required to understand my Fraction class:
class Fraction(object):
def __init__(self,num,den=1,reduce=True):
# only accept integers or convertable strings
if not(type(num) == int and type(den) == int):
if type(num) == str:
try:
num = int(num)
except ValueError:
raise RuntimeError("You can only pass to the numerator and \
denominator integers or integer convertable strings!")
else:
raise RuntimeError("You can only pass to the numerator and \
denominator integers or integer convertable strings!")
if type(den) == str:
try:
den = int(den)
except ValueError:
raise RuntimeError("You can only pass to the numerator and \
denominator integers or integer convertable strings!")
else:
raise RuntimeError("You can only pass to the numerator and \
denominator integers or integer convertable strings!")
# don't accept fractions with denominator 0
if den == 0:
raise ZeroDivisionError("The denominator must not be 0")
# if both num and den are negative, flip both
if num < 0 and den < 0:
num = abs(num)
den = abs(num)
# if only the den is negative, change the "-" to the numerator
elif den < 0:
num *= -1
den = abs(den)
self.num = num
self.den = den
# the self.auto is a variable that will tell us if we are supposed to
#automatically reduce the Fraction to its lower terms. when doing some
#maths, if either one of the fractions has self.auto==False, the result
#will also have self.auto==False
self.auto = reduce
if self.auto:
self.reduce()
def float_to_fraction(f):
'''should not be called by an instance of a Fraction, since it does not\
accept, purposedly, the "self" argument. Instead, call it as\
Fraction.float_to_fraction to create a new Fraction with a given float'''
# Start by making the number a string
f = str(f)
exp = ""
# If the number has an exponent (is multiplied by 10 to the power of sth
#store it for later.
if "e" in f:
# Get the "e+N" or "e-N"
exp = f[f.index("e"):]
# Slice the exponent from the string
f = f[:f.index("e")]
# Start the numerator and the denominator
num = "0"
den = "1"
# Variable to check if we passed a ".", marking the decimal part of a
#number
decimal = False
for char in f:
if char != ".":
# Add the current char to the numerator
num += char
if decimal:
# If we are to the right of the ".", also add a 0 to the
#denominator to keep proportion
den += "0"
# Slice parsed character
f = f[1:]
if char == ".":
# Tell the function we are now going to the decimal part of the
#float.
decimal = True
# Slice the "."
f = f[1:]
# Parse the exponent, if there is one
if exp != "":
# If it is a negative exponent, we should make the denominator bigger
if exp[1] == "-":
# Add as many 0s to the den as the absolute value of what is to
#the right of the "-" sign. e.g.: if exp = "e-12", add 12 zeros
den += "0"*int(exp[2:])
# Same stuff as above, but in the numerator
if exp[1] == "+":
num += "0"*int(exp[2:])
# Last, return the Fraction object with the parsed num and den!
return Fraction(int(num),int(den))
My float_to_fraction() function converts, 100% accurately, a given float to a Fraction. But as I remember from my math classes a cyclic decimal with a n-digit long cycle, like 0.123123123123... or 0.(123) can be written in the form of a fraction with numerator = cycle and denominator = (as many 9s as the length of the cycle):
123/999 = 0.(123)
3/9 (=1/3) = 0.(3); 142857/999999 (=1/7) = 0.(142857) etc, etc...
But with this implementation, if I pass to the float_to_fraction() an argument like 1/3, it will parse "0.3333333333333333" which is finite, returning this Fraction: 3333333333333333/10000000000000000. It IS accurate, since I passed to the function a finite number! How can I implement, in this function, a way of recognizing cyclic decimals, so I can return, instead of a Fraction with a denominator = 10^n, a denominator with loads of 9s.
The best way to convert a decimal expression into an approximating rational is via the continued fraction expansion.
For x=0.123123123123 this results in
r=x,
a[0]=floor(r)=0, r=r-a[0], r=1/r=8.121951219520,
a[1]=floor(r)=8, r=r-a[1], r=1/r=8.199999999453,
a[2]=floor(r)=8, r=r-a[2], r=1/r=5.000000013653,
a[3]=floor(r)=5, r=r-a[3], r=1/r=73243975.48780,
And at this point r-a[3]<1e-5 the iteration stops. The rational approximation found is
x=1/(8+1/(8+1/5))=1/(8+5/41)=41/333 (=123/999)
The intermediate convergents are
1/8=0.125, x- 1/8 = -0.001876876877,
1/(8+1/8)=8/65, x- 8/65 = 0.0000462000460,
41/333, x-41/333 = -1.231231231231e-13.

Float to binary

I'm trying to convert a floating point number to binary representation; how can I achieve this?
My goal is, however, not to be limited by 2m so I'm hoping for something that could be easily extended to any base (3, 4, 8) ecc.
I've got a straightforward implementation so far for integers:
import string
LETTER = '0123456789' + string.ascii_lowercase
def convert_int(num, base):
if base == 1:
print "WARNING! ASKING FOR BASE = 1"
return '1' * num if num != 0 else '0'
if base > 36: raise ValueError('base must be >= 1 and <= 36')
num, rest = divmod(num, base)
rest = [LETTER[rest]]
while num >= base:
num, r = divmod(num, base)
rest.append(LETTER[r])
rest.reverse()
return (LETTER[num] if num else '') + ''.join(str(x) for x in rest)
any help appreciated :)
edit:
def convert_float(num, base, digits=None):
num = float(num)
if digits is None: digits = 6
num = int(round(num * pow(base, digits)))
num = convert_int(num, base)
num = num[:-digits] + '.' + num[:digits]
if num.startswith('.'): num = '0' + num
return num
is that right? why do i get this behaviour?
>>> convert_float(1289.2893, 16)
'509.5094a0'
>>> float.hex(1289.2983)
'0x1.42531758e2196p+10'
p.s.
How to convert float number to Binary?
I've read that discussion, but I don't get the answer.. I mean, does it work only for 0.25, 0.125? and I dont understand the phrase 'must be in reverse order'...
For floats there is built-in method hex().
http://docs.python.org/library/stdtypes.html#float.hex
It gives you the hexadecimal representation of a given number. And translation form hex to binary is trivial.
For example:
In [15]: float.hex(1.25)
Out[15]: '0x1.4000000000000p+0'
In [16]: float.hex(8.25)
Out[16]: '0x1.0800000000000p+3'
Next answer with a bit of theory.
Explanation below does not explain IEEE Floating Point standard only general ideas concerning representation of floating point numbers
Every float number is represented as a fractional part multiplied by an exponent multiplied by a sign. Additionally there is so called bias for exponent, which will be explained bellow.
So we have
Sign bit
Fractional part digits
Exponent part digits
Example for base 2 with 8 bit fraction and 8 bit exponent
Bits in fraction part tell us which summands (numbers to be added) from sequence below are to be included in represented number value
2^-1 + 2^-2 + 2^-3 + 2^-4 + 2^-5 + 2^-6 + 2^-7 + 2^-8
So if you have say 01101101 in fractional part it gives
0*2^-1 + 1*2^-2 + 1*2^-3 + 0*2^-4 + 1*2^-5 + 1*2^-6 + 0*2^-7 + 1*2^-8 = 0.42578125
Now non-zero numbers that are representable that way fall between
2 ** -8 = 0.00390625 and 1 - 2**-8 = 0.99609375
Here the exponent part comes in. Exponent allows us to represent very big numbers by multiplying the fraction part by exponent. So if we have an 8bit exponent we can multiply the resulting fraction by numbers between 0 and 2^255.
So going back to example above let's take exponent of 11000011 = 195.
We have fractional part of 01101101 = 0.42578125 and exponent part 11000011 = 195. It gives us the number 0.42578125 * 2^195, this is really big number.
So far we can represent non-zero numbers between 2^-8 * 2^0 and (1-2^-8) * 2^255. This allows for very big numbers but not for very small numbers. In order to be able to represent small numbers we have to include so called bias in our exponent. It is a number that will be always subtracted from exponent in order to allow for representation of small numbers.
Let's take a bias of 127. Now all exponents are subtracted 127. So numbers that can be represented are between 2^-8 * 2^(0 - 127) and (1-2^-8) * 2^(255 - 127 = 128)
Example number is now 0.42578125 * 2^(195-127 = 68) which is still pretty big.
Example ends
In order to understand this better try to experiment with different bases and sizes for fractional and exponential part. At beginning don't try with odd bases because it only complicates things necessary.
Once you grasp how this representation works you should be able to write code to obtain representation of any number in any base, fractional/exponential part combination.
If you want to convert a float to a string with d digits after the decimal radix point:
Multiply the number by base**d.
Round to the nearest integer.
Convert the integer to a string.
Insert the . character d digits before the end.
For example, to represent 0.1 in base 12 with 4 decimal dozenal places,
0.1 × 124 = 2073.6
Round to nearest integer → 2074
Convert to string → 124A
Add radix point → 0.124A
This isn't the same style of binary representation that you want, but this will convert an IEEE 754 into it's sign, mantissa and base, which can be used to create a hex representation in a fairly straightforward fashion. Note that the 'value' of the mantissa is 1+BINARY, where BINARY is the binary representation - hence the -1 in the return.
I wrote this code and am declaring it public domain.
def disect_float(f):
f = float(f); #fixes passing integers
sign = 0x00; #positive
exp = 0;
mant = 0;
if(f < 0): #make f positive, store the sign
sign = '-'; #negative
f = -f;
#get the mantissa by itself
while(f % 1 > 0): #exp is positive
f*=2
exp+=1
#while(f % 1 > 0):
tf = f/2;
while(tf % 1 <= 0): #exp is negative
exp-=1;
f=tf;
tf=f/2;
if(exp < -1024): break;
mant=int(f);
return sign, mant-1, exp;
There is one trick that i observed that we can do using simple string manipulations. I felt this method to be simpler than other methods that i came across.
s = "1101.0101"
s1, s2 = s.split(".")
s1 = int(s1, 2)
s2 = int(s2, 2)/(2**len(s2))
x = s1+s2
print(x)
Output :
13.3125
Hope it will be helpful to someone.
Answering the title directly and using float.hex, which uses 64bit IEE754, one could write this method:
def float_to_bin(x):
if x == 0:
return "0" * 64
w, sign = (float.hex(x), 0) if x > 0 else (float.hex(x)[1:], 1)
mantissa, exp = int(w[4:17], 16), int(w[18:])
return "{}{:011b}{:052b}".format(sign, exp + 1023, mantissa)
float_to_bin(-0.001) # '1011111101010000000010110011111101011000011011100110110100101010'
Note however, this does not work for NaN and Inf.

Categories