Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Modern Python Cookbook

You're reading from   Modern Python Cookbook 130+ updated recipes for modern Python 3.12 with new techniques and tools

Arrow left icon
Product type Paperback
Published in Jul 2024
Publisher Packt
ISBN-13 9781835466384
Length 818 pages
Edition 3rd Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Steven F. Lott Steven F. Lott
Author Profile Icon Steven F. Lott
Steven F. Lott
Arrow right icon
View More author details
Toc

Table of Contents (20) Chapters Close

Preface 1. Chapter 1 Numbers, Strings, and Tuples FREE CHAPTER 2. Chapter 2 Statements and Syntax 3. Chapter 3 Function Definitions 4. Chapter 4 Built-In Data Structures Part 1: Lists and Sets 5. Chapter 5 Built-In Data Structures Part 2: Dictionaries 6. Chapter 6 User Inputs and Outputs 7. Chapter 7 Basics of Classes and Objects 8. Chapter 8 More Advanced Class Design 9. Chapter 9 Functional Programming Features 10. Chapter 10 Working with Type Matching and Annotations 11. Chapter 11 Input/Output, Physical Format, and Logical Layout 12. Chapter 12 Graphics and Visualization with Jupyter Lab 13. Chapter 13 Application Integration: Configuration 14. Chapter 14 Application Integration: Combination 15. Chapter 15 Testing 16. Chapter 16 Dependencies and Virtual Environments 17. Chapter 17 Documentation and Style 18. Other Books You May Enjoy
19. Index

1.1 Choosing between float, decimal, and fraction

Python offers several ways to work with rational numbers and approximations of irrational numbers. We have three basic choices:

  • Float

  • Decimal

  • Fraction

When we have choices, it helps to have some criteria for making a selection.

1.1.1 Getting ready

There are three general cases for expressions that involve numbers beyond integers, which are:

  1. Currency: Dollars, cents, euros, and so on. Currency generally has a fixed number of decimal places and rounding rules to properly quantize results.

  2. Rational Numbers or Fractions: When we scale a recipe that serves eight, for example, down to five people, we’re doing fractional math using a scaling factor of 5 8.

  3. Floating Point: This includes all other kinds of calculations. This also includes irrational numbers, like π, root extraction, and logarithms.

When we have one of the first two cases, we should avoid floating-point numbers.

1.1.2 How to do it...

We’ll look at each of the three cases separately.

Doing currency calculations

When working with currency, we should always use the decimal module. If we try to use the values of Python’s built-in float type, we can run into problems with the rounding and truncation of numbers:

  1. To work with currency, import the Decimal class from the decimal module:

    >>> from decimal import Decimal
  2. We need to create Decimal objects from strings or integers. In this case, we want 7.25%, which is 7.25 100. We can compute the value using Decimal objects:

    >>> tax_rate = Decimal(’7.25’)/Decimal(100) 
     
    >>> purchase_amount = Decimal(’2.95’) 
     
    >>> tax_rate * purchase_amount 
     
    Decimal(’0.213875’)

    We could also use Decimal(’0.0725’) instead of doing the division explicitly.

  3. To round to the nearest penny, create a penny object:

    >>> penny = Decimal(’0.01’)
  4. Quantize the result using the penny object:

    >>> total_amount = purchase_amount + tax_rate * purchase_amount 
     
    >>> total_amount.quantize(penny) 
     
    Decimal(’3.16’)

This uses the default rounding rule of ROUND_HALF_EVEN. The Decimal module offers other rounding variations. We might, for example, do something like this:

>>> import decimal 
 
>>> total_amount.quantize(penny, decimal.ROUND_UP) 
 
Decimal(’3.17’)

This shows the consequences of using a different rounding rule.

Fraction calculations

When we’re doing calculations that have exact fraction values, we can use the fractions module to create rational numbers. In this example, we want to scale a recipe for eight down to five people, using 58 of each ingredient. When the recipe calls for 21 2 cups of rice, what does that scale down to?

To work with fractions, we’ll do this:

  1. Import the Fraction class from the fractions module:

    >>> from fractions import Fraction
  2. Create Fraction objects from strings, integers, or pairs of integers. We created one fraction from a string, ’2.5’. We created the second fraction from a floating-point expression, 5 / 8. This only works when the denominator is a power of 2:

    >>> sugar_cups = Fraction(’2.5’) 
     
    >>> scale_factor = Fraction(5/8) 
     
    >>> sugar_cups * scale_factor 
     
    Fraction(25, 16)

We can see that we’ll use almost a cup and a half of rice to scale the recipe for five people instead of eight. While float values will often be useful for rational fractions, they may not be exact unless the denominator is a power of two.

Floating-point approximations

Python’s built-in float type can represent a wide variety of values. The trade-off here is that a float value is often an approximation. There may be a small discrepancy that reveals the differences between the implementation of float and the mathematical ideal of an irrational number:

  1. To work with float, we often need to round values to make them look sensible. It’s important to recognize that all float calculations are an approximation:

    >>> (19/155)*(155/19) 
     
    0.9999999999999999
  2. Mathematically, the value should be 1. Because of the approximations used, the computed result isn’t exactly 1. We can use round(answer, 3) to round to three digits, creating a value that’s more useful:

    >>> answer = (19/155)*(155/19) 
     
    >>> round(answer, 3) 
     
    1.0

Approximations have a very important consequence.

Don’t compare floating-point values for exact equality.

Code that uses an exact == test between floating-point numbers has the potential to cause problems when two approximations differ by a single bit.

The float approximation rules come from the IEEE, and are not a unique feature of Python. Numerous programming languages work with float approximations and have identical behavior.

1.1.3 How it works...

For these numeric types, Python offers a variety of operators: +, -, *, /, //, %, and **. These are for addition, subtraction, multiplication, true division, truncated division, modulo, and raising to a power, respectively. We’ll look at the two division operators, / and //, in the Choosing between true division and floor division recipe.

Python will do some conversions between the various numeric types. We can mix int and float values; the integers will be promoted to floating-point to provide the most accurate answer possible. Similarly, we can mix int and Fraction as well as mixing int and Decimal. Note that we cannot casually mix Decimal with float or Fraction; an explicit conversion function will be required.

It’s important to note that float values are approximations. The Python syntax allows us to write floating-point values using base 10 digits; however, that’s not how values are represented internally.

We can write the value 8.066 × 1067 like this in Python:

>>> 8.066e+67 
 
8.066e+67

The actual value used internally will involve a binary approximation of the decimal value we wrote. The internal value for this example is this:

>>> (6737037547376141/(2**53))*(2**226) 
 
8.066e+67

The numerator is a big number, 6737037547376141. The denominator is always 253. This is why values can get truncated.

We can use the math.frexp() function to see these internal details of a number:

>>> import math 
 
>>> math.frexp(8.066E+67) 
 
(0.7479614202861186, 226)

The two parts are called the mantissa (or significand) and the exponent. If we multiply the mantissa by 253, we always get a whole number, which is the numerator of the binary fraction.

Unlike the built-in float, a Fraction is an exact ratio of two integer values. We can create ratios that involve integers with a very large number of digits. We’re not limited by a fixed denominator.

A Decimal value, similarly, is based on a very large integer value, as well as a scaling factor to determine where the decimal place goes. These numbers can be huge and won’t suffer from peculiar representation issues.

1.1.4 There’s more...

The Python math module contains several specialized functions for working with floating-point values. This module includes common elementary functions such as square root, logarithms, and various trigonometry functions. It also has some other functions such as gamma, factorial, and the Gaussian error function.

The math module includes several functions that can help us do more accurate floating-point calculations. For example, the math.fsum() function will compute a floating-point sum more carefully than the built-in sum() function. It’s less susceptible to approximation issues.

We can also make use of the math.isclose() function to compare two floating-point values, an expression, and a literal 1.0, to see if they’re nearly equal:

>>> (19/155)*(155/19) == 1.0 
 
False 
 >>> math.isclose((19/155)*(155/19), 1.0) 
 
True

This function provides us with a way to compare two floating-point numbers meaningfully for near-equality.

Python also offers complex numbers. A complex number has a real and an imaginary part. In Python, we write 3.14+2.78j to represent the complex number 3.14 + 2.78√ --- − 1. Python will comfortably convert between float and complex. We have the usual group of operators available for complex numbers.

To support complex numbers, there’s the cmath package. The cmath.sqrt() function, for example, will return a complex value rather than raise an exception when extracting the square root of a negative number. Here’s an example:

>>> math.sqrt(-2) 
 
Traceback (most recent call last): 
 
... 
 
ValueError: math domain error 
 >>> import cmath 
 
>>> cmath.sqrt(-2) 
 
1.4142135623730951j

This module is helpful when working with complex numbers.

1.1.5 See also

You have been reading a chapter from
Modern Python Cookbook - Third Edition
Published in: Jul 2024
Publisher: Packt
ISBN-13: 9781835466384
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime