Lab: The Floating-Point Trap
Try running this in your Python terminal to see the logic gap:
- Step 1: Calculate
0.1 + 0.2. - Step 2: Compare it to
0.3. - Step 3: Use the
decimalmodule to see the “true” value.
Python
import decimal
# The Trap
print(0.1 + 0.2 == 0.3) # Result: False
# The Reality
print(decimal.Decimal(0.1))
# You will see: 0.1000000000000000055511151231257827021181583404541015625
Lab: Basics & Type Conversion
Task:
Calculate the total and print its type.
Create a variable price with the value 19.99.
Convert the integer 5 to a float and store it in quantity.
import math
price = 19.99
quantity = float(5)
total = price * quantity
print(f"Total: {total}, Type: {type(total)}")
# Output: Total: 99.95, Type: <class 'float'>Lab: Precision & Rounding
Use a built-in function to round result to 2 decimal places.
Calculate $0.1 + 0.2$ and store it in result.
Print result. (Note: Observe if it is exactly 0.3).
import math
result = 0.1 + 0.2
print(f"Raw result: {result}")
# Output: 0.30000000000000004 (Due to binary floating-point representation)
rounded_result = round(result, 2)
print(f"Rounded: {rounded_result}")Why does 0.1 + 0.2 not equal 0.3?
In Python (and most programming languages), floats are represented in binary (base-2). Just as the fraction 1/3 cannot be perfectly represented in decimal (it’s 0.3333…), the fraction 1/10 (0.1) cannot be perfectly represented in binary. This leads to the tiny “overflow” you see in the raw result.
Lab: Formatting Output
Task:
Given pi_approx = 22 / 7, print the value formatted to exactly 3 decimal places using an f-string.
import math
pi_approx = 22 / 7
print(f"Pi to 3 decimal places: {pi_approx:.3f}")Lab: The math Module
Task:
Use math.isclose() to check if 0.1 + 0.2 is effectively equal to 0.3.
Import the math module.
Use math.floor() and math.ceil() on the number 4.7.
import math
val = 4.7
print(f"Floor: {math.floor(val)}") # 4
print(f"Ceil: {math.ceil(val)}") # 5
# Checking equality for floats
comparison = math.isclose(0.1 + 0.2, 0.3)
print(f"Is 0.1 + 0.2 close to 0.3? {comparison}")Lab: The Initialization
Goal: Create a script that handles user input for a grocery store.
- Ask the user for theÂ
price of an item (e.g., 2.50). - Ask for theÂ
quantity (e.g., 3). - Calculate the total. Even if the user enters “3”, ensure the calculation treats it as a float.
- Bonus:Â Print the memory address of the float variable usingÂ
id().
import math
# Using float() to wrap input ensures we don't get a string error
price = float(input("Enter price: ")) # User enters 2.5
quantity = float(input("Enter quantity: ")) # User enters 3
total = price * quantity
print(f"Total: {total}") # Output: 7.5
print(f"Memory ID: {id(total)}")Lab:
The Precision Paradox
Goal: Demonstrate why developers never use floats for financial calculations.
- AddÂ
0.1Â andÂ0.2. - Compare the result toÂ
0.3Â using theÂ==Â operator. - Explain why the result isÂ
False using a comment.
import math
result = 0.1 + 0.2
print(f"0.1 + 0.2 is {result}")
# Result: 0.30000000000000004
print(result == 0.3) # Output: False
# Fix: Use math.isclose for floating point comparison
print(math.isclose(result, 0.3)) # Output: True
Lab:
Advanced Rounding
Goal: Explore the math module.
- GivenÂ
val = 12.51, use three different methods to turn it into a whole number:math.floor()math.ceil()int()Â conversion.
- Observe how they differ for the same value.
import math
val = 12.51
print(math.floor(val)) # 12 (Down to nearest integer)
print(math.ceil(val)) # 13 (Up to nearest integer)
print(int(val)) # 12 (Truncates everything after decimal)
Lab:
Scientific Notation & Large Numbers
Goal: Work with very small and very large floats.
- Create a variableÂ
atoms equal to 6.022×1023. - Create a variableÂ
size equal to 0.000000005. - Print them and see how Python automatically formats them.
import math
### Solution 4: Scientific Notation
atoms = 6.022e23
size = 5e-9
print(atoms) # 6.022e+23
print(size) # 5e-09Lab:
The “Boss Level” Precision (The decimal Module)
Goal: Fix the floating-point error from Challenge 2 using Python’s decimal module.
- ImportÂ
Decimal from theÂdecimal module. - Create two variables usingÂ
Decimal, but initialize them with strings ("0.1" andÂ"0.2"). - Add them together and compare the result toÂ
Decimal("0.3"). - Question:Â Why must we use strings insideÂ
Decimal()Â instead of raw floats?
from decimal import Decimal
### Solution 5: The Decimal Module
# Using strings ensures the decimal starts exact.
# If you used Decimal(0.1), you'd just be wrapping an imprecise float!
d1 = Decimal("0.1")
d2 = Decimal("0.2")
d_sum = d1 + d2
print(f"Decimal Sum: {d_sum}") # Output: 0.3
print(d_sum == Decimal("0.3")) # Output: True
Lab:
Infinity and “Not a Number”
- Create a variableÂ
inf_val by converting the stringÂ"inf" to a float. - Create a variableÂ
nan_val by converting the stringÂ"nan" to a float. - Try addingÂ
1Â toÂinf_val. What happens?
from decimal import Decimal
### Solution 6: Infinity and NaN
inf_val = float("inf")
nan_val = float("nan")
print(inf_val + 1) # Output: inf (Infinity plus anything is still infinity)
print(nan_val == nan_val) # Output: False
# EXPLANATION: NaN is never equal to anything, including itself.
# To check for NaN, use math.isnan().
Lab:
Floating Point Representation
Goal: See how Python “thinks” about a float under the hood.
- Take the floatÂ
0.125. - Use the methodÂ
.as_integer_ratio()Â on it. - Question:Â What does this method tell you about how the float is stored?
from decimal import Decimal
val = 0.125
ratio = val.as_integer_ratio()
print(ratio) # Output: (1, 8)
# EXPLANATION: 0.125 is exactly 1/8.
# Since 8 is a power of 2, this float is perfectly precise in binaryWhen to use what?
| Scenario | Recommended Tool |
| General Science/Physics | Standard float (Fast, handles scientific notation well). |
| Money / Accounting | decimal.Decimal (Slow, but 100% accurate to human decimals). |
| Complex Math | math or cmath module. |
| Massive Data Sets | numpy.float64 (Extremely memory efficient). |
Lab:
Mixed-Type Arithmetic
Goal: Understand “Coercion” (how Python promotes types).
- Create an integerÂ
a = 5Â and a floatÂb = 2.0. - DivideÂ
a / 2. What is the resulting type? - PerformÂ
a + b. What is the resulting type? - Question:Â Why does Python choose to turn integers into floats during math instead of the other way around?
import math
### Solution 8: Type Coercion
a = 5
b = 2.0
print(type(a / 2)) # <class 'float'> (True division always returns a float)
print(type(a + b)) # <class 'float'>
# EXPLANATION: Python "promotes" the integer to a float because floats
# can represent a wider range of values (like decimals).
# Converting a float to an int would lose data (truncation).
Lab:
Dealing with “Dirty” Strings
Goal: Clean up data commonly found in web scraping or CSV files.
- You are given a list of prices as strings:Â
raw_data = ["$12.50", " 100.99 ", "N/A", "5.00"]. - Loop through the list and:
- Strip theÂ
$Â sign. - Remove extra whitespace.
- Skip or handle strings like “N/A” so the code doesn’t crash.
- Convert the valid prices to floats and sum them.
- Strip theÂ
import math
raw_data = ["$12.50", " 100.99 ", "N/A", "5.00"]
clean_prices = []
for item in raw_data:
try:
# Clean the string
processed = item.replace("$", "").strip()
# Attempt conversion
clean_prices.append(float(processed))
except ValueError:
print(f"Skipping invalid data: {item}")
print(f"Total: {sum(clean_prices)}") # Output: 118.49
Lab:
The fsum Advantage
Goal: Learn how to sum a large list of floats without losing a single cent.
- Create a list:Â
values = [0.1] * 10. - Sum them using the standardÂ
sum(values). - Sum them usingÂ
math.fsum(values). - Compare the results.
import math
values = [0.1] * 10
standard_sum = sum(values)
precise_sum = math.fsum(values)
print(f"Standard: {standard_sum}") # Output: 0.9999999999999999
print(f"fsum: {precise_sum}") # Output: 1.0
# EXPLANATION: math.fsum tracks the "lost" precision bits during
# each addition, making it superior for summing long lists of floats.Lab:
Bits and Bytes
Goal: Visualize how a float looks in its hex (hexadecimal) form.
- Python floats have aÂ
.hex()Â method. Take the floatÂ2.5Â andÂ0.1Â and print their hex strings. - Question:Â Why is the hex forÂ
2.5Â much shorter and “cleaner” than the hex forÂ0.1?
import math
### Solution 11: Hex Representation
print((2.5).hex()) # Output: '0x1.4000000000000p+1'
print((0.1).hex()) # Output: '0x1.999999999999ap-4' (Notice the repeating '9999...')
# EXPLANATION: 2.5 is a "dyadic" rational (the denominator is a power of 2),
# so it terminates in binary. 0.1 repeats infinitely, just like 1/3 in decimal.
Lab:
Comparison with Epsilon
Goal: Understand the “smallest possible” difference.
- In numerical computing, we often compare a difference to a tiny value called Epsilon.
- Calculate the absolute difference betweenÂ
(0.1 + 0.2)Â andÂ0.3. - Check if this difference is smaller thanÂ
1e-15.
import math
diff = abs((0.1 + 0.2) - 0.3)
epsilon = 1e-15
print(f"Difference: {diff}")
print(f"Is it negligible? {diff < epsilon}") # Output: True
# NOTE: This is essentially what math.isclose() does under the hood.
Lab:
List Filtering & Overflow
Goal: Handle a list containing “broken” float data.
- Create a list:Â
data = [1.5, float('inf'), 2.2, float('nan'), 3.7, -float('inf')]. - Write a list comprehension to create a new list that only contains finite, real numbers (noÂ
inf orÂnan). - Hint: UseÂ
math.isfinite().
### Solution 13: Filtering with math.isfinite()
data = [1.5, float('inf'), 2.2, float('nan'), 3.7, -float('inf')]
# math.isfinite() returns False for NaN and both positive/negative Infinity
clean_data = [x for x in data if math.isfinite(x)]
print(f"Cleaned Data: {clean_data}") # Output: [1.5, 2.2, 3.7]