On percentage (or scaling) integers by fractions


#1

Hello,

I have a conversion question.
Look at this code:

/*
LOCAL VARIABLES
local_ticks_length     cycle duration in K-rate ticks (int32_t)
local_current_tick     current tick (int32_t)
local_cycle_length    percentage of the total length (int32_t)

INLETS
inlet_ticks_length    cycle duration in ticks (int32.positive)

PARAMS
params_cycle_length   the percentage to apply to the total length (frac32.u.map)
*/

// PLEASE HELP ME!!!
local_cycle_length = local_ticks_length * (params_cycle_length / 64);
local_current_tick = local_current_tick + 1 % local_cycle_length;

This is a part of an object that cycles and then triggers at the beginning of the cycle.
What I need to do is scaling the length of the cycle using a frac32.u.map:

local_ticks_length * (params_cycle_length / 64);

where 64 is the upper value of the frac32.u.map.
I have no idea on how to convert those variables in order to cycle only in the range of the scaled length.
Any help would be very appreciated!

Thanks!
Daniele


#2

Here is a patch to show what I need:

Press the toggle button for some seconds then press it again:

cycles.axp (4.3 KB)

And here is the updated code, but I still don't get floating point math...

//reset when updating cycle length
if (local_ticks_length != inlet_ticks) {
  local_ticks_length = inlet_ticks;
  local_current_tick = 0;
  local_count = 0;
}

//do nothing if length is zero
if (local_ticks_length == 0) {
  outlet_trigger = false;
  return;
}

disp_ticks = local_ticks_length;
disp_count = local_count;

if (param_cycle > 0) {
  int32_t max = 0x07FFFFFF;
  outlet_test = max;
  // please help me! :-)
  // I need to convert this to correct types and round it before the % operator!
  local_cycle_length = local_ticks_length * (param_cycle / max);
  local_current_tick = (local_current_tick + 1) % local_cycle_length;
} else {
  //increase current tick
  local_current_tick = (local_current_tick + 1) % local_ticks_length;
}

//increase current tick
// local_current_tick = (local_current_tick + 1) % local_ticks_length;

if (local_current_tick == 0) {
  local_count ++;
  outlet_trigger = 1;
} else {
  outlet_trigger = 0;
}

#3

Ok, so...i did a first attempt using "fixed point math", using this article:

Now the code to calculate the scaling looks like:

// first attempt to scale:
int32_t max = 0x07FFFFFF;
int32_t perc = ((int64_t)param_cycle * (int64_t)max) / (1 << 16);
local_cycle_length = ((int64_t)local_ticks_length * (1 << 16)) / perc;

But I still don't get the expected result...

cycles.axp (4.7 KB)


#4

I have done this on Python -

There are a few ways to create a fraction in Python, and they all involve using the Fraction class. It’s the only thing you ever need to import from the fractions module. The class constructor accepts zero, one, or two arguments of various types:

from fractions import Fraction
print(Fraction())
0
print(Fraction(0.75))
3/4
print(Fraction(3, 4))
3/4
When you call the class constructor without arguments, it creates a new fraction representing the number zero. A single-argument flavor attempts to convert the value of another data type to a fraction. Passing in a second argument makes the constructor expect a numerator and a denominator, which must be instances of the Rational class or its descendants.

Note that you must print() a fraction to reveal its human-friendly textual representation with the slash character (/) between the numerator and the denominator. If you don’t, it will fall back to a slightly more explicit string representation made up of a piece of Python code. You’ll learn how to convert fractions to strings later in this tutorial.

Rational Numbers
When you call the Fraction() constructor with two arguments, they must both be rational numbers such as integers or other fractions. If either the numerator or denominator isn’t a rational number, then you won’t be able to create a new fraction:

Fraction(3, 4.0)
Traceback (most recent call last):
...
raise TypeError("both arguments should be "
TypeError: both arguments should be Rational instances
You get a TypeError instead. Although 4.0 is a rational number in mathematics, it isn’t considered as such by Python. That’s because the value is stored as a floating-point data type, which is too broad and can be used to represent any real number.

Note: The floating-point data type can’t store irrational numbers in a computer’s memory precisely due to their non-terminating and non-repeating decimal expansion. In practice, though, it’s not a big deal because their approximations will usually be good enough. The only truly reliable way to do so would require using symbolic computation on conventional symbols like π.

Similarly, you can’t create a fraction whose denominator is zero because that would lead to a division by zero, which is undefined and has no meaning in mathematics:

Fraction(3, 0)
Traceback (most recent call last):
...
raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
ZeroDivisionError: Fraction(3, 0)
Python raises the ZeroDivisionError. However, when you specify a valid numerator and a valid denominator, they’ll be automatically normalized for you as long as they have a common divisor:

Fraction(9, 12) # GCD(9, 12) = 3
Fraction(3, 4)

Fraction(0, 12) # GCD(0, 12) = 12
Fraction(0, 1)
Both magnitudes get simplified by their greatest common divisor (GCD), which happens to be three and twelve, respectively. The normalization also takes the minus sign into account when you define negative fractions:

-Fraction(9, 12)
Fraction(-3, 4)

Fraction(-9, 12)
Fraction(-3, 4)

Fraction(9, -12)
Fraction(-3, 4)
Whether you put the minus sign before the constructor or before either of the arguments, for consistency, Python will always associate the sign of a fraction with its numerator. There’s currently a way of disabling this behavior, but it’s undocumented and could get removed in the future.

You’ll typically define fractions as a quotient of two integers. Whenever you provide only one integer, Python will turn that number into an improper fraction by assuming the denominator is 1:

Fraction(3)
Fraction(3, 1)
Conversely, if you skip both arguments, the numerator will be 0:

Fraction()
Fraction(0, 1)
You don’t always have to provide integers for the numerator and denominator, though. The documentation states that they can be any rational numbers, including other fractions:

one_third = Fraction(1, 3)

Fraction(one_third, 3)
Fraction(1, 9)

Fraction(3, one_third)
Fraction(9, 1)

Fraction(one_third, one_third)
Fraction(1, 1)
In each case, you’ll get a fraction as a result, even though they sometimes represent integer values like 9 and 1. Later, you’ll see how to convert fractions to other data types.

What happens if you give the Fraction constructor a single argument that also happens to be a fraction? Try this code to find out:

Fraction(one_third) == one_third
True

Fraction(one_third) is one_third
False
You’re getting the same value, but it’s a distinct copy of the input fraction. That’s because calling the constructor always produces a new instance, which coincides with the fact that fractions are immutable, just like other numeric types in Python.

Remove ads
Floating-Point and Decimal Numbers
So far, you’ve only used rational numbers to create fractions. After all, the two-argument version of the Fraction constructor requires that both numbers are Rational instances. However, that’s not the case with the single-argument constructor, which will happily accept any real number and even a non-numeric value such as a string.

Two prime examples of real number data types in Python are float and decimal.Decimal. While only the latter can represent rational numbers exactly, both can approximate irrational numbers just fine. Related to this, if you were wondering, Fraction is similar to Decimal in this regard since it’s a descendant of Real.

Before Python 3.2, you could only create fractions from real numbers using the .from_float() and .from_decimal() class methods. While not deprecated, they’re redundant today because the Fraction constructor can take both data types directly as an argument:

from decimal import Decimal
Fraction(0.75) == Fraction(Decimal("0.75"))
True
Whether you make Fraction objects from float or Decimal objects, their values are the same. You’ve seen a fraction created from a floating-point value before:

print(Fraction(0.75))
3/4
The result is the same number expressed in fractional notation. However, this code works as expected only by coincidence. In most cases, you won’t get the intended value due to the representation error that affects float numbers, whether they’re rational or not:

print(Fraction(0.1))
3602879701896397/36028797018963968
Whoa! What happened here?

Let’s break it down in slow motion. The previous number, which can be represented as either 0.75 or ¾, can also be expressed as the sum of ½ and ¼, which are negative powers of 2 that have exact binary representations. On the other hand, the number ⅒ can only be approximated with a non-terminating repeating expansion of binary digits:

The Binary Expansion of One Tenth
Because the binary string must eventually end due to the finite memory, its tail gets rounded. By default, Python only shows the most significant digits defined in sys.float_info.dig, but you can format a floating-point number with an arbitrary number of digits if you want to:

str(0.1)
'0.1'

format(0.1, ".17f")
'0.10000000000000001'
format(0.1, ".18f")
'0.100000000000000006'
format(0.1, ".19f")
'0.1000000000000000056'
format(0.1, ".55f")
'0.1000000000000000055511151231257827021181583404541015625'
When you pass a float or a Decimal number to the Fraction constructor, it calls their .as_integer_ratio() method to obtain a tuple of two irreducible integers whose ratio gives precisely the same decimal expansion as the input argument. These two numbers are then assigned to the numerator and denominator of your new fraction.

Note: Since Python 3.8, Fraction also implements .as_integer_ratio() to complement other numeric types. It can help convert your fraction into a tuple, for example.

Now, you can piece together where these two big numbers came from:

Fraction(0.1)
Fraction(3602879701896397, 36028797018963968)

(0.1).as_integer_ratio()
(3602879701896397, 36028797018963968)
If you pull out your pocket calculator and punch these numbers in, then you’ll get back 0.1 as a result of the division. However, if you were to divide them by hand or use a tool like WolframAlpha, then you’d end up with those fifty-five decimal places you saw earlier.

There is a way to find close approximations of your fraction that have more down-to-earth values. You can use .limit_denominator(), for example, which you’ll learn more about later in this tutorial:

one_tenth = Fraction(0.1)
one_tenth
Fraction(3602879701896397, 36028797018963968)

one_tenth.limit_denominator()
Fraction(1, 10)

one_tenth.limit_denominator(max_denominator=int(1e16))
Fraction(1000000000000000, 9999999999999999)
This might not always give you the best approximation, though. The bottom line is that you should never try to create fractions straight from real numbers such as float if you want to avoid the rounding errors that will likely come up. Even the Decimal class might be susceptible to that if you’re not careful enough.

Anyway, fractions let you communicate the decimal notation most accurately with a string in their constructor.

Strings
There are two string formats that the Fraction constructor accepts, which correspond to decimal and fractional notation:

Fraction("0.1")
Fraction(1, 10)

Fraction("1/10")
Fraction(1, 10)
Both notations can optionally have a plus sign (+) or a minus sign (-), while the decimal one can additionally include the exponent in case you want to use the scientific notation:

Fraction("-2e-3")
Fraction(-1, 500)

Fraction("+2/1000")
Fraction(1, 500)
The only difference between the two results is that one is negative and one is positive.

When you use the fractional notation, you can’t use whitespace characters around the slash character (/):

Fraction("1 / 10")
Traceback (most recent call last):
...
raise ValueError('Invalid literal for Fraction: %r' %
ValueError: Invalid literal for Fraction: '1 / 10'
To find out exactly which strings are valid or not, you can explore the regular expression in the module’s source code. Remember to create fractions from a string or a correctly instantiated Decimal object rather than a float value so that you can retain maximum precision.

Now that you’ve created a few fractions, you might be wondering what they can do for you other than group two numbers. That’s a great question!

Inspecting a Python Fraction
The Rational abstract base class defines two read-only attributes for accessing a fraction’s numerator and denominator:

from fractions import Fraction
half = Fraction(1, 2)
half.numerator
1
half.denominator
2
Since fractions are immutable, you can’t change their internal state:

half.numerator = 2
Traceback (most recent call last):
File "", line 1, in
AttributeError: can't set attribute
If you try assigning a new value to one of the fraction’s attributes, then you’ll get an error. In fact, you have to create a new fraction whenever you’d like to modify one. For example, to invert your fraction, you could call .as_integer_ratio() to get a tuple and then use the slicing syntax to reverse its elements:

Fraction(*half.as_integer_ratio()[::-1])
Fraction(2, 1)
The unary star operator (*) unpacks your reversed tuple and relays its elements to the Fraction constructor.

Another useful method that comes with every fraction lets you find the closest rational approximation to a number given in the decimal notation. It’s the .limit_denominator() method, which you’ve already touched on earlier in this tutorial. You can optionally request the maximum denominator for your approximation:

pi = Fraction("3.141592653589793")

pi
Fraction(3141592653589793, 1000000000000000)

pi.limit_denominator(20_000)
Fraction(62813, 19994)

pi.limit_denominator(100)
Fraction(311, 99)

pi.limit_denominator(10)
Fraction(22, 7)
The initial approximation might not be the most convenient to use, but it is the most faithful. This method can also help you recover a rational number stored as a floating-point data type. Basically
I have to do the same and implement on iphone app development agency. Remember that float may not represent all rational numbers exactly, even when they have terminating decimal expansions:

pi = Fraction(3.141592653589793)

pi
Fraction(884279719003555, 281474976710656)

pi.limit_denominator()
Fraction(3126535, 995207)

pi.limit_denominator(10)
Fraction(22, 7)
You’ll notice a different result on the highlighted line as compared to the previous code block, even though the float instance looks the same as the string literal that you passed to the constructor before! Later, you’ll explore an example of using .limit_denominator() to find approximations of irrational numbers.

Converting a Python Fraction to Other Data Types
You’ve learned how to create fractions from the following data types:

str
int
float
decimal.Decimal
fractions.Fraction
What about the opposite? How do you convert a Fraction instance back to these types? You’ll find out in this section.

Floating-Point and Integer Numbers
Converting between native data types in Python usually involves calling one of the built-in functions such as int() or float() on an object. These conversions work as long as the object implements the corresponding special methods such as .int() or .float(). Fractions happen to inherit only the latter from the Rational abstract base class:

from fractions import Fraction
three_quarters = Fraction(3, 4)

float(three_quarters)
0.75

three_quarters.float() # Don't call special methods directly
0.75

three_quarters.int()
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'Fraction' object has no attribute 'int'
You’re not supposed to call special methods on objects directly, but it’s helpful for demonstration purposes. Here, you’ll notice that fractions implement only .float() and not .int().

When you investigate the source code, you’ll notice that the .float() method conveniently divides a fraction’s numerator by its denominator to get a floating-point number:

three_quarters.numerator / three_quarters.denominator
0.75
Bear in mind that turning a Fraction instance into a float instance will likely result in a lossy conversion, meaning you might end up with a number that’s slightly off:

float(Fraction(3, 4)) == Fraction(3, 4)
True

float(Fraction(1, 3)) == Fraction(1, 3)
False

float(Fraction(1, 10)) == Fraction(1, 10)
False
Although fractions don’t provide the implementation for the integer conversion, all real numbers can be truncated, which is a fall-back for the int() function:

fraction = Fraction(14, 5)

int(fraction)
2

import math
math.trunc(fraction)
2

fraction.trunc() # Don't call special methods directly
2
You’ll discover a few other related methods in a section about rounding fractions later on.

Decimal Numbers
If you try creating a Decimal number from a Fraction instance, then you’ll quickly find out that such a direct conversion isn’t possible:

from decimal import Decimal
Decimal(Fraction(3, 4))
Traceback (most recent call last):
File "", line 1, in
TypeError: conversion from Fraction to Decimal is not supported
When you try, you get a TypeError. Since a fraction represents a division, however, you can bypass that limitation by wrapping only one of the numbers with Decimal and dividing them manually:

fraction = Fraction(3, 4)
fraction.numerator / Decimal(fraction.denominator)
Decimal('0.75')
Unlike float but similar to Fraction, the Decimal data type is free from the floating-point representation error. So, when you convert a rational number that can’t be represented exactly in binary floating-point, you’ll retain the number’s precision:

fraction = Fraction(1, 10)
decimal = fraction.numerator / Decimal(fraction.denominator)

fraction == decimal
True

fraction == 0.1
False

decimal == 0.1
False
At the same time, rational numbers with non-terminating repeating decimal expansion will lead to precision loss when converted from fractional to decimal notation:

fraction = Fraction(1, 3)
decimal = fraction.numerator / Decimal(fraction.denominator)

fraction == decimal
False

decimal
Decimal('0.3333333333333333333333333333')
That’s because there’s an infinite number of threes in the decimal expansion of one-third, or Fraction(1, 3), while the Decimal type has a fixed precision. By default, it stores only twenty-eight decimal places. You can adjust it if you want, but it’s going to be finite nevertheless.

Strings
The string representation of fractions reveals their values using the familiar fractional notation, while their canonical representation outputs a piece of Python code comprised of a call to the Fraction constructor:

one_third = Fraction(1, 3)

str(one_third)
'1/3'

repr(one_third)
'Fraction(1, 3)'
Whether you use str() or repr(), the result is a string, but their contents are different.

Unlike other numeric types, fractions don’t support string formatting in Python:

from decimal import Decimal
format(Decimal("0.3333333333333333333333333333"), ".2f")
'0.33'

format(Fraction(1, 3), ".2f")
Traceback (most recent call last):
File "", line 1, in
TypeError: unsupported format string passed to Fraction.format
If you try, then you get a TypeError. That might be an issue if you’d like to refer to a Fraction instance in a string template to fill out the placeholders, for example. On the other hand, you can quickly fix this by converting fractions to floating-point numbers, especially as you don’t need to care about precision in such a scenario.

If you’re working in a Jupyter Notebook, then you might want to render LaTeX formulas based on your fractions instead of their regular textual representation. To do that, you must monkey patch the Fraction data type by adding a new method, .reprpretty_(), which Jupyter Notebook recognizes:

from fractions import Fraction
from IPython.display import display, Math

Fraction.reprpretty_ = lambda self, *args: \
display(Math(rf"$$\frac{{{self.numerator}}}{{{self.denominator}}}"))
It wraps a piece of LaTeX markup in a Math object and sends it to your notebook’s rich display, which can render the markup using the MathJax library:

LaTeX Fraction In a Jupyter Notebook
The next time you evaluate a notebook cell that contains a Fraction instance, it’ll draw a beautiful math formula instead of printing text.

Performing Rational Number Arithmetic on Fractions
As mentioned before, you can use fractions in arithmetic expressions consisting of other numeric types. Fractions will interoperate with most numeric types except for decimal.Decimal, which has its own set of rules. Moreover, the data type of the other operand, regardless of whether it lies to the left or the right of your fraction, will determine the type of your arithmetic operation’s result.

Addition
You can add two or more fractions without having to think about reducing them to their common denominator:

from fractions import Fraction
Fraction(1, 2) + Fraction(2, 3) + Fraction(3, 4)
Fraction(23, 12)
The result is a new fraction that’s the sum of all the input fractions. The same will happen when you add up integers and fractions:

Fraction(1, 2) + 3
Fraction(7, 2)
However, as soon as you start mixing fractions with non-rational numbers—that is, numbers that aren’t subclasses of numbers.Rational—then your fraction will be converted to that type first before being added:

Fraction(3, 10) + 0.1
0.4

float(Fraction(3, 10)) + 0.1
0.4
You get the same result whether or not you explicitly use float(). That conversion may result in some loss of precision since your fraction as well as the outcome are now stored in floating-point representation. Even though the number 0.4 seems right, it’s not exactly equal to the fraction 4/10.

Subtraction
Subtracting fractions is no different than adding them. Python will find the common denominator for you:

Fraction(3, 4) - Fraction(2, 3) - Fraction(1, 2)
Fraction(-5, 12)

Fraction(4, 10) - 0.1
0.30000000000000004
This time, the precision loss is so significant that it’s visible at a glance. Notice the long stream of zeros followed by a digit 4 at the end of the decimal expansion. It’s the result of rounding a value that would otherwise require an infinite number of binary digits.

Multiplication
When you multiply two fractions, their numerators and denominators get multiplied element-wise, and the resulting fraction gets automatically reduced if necessary:

Fraction(1, 4) * Fraction(3, 2)
Fraction(3, 8)

Fraction(1, 4) * Fraction(4, 5) # The result is 4/20
Fraction(1, 5)

Fraction(1, 4) * 3
Fraction(3, 4)

Fraction(1, 4) * 3.0
0.75
Again, depending on the type of the other operand, you’ll end up with a different data type in the result.

Division
There are two division operators in Python, and fractions support both of them:

True division: /
Floor division: //
The true division results in another fraction, while a floor division always returns a whole number with the fractional part truncated:

Fraction(7, 2) / Fraction(2, 3)
Fraction(21, 4)

Fraction(7, 2) // Fraction(2, 3)
5

Fraction(7, 2) / 2
Fraction(7, 4)

Fraction(7, 2) // 2
1

Fraction(7, 2) / 2.0
1.75

Fraction(7, 2) // 2.0
1.0
Note that the floor division’s result isn’t always an integer! The result may end up a float depending on what data type you use together with the fraction. Fractions also support the modulo operator (%) as well as the divmod() function, which might help in creating mixed fractions from improper ones:

def mixed(fraction):
... floor, rest = divmod(fraction.numerator, fraction.denominator)
... return f"{floor} and {Fraction(rest, fraction.denominator)}"
...
mixed(Fraction(22, 7))
'3 and 1/7'
Instead of generating a string like in the output above, you could update the function to return a tuple comprised of the whole part and the fractional remainder. Go ahead and try modifying the return value of the function to see the difference.

Exponentiation
You can raise fractions to a power with the binary exponentiation operator (**) or the built-in pow() function. You can also use fractions themselves as exponents. Go back to your Python interpreter now and start exploring how to raise fractions to a power:

Fraction(3, 4) ** 2
Fraction(9, 16)

Fraction(3, 4) ** (-2)
Fraction(16, 9)

Fraction(3, 4) ** 2.0
0.5625
You’ll notice that you can use both positive and negative exponent values. When the exponent isn’t a Rational number, your fraction is automatically converted to float before proceeding.

Things get more complicated when the exponent is a Fraction instance. Since fractional powers typically produce irrational numbers, both operands are converted to float unless the base and the exponent are whole numbers:

2 ** Fraction(2, 1)
4

2.0 ** Fraction(2, 1)
4.0

Fraction(3, 4) ** Fraction(1, 2)
0.8660254037844386

Fraction(3, 4) ** Fraction(2, 1)
Fraction(9, 16)
The only time you get a fraction as a result is when the denominator of the exponent is equal to one and you’re raising a Fraction instance.

Hope this will help.