4. 使用数字
在第 3 章中,你学习了如何创建变量,并使用 Python 的基本数值类型:整数和浮点数。现在是时候真正“让这些数字动起来”了。在本章中,你将学习如何执行计算、组合运算,并使用 Python 的内置工具来处理数值数据。
数字在编程中是基础。无论你是在计算总和、处理测量数据、管理库存,还是分析数据,你都需要执行算术运算。Python 让数值运算变得直观易用,但其中也有一些重要细节需要理解——特别是不同类型的除法如何工作、运算符之间如何相互作用,以及浮点数的行为方式。
在本章结束时,你会熟练地执行各种计算,理解运算符的行为,并使用 Python 的数值函数来解决真实世界中的问题。
4.1) 基本算术:加法、减法和乘法
先从最基础的算术运算开始。Python 为基本数学运算使用熟悉的符号,这些运算的行为和你日常接触的算术基本一致。
4.1.1) 使用 + 运算符进行加法
+ 运算符用于将两个数字相加。它既适用于整数,也适用于浮点数:
# basic_addition.py
# Adding integers
total = 15 + 27
print(total) # Output: 42
# Adding floats
price = 19.99 + 5.50
print(price) # Output: 25.49
# You can add multiple numbers in one expression
sum_total = 10 + 20 + 30 + 40
print(sum_total) # Output: 100加法本身很直接,但有一个重要细节:当你把一个整数和一个浮点数相加时,Python 会自动把结果转换为浮点数,以保留小数精度:
# mixed_addition.py
result = 10 + 3.5
print(result) # Output: 13.5
print(type(result)) # Output: <class 'float'>之所以会发生这种自动转换,是因为浮点数既可以表示整数也可以表示小数,而整数无法表示小数。Python 会选择不会丢失信息的类型。
4.1.2) 使用 - 运算符进行减法
- 运算符会用第一个数减去第二个数:
# basic_subtraction.py
# Subtracting integers
difference = 100 - 42
print(difference) # Output: 58
# Subtracting floats
remaining = 50.75 - 12.25
print(remaining) # Output: 38.5
# Subtraction can produce negative results
balance = 20 - 35
print(balance) # Output: -15和加法一样,当你混合使用整数和浮点数时,减法也会把整数提升为浮点数:
# mixed_subtraction.py
result = 100 - 0.01
print(result) # Output: 99.99
print(type(result)) # Output: <class 'float'>4.1.3) 使用 * 运算符进行乘法
* 运算符用于将两个数字相乘:
# basic_multiplication.py
# Multiplying integers
product = 6 * 7
print(product) # Output: 42
# Multiplying floats
area = 3.5 * 2.0
print(area) # Output: 7.0
# Multiplying by zero always gives zero
result = 1000 * 0
print(result) # Output: 0乘法遵循相同的类型转换规则。当你用一个整数和一个浮点数相乘时,结果是浮点数:
# mixed_multiplication.py
result = 5 * 2.5
print(result) # Output: 12.5
print(type(result)) # Output: <class 'float'>
# Even if the result is a whole number
result = 4 * 2.0
print(result) # Output: 8.0 (note the .0)
print(type(result)) # Output: <class 'float'>注意最后一个例子:尽管 4 × 2.0 在数学上等于 8,Python 仍然将其表示为 8.0,因为参与运算的其中一个操作数是浮点数。结果的类型由输入的类型决定,而不是由数学结果是否是整数决定。
4.1.4) 基本算术的实际示例
来看这些运算在更贴近实际场景中的组合使用方式:
# shopping_cart.py
# Calculate a shopping cart total
item1_price = 12.99
item2_price = 8.50
item3_price = 15.00
subtotal = item1_price + item2_price + item3_price
print(f"Subtotal: ${subtotal}") # Output: Subtotal: $36.49
tax_rate = 0.08
tax = subtotal * tax_rate
print(f"Tax: ${tax}") # Output: Tax: $2.9192
total = subtotal + tax
print(f"Total: ${total}") # Output: Total: $39.4092# temperature_change.py
# Calculate temperature change
morning_temp = 45.5
afternoon_temp = 68.2
change = afternoon_temp - morning_temp
print(f"Temperature increased by {change} degrees")
# Output: Temperature increased by 22.7 degrees这些基本运算构成了更复杂计算的基础。理解它们的工作方式——尤其是 Python 如何处理类型转换——可以帮助你避免在程序执行计算时遇到意料之外的结果。
4.2) 除法、地板除和余数:/、// 和 %
在 Python 中,除法比加法、减法或乘法更复杂。Python 提供了三种与除法相关的运算符,它们各自有不同的用途。理解何时使用哪一种,对写出正确的程序非常关键。
4.2.1) 使用 / 进行常规除法
/ 运算符执行“真实除法 (true division)”——它总是返回一个浮点数结果,即使两个操作数都是整数:
# true_division.py
# Dividing integers
result = 10 / 2
print(result) # Output: 5.0 (note: float, not 5)
print(type(result)) # Output: <class 'float'>
# Division that doesn't result in a whole number
result = 10 / 3
print(result) # Output: 3.3333333333333335
# Dividing floats
result = 15.5 / 2.5
print(result) # Output: 6.2这里的关键点是:/ 总是 产生浮点数结果,即使两个操作数都是整数,并且结果在数学上恰好是整数。这是刻意设计的:Python 希望除法的行为保持一致,并尽可能保留小数精度。
这种行为和某些其他编程语言不同,在那些语言中,两个整数相除会得到整数结果。在 Python 3 中,如果你想要整数结果,需要使用地板除(下一小节会介绍),或者显式地转换结果类型。
4.2.2) 使用 // 进行地板除 (floor division)
// 运算符执行“地板除 (floor division)”——它先做除法,然后向下取整到最接近的整数。当两个操作数都是整数时,结果是整数;当任一操作数是浮点数时,结果是浮点数(但仍然是向下取整后的值):
# floor_division.py
# Floor division with integers
result = 10 // 3
print(result) # Output: 3 (not 3.333...)
print(type(result)) # Output: <class 'int'>
# Even division still gives an integer
result = 10 // 2
print(result) # Output: 5 (integer, not 5.0)
print(type(result)) # Output: <class 'int'>
# Floor division with floats gives a float
result = 10.0 // 3
print(result) # Output: 3.0
print(type(result)) # Output: <class 'float'>“向下取整”是指朝负无穷方向前进,而不仅仅是简单地去掉小数部分。这在涉及负数时尤其重要:
# floor_division_negative.py
# Positive numbers: rounds down (toward negative infinity)
result = 7 // 2
print(result) # Output: 3
# Negative numbers: still rounds toward negative infinity
result = -7 // 2
print(result) # Output: -4 (not -3!)
# Why -4? Because -3.5 rounded down (toward negative infinity) is -4
# Think of the number line: ... -4, -3.5, -3, -2, -1, 0, 1, 2, 3, 3.5, 4 ...当你需要将物品分成若干组,或者计算有多少完整单元可以装下某个数量时,地板除非常有用:
# floor_division_practical.py
# How many complete dozens in 50 eggs?
eggs = 50
eggs_per_dozen = 12
complete_dozens = eggs // eggs_per_dozen
print(f"Complete dozens: {complete_dozens}") # Output: Complete dozens: 4
# How many full hours in 150 minutes?
minutes = 150
full_hours = minutes // 60
print(f"Full hours: {full_hours}") # Output: Full hours: 24.2.3) 使用 % 求余数(取模)
% 运算符(称为“取模 (modulo)”或“模运算 (mod)”)返回除法后的余数。它回答的问题是:“除完之后还剩多少?”
# modulo_basic.py
# 10 divided by 3 is 3 with remainder 1
result = 10 % 3
print(result) # Output: 1
# 10 divided by 2 is 5 with remainder 0 (even division)
result = 10 % 2
print(result) # Output: 0
# Works with floats too
result = 10.5 % 3
print(result) # Output: 1.5取模运算在许多常见编程模式中极其有用:
判断一个数是偶数还是奇数:
# even_odd_check.py
number = 17
if number % 2 == 0:
print(f"{number} is even")
else:
print(f"{number} is odd")
# Output: 17 is odd计算剩余的项目数量:
# leftover_items.py
eggs = 50
eggs_per_dozen = 12
leftover = eggs % eggs_per_dozen
print(f"Leftover eggs: {leftover}") # Output: Leftover eggs: 2在某个范围内“环绕”数值(类似时钟算术):
# clock_arithmetic.py
# What hour is it 25 hours from now? (on a 12-hour clock)
current_hour = 10
hours_later = 25
future_hour = (current_hour + hours_later) % 12
print(f"Hour: {future_hour}") # Output: Hour: 114.2.4) //、% 和 / 之间的关系
地板除和取模运算关系紧密。它们一起可以给出除法的完整结果:
# division_relationship.py
dividend = 17
divisor = 5
quotient = dividend // divisor # How many times 5 goes into 17
remainder = dividend % divisor # What's left over
print(f"{dividend} ÷ {divisor} = {quotient} remainder {remainder}")
# Output: 17 ÷ 5 = 3 remainder 2
# You can verify: quotient * divisor + remainder should equal dividend
verification = quotient * divisor + remainder
print(f"Verification: {quotient} × {divisor} + {remainder} = {verification}")
# Output: Verification: 3 × 5 + 2 = 17这个关系始终成立:对于任意数字 a 和 b(其中 b 不为 0),都有 a == (a // b) * b + (a % b)。
4.2.5) 选择合适的除法运算符
下面是一个简要的选择指南:
- 当你需要精确的数学结果并保留小数(大多数计算场景)时,使用
/ - 当你需要计算有多少“完整的组”能装下某个数量(例如打“打”、小时、页数)时,使用
// - 当你需要余数或剩余量(例如判断奇偶、范围环绕、计算剩余)时,使用
%
# choosing_operators.py
# Scenario: Distributing 47 candies among 6 children
candies = 47
children = 6
# How many candies per child? (use //)
per_child = candies // children
print(f"Each child gets {per_child} candies") # Output: Each child gets 7 candies
# How many candies are left over? (use %)
leftover = candies % children
print(f"Leftover candies: {leftover}") # Output: Leftover candies: 5
# What's the exact average? (use /)
average = candies / children
print(f"Average per child: {average}") # Output: Average per child: 7.8333333333333334.3) 复合赋值运算符
在编程中,你经常需要基于变量当前的值来更新它。例如,为累计总和增加值、从余额中扣除金额,或将某个值按比例放大。Python 提供了一种简洁的方式来完成这些操作,这就是复合赋值 (augmented assignment) 运算符。
4.3.1) 什么是复合赋值运算符
复合赋值运算符将算术运算与赋值操作结合在一起。你不必这样写:
# traditional_update.py
count = 10
count = count + 5 # Add 5 to count
print(count) # Output: 15而是可以写成:
# augmented_update.py
count = 10
count += 5 # Same as: count = count + 5
print(count) # Output: 15两种写法效果完全相同,但使用复合赋值的版本更简洁,而且更清晰地表达了意图:“让 count 增加 5”。
4.3.2) 所有的复合赋值运算符
Python 为所有算术运算都提供了对应的复合赋值运算符:
# all_augmented_operators.py
# Addition
x = 10
x += 5 # x = x + 5
print(f"After += 5: {x}") # Output: After += 5: 15
# Subtraction
x = 10
x -= 3 # x = x - 3
print(f"After -= 3: {x}") # Output: After -= 3: 7
# Multiplication
x = 10
x *= 4 # x = x * 4
print(f"After *= 4: {x}") # Output: After *= 4: 40
# Division
x = 10
x /= 2 # x = x / 2
print(f"After /= 2: {x}") # Output: After /= 2: 5.0
# Floor division
x = 10
x //= 3 # x = x // 3
print(f"After //= 3: {x}") # Output: After //= 3: 3
# Modulo
x = 10
x %= 3 # x = x % 3
print(f"After %= 3: {x}") # Output: After %= 3: 1
# Exponentiation (we'll see more about ** in section 4.6)
x = 2
x **= 3 # x = x ** 3 (2 to the power of 3)
print(f"After **= 3: {x}") # Output: After **= 3: 84.3.3) 复合赋值的常见用法
复合赋值运算符在一些常见的编程模式中非常好用:
累计总和:
# accumulating_total.py
total = 0
total += 10 # Add first item
total += 25 # Add second item
total += 15 # Add third item
print(f"Total: {total}") # Output: Total: 50计数出现次数:
# counting.py
count = 0
# Imagine these happen as we process data
count += 1 # Found one
count += 1 # Found another
count += 1 # Found another
print(f"Count: {count}") # Output: Count: 3更新余额:
# balance_updates.py
balance = 100.00
balance -= 25.50 # Purchase
balance += 50.00 # Deposit
balance -= 10.00 # Purchase
print(f"Balance: ${balance}") # Output: Balance: $114.5执行重复运算:
# repeated_operations.py
value = 100
value *= 1.1 # Increase by 10%
value *= 1.1 # Increase by 10% again
value *= 1.1 # Increase by 10% again
print(f"Value after three 10% increases: {value}")
# Output: Value after three 10% increases: 133.100000000000024.3.4) 关于复合赋值的重要细节
对于不可变类型 (immutable type),复合赋值会创建新对象:
回忆第 3 章中的内容,数字是不可变的——你无法修改一个数字的值,只能创建一个新的数字。当你写 x += 5 时,Python 会创建一个新的数字对象,并把它赋给 x。旧的数字对象会被丢弃(我们会在第 17 章关于 Python 对象模型时更深入讨论):
# augmented_with_immutables.py
x = 10
print(id(x)) # Output: (some memory address)
x += 5
print(id(x)) # Output: (different memory address)目前你只需要理解:x += 5 等价于 x = x + 5——它只是一个方便的简写形式,而不是一种完全不同的操作。
在变量存在之前不能使用复合赋值:
# augmented_requires_existing.py
# This will cause an error:
# count += 1 # NameError: name 'count' is not defined
# You must initialize the variable first:
count = 0
count += 1 # Now this works
print(count) # Output: 1类型转换规则与普通运算相同:
# augmented_type_conversion.py
x = 10 # integer
x += 2.5 # Add a float
print(x) # Output: 12.5
print(type(x)) # Output: <class 'float'>结果遵循和普通运算相同的类型转换规则:当你混合使用整数和浮点数时,结果为浮点数。
4.3.5) 何时使用复合赋值
只要你是在基于变量当前值更新它时,就可以使用复合赋值运算符。它们能让你的代码:
- 更简洁:
x += 5比x = x + 5更短 - 更易读:意图一目了然
- 更不易出错:减少重复书写变量名时打错的风险
对比这两个版本:
# comparison.py
# Without augmented assignment
accumulated_distance_in_kilometers = 0
accumulated_distance_in_kilometers = accumulated_distance_in_kilometers + 10
accumulated_distance_in_kilometers = accumulated_distance_in_kilometers + 25
# With augmented assignment
accumulated_distance_in_kilometers = 0
accumulated_distance_in_kilometers += 10
accumulated_distance_in_kilometers += 25使用复合赋值的版本更清晰,也更不容易出现拼写错误。复合赋值运算符虽然是一个小特性,但在真实的 Python 代码中被频繁使用。
4.4) 运算符优先级与括号
当你在一个表达式中组合多个运算时,Python 需要一套规则来决定运算顺序。表达式 2 + 3 * 4 应该被计算为 (2 + 3) * 4 = 20,还是 2 + (3 * 4) = 14?答案取决于运算符优先级——即决定哪些运算先执行的规则。
4.4.1) 理解运算符优先级
Python 遵循你在数学中学过的同样优先级规则:乘法和除法优先于加法和减法。这通常用缩写 PEMDAS 来记忆(Parentheses 括号、Exponents 幂、Multiplication/Division 乘除、Addition/Subtraction 加减),我们会在 4.6 小节讨论幂运算。
# precedence_basic.py
# Multiplication happens before addition
result = 2 + 3 * 4
print(result) # Output: 14 (not 20)
# Python calculates: 2 + (3 * 4) = 2 + 12 = 14
# Division happens before subtraction
result = 10 - 8 / 2
print(result) # Output: 6.0 (not 1.0)
# Python calculates: 10 - (8 / 2) = 10 - 4.0 = 6.0到目前为止我们见过的运算符,其优先级从高到低如下:
- 括号
()—— 优先级最高,总是最先计算 - 幂运算
**——(将在 4.6 小节介绍) - 乘法、除法、地板除、取模
*、/、//、%—— 同一优先级,从左到右计算 - 加法、减法
+、-—— 同一优先级,从左到右计算
下面是一些关于优先级的更多示例:
# precedence_examples.py
# Multiplication before addition
result = 5 + 2 * 3
print(result) # Output: 11 (5 + 6)
# Division before subtraction
result = 20 - 10 / 2
print(result) # Output: 15.0 (20 - 5.0)
# Multiple operations at the same level: left to right
result = 10 - 3 + 2
print(result) # Output: 9 ((10 - 3) + 2)
result = 20 / 4 * 2
print(result) # Output: 10.0 ((20 / 4) * 2)4.4.2) 使用括号控制运算顺序
括号会覆盖默认的优先级。无论包含什么运算,括号中的内容总是优先计算:
# parentheses_override.py
# Without parentheses: multiplication first
result = 2 + 3 * 4
print(result) # Output: 14
# With parentheses: addition first
result = (2 + 3) * 4
print(result) # Output: 20
# Another example
result = 10 - 8 / 2
print(result) # Output: 6.0
result = (10 - 8) / 2
print(result) # Output: 1.0你可以嵌套括号来构造更复杂的表达式。Python 总是从最内层的括号开始计算:
# nested_parentheses.py
result = ((2 + 3) * 4) - 1
print(result) # Output: 19
# Step 1: (2 + 3) = 5
# Step 2: (5 * 4) = 20
# Step 3: 20 - 1 = 19
result = 2 * (3 + (4 - 1))
print(result) # Output: 12
# Step 1: (4 - 1) = 3
# Step 2: (3 + 3) = 6
# Step 3: 2 * 6 = 124.4.3) 当运算符具有相同优先级时
当表达式中有多个同一优先级的运算符时,Python 按从左到右的顺序计算(这称为“左结合性 (left associativity)”):
# left_to_right.py
# Addition and subtraction: left to right
result = 10 - 3 + 2 - 1
print(result) # Output: 8
# Evaluation: ((10 - 3) + 2) - 1 = (7 + 2) - 1 = 9 - 1 = 8
# Multiplication and division: left to right
result = 20 / 4 * 2
print(result) # Output: 10.0
# Evaluation: (20 / 4) * 2 = 5.0 * 2 = 10.0
# This matters! Different order gives different result:
result = 20 / (4 * 2)
print(result) # Output: 2.54.4.4) 运算优先级的实际示例
来看一些真实场景中优先级很关键的例子:
# temperature_conversion.py
# Convert Fahrenheit to Celsius: C = (F - 32) * 5 / 9
fahrenheit = 98.6
# Correct: parentheses ensure subtraction happens first
celsius = (fahrenheit - 32) * 5 / 9
print(f"{fahrenheit}°F = {celsius}°C")
# Output: 98.6°F = 37.0°C
# Wrong: without parentheses, multiplication happens first
# celsius = fahrenheit - 32 * 5 / 9 # This would be wrong!
# This would calculate: fahrenheit - ((32 * 5) / 9)# calculate_average.py
# Calculate average of three numbers
num1 = 85
num2 = 92
num3 = 78
# Correct: parentheses ensure addition happens before division
average = (num1 + num2 + num3) / 3
print(f"Average: {average}") # Output: Average: 85.0
# Wrong: without parentheses, only num3 is divided by 3
# average = num1 + num2 + num3 / 3 # This would be wrong!
# This would calculate: 85 + 92 + (78 / 3) = 85 + 92 + 26.0 = 203.0# discount_calculation.py
# Calculate price after discount and tax
original_price = 100.00
discount_rate = 0.20
tax_rate = 0.08
# Calculate discount amount
discount = original_price * discount_rate
# Calculate price after discount
discounted_price = original_price - discount
# Calculate final price with tax
final_price = discounted_price * (1 + tax_rate)
print(f"Final price: ${final_price}") # Output: Final price: $86.4
# Or in one expression (using parentheses to be clear):
final_price = (original_price * (1 - discount_rate)) * (1 + tax_rate)
print(f"Final price: ${final_price}") # Output: Final price: $86.44.4.5) 运算符优先级的最佳实践
即使不是必须,也使用括号提升可读性:
有时即便不影响结果,添加括号也能让意图更清晰:
# clarity_with_parentheses.py
# These are equivalent, but the second is clearer:
result = 2 + 3 * 4
result = 2 + (3 * 4) # Clearer: shows you know multiplication happens first
# Complex expressions benefit from parentheses:
result = (subtotal * tax_rate) + (subtotal * tip_rate) # Clear intent将复杂表达式拆分为多个步骤:
当表达式过于复杂时,通常更好的做法是拆成多行:
# breaking_into_steps.py
# Instead of this complex one-liner:
result = ((price * quantity) * (1 - discount)) * (1 + tax)
# Consider breaking it into steps:
subtotal = price * quantity
discounted = subtotal * (1 - discount)
final = discounted * (1 + tax)
result = final多步版本更易阅读、调试和验证。不要为了简短而牺牲清晰度。
如果拿不准,就用括号:
如果你不确定优先级,直接加括号。Python 解释器不会介意,而未来的你(或者阅读你代码的其他程序员)会感谢你。
理解运算符优先级有助于你写出正确的表达式,也有助于阅读他人的代码。核心原则是:当你组合多个运算时,要考虑计算顺序,并用括号明确表达你的意图。
4.5) 在表达式中混合使用整数和浮点数
你已经看到,Python 在简单运算中会自动处理整数和浮点数的混合使用。现在我们系统地探讨这种行为,并理解数值表达式中类型转换的规则。
4.5.1) 类型提升规则
当 Python 执行的算术运算同时涉及整数和浮点数时,它会自动先把整数转换(“提升 (promote)”)为浮点数,再进行运算。结果总是浮点数:
# type_promotion.py
# Integer + Float = Float
result = 10 + 3.5
print(result) # Output: 13.5
print(type(result)) # Output: <class 'float'>
# Float + Integer = Float (order doesn't matter)
result = 3.5 + 10
print(result) # Output: 13.5
print(type(result)) # Output: <class 'float'>
# This applies to all arithmetic operators
result = 5 * 2.0
print(result) # Output: 10.0 (float, not int)
print(type(result)) # Output: <class 'float'>为什么 Python 要这么做?因为浮点数可以表示整数和小数,而整数不能表示小数。转换为浮点数可以保留所有信息,避免精度丢失。
下面是一个 Python 如何决定结果类型的可视化示意:
4.5.2) 复杂表达式中的类型提升
当一个表达式包含多个混合类型运算时,Python 会在每一步都应用类型提升规则:
# complex_mixed_types.py
# Multiple operations with mixed types
result = 10 + 3.5 * 2
print(result) # Output: 17.0
print(type(result)) # Output: <class 'float'>
# What happens step by step:
# 1. 3.5 * 2 → 3.5 * 2.0 (2 promoted to float) → 7.0 (float)
# 2. 10 + 7.0 → 10.0 + 7.0 (10 promoted to float) → 17.0 (float)
# Another example
result = 5 / 2 + 3
print(result) # Output: 5.5
print(type(result)) # Output: <class 'float'>
# Step by step:
# 1. 5 / 2 → 2.5 (division always produces float)
# 2. 2.5 + 3 → 2.5 + 3.0 (3 promoted to float) → 5.5 (float)一旦表达式中的某一步产生了浮点数,后续涉及该结果的所有运算也都会产生浮点数。
4.5.3) 特例:常规除法总会产生浮点数
回忆 4.2 小节中所说,/ 运算符总会产生浮点数,即使两个操作数都是整数:
# division_always_float.py
# Even when the result is a whole number
result = 10 / 2
print(result) # Output: 5.0 (not 5)
print(type(result)) # Output: <class 'float'>
# This means any expression with / will have a float result
result = 10 / 2 + 3 # 5.0 + 3 → 5.0 + 3.0 → 8.0
print(result) # Output: 8.0
print(type(result)) # Output: <class 'float'>如果你希望从除法中得到整数结果,可以使用 //(但要记住它是向下取整):
# floor_division_integer.py
# Floor division with integers produces an integer
result = 10 // 2
print(result) # Output: 5 (integer)
print(type(result)) # Output: <class 'int'>
# But floor division with any float produces a float
result = 10.0 // 2
print(result) # Output: 5.0 (float)
print(type(result)) # Output: <class 'float'>4.5.4) 类型混合的实际影响
理解类型提升规则有助于你预测和控制结果的类型:
# practical_type_mixing.py
# Calculating price per item
total_cost = 47.50
num_items = 5
price_per_item = total_cost / num_items # Float / int → float
print(f"Price per item: ${price_per_item}")
# Output: Price per item: $4.75
# Calculating average (will be float even if inputs are integers)
total_points = 450
num_tests = 5
average = total_points / num_tests # Int / int → float
print(f"Average: {average}") # Output: Average: 90.0
# If you need an integer result, convert explicitly
average_rounded = int(total_points / num_tests)
print(f"Average (as integer): {average_rounded}")
# Output: Average (as integer): 904.5.5) 当你需要整数结果时
有时你特别需要整数结果用于计数、索引或其他离散操作。下面是确保得到整数的几种方法:
# ensuring_integer_results.py
# Using floor division when you need integer results
items = 47
items_per_box = 12
# How many complete boxes?
complete_boxes = items // items_per_box # Integer result
print(f"Complete boxes: {complete_boxes}")
# Output: Complete boxes: 3
# If you use regular division, you get a float
boxes_float = items / items_per_box
print(f"Boxes (float): {boxes_float}")
# Output: Boxes (float): 3.9166666666666665
# Converting float to integer (truncates toward zero)
boxes_truncated = int(boxes_float)
print(f"Boxes (truncated): {boxes_truncated}")
# Output: Boxes (truncated): 3请注意两者差异:// 是向下取整(朝负无穷取整),而 int() 是截断 (truncate),即朝 0 方向取整。对正数来说它们相同,但对负数则不同:
# truncation_vs_floor.py
# For positive numbers: same result
print(7 // 2) # Output: 3
print(int(7/2)) # Output: 3
# For negative numbers: different results
print(-7 // 2) # Output: -4 (rounds down toward negative infinity)
print(int(-7/2)) # Output: -3 (truncates toward zero)4.5.6) 避免意外得到浮点结果
有时你会惊讶地发现自己得到了浮点数,而你本以为会得到整数。这通常是因为使用了除法或混合了类型:
# unexpected_floats.py
# Calculating average - result is always float because of division
count = 10
total = 100
average = total / count
print(average) # Output: 10.0 (float, even though it's a whole number)
# If you need an integer and you know it divides evenly:
average_int = total // count
print(average_int) # Output: 10 (integer)
# Or convert explicitly:
average_int = int(total / count)
print(average_int) # Output: 10 (integer)关键结论是:Python 的类型提升规则旨在保留精度并避免数据丢失。当你混用整数和浮点数,或使用常规除法时,要预期得到浮点结果。如果你需要整数,可以使用地板除或显式转换,但要清楚它们的取整方式。
4.6) 常用数值内置函数:abs()、min()、max() 和 pow()
Python 提供了一些用于常见数值运算的内置函数。这些函数同时支持整数和浮点数,是日常编程中的重要工具。我们来看看最常用的几个。
4.6.1) 使用 abs() 求绝对值
abs() 函数返回一个数字的绝对值——也就是它到 0 的距离,不考虑正负号:
# absolute_value.py
# Absolute value of negative numbers
result = abs(-42)
print(result) # Output: 42
# Absolute value of positive numbers (unchanged)
result = abs(42)
print(result) # Output: 42
# Works with floats too
result = abs(-3.14)
print(result) # Output: 3.14
# Absolute value of zero is zero
result = abs(0)
print(result) # Output: 0当你只关心大小而不关心方向时,绝对值函数很有用:
# practical_abs.py
# Calculate temperature difference (magnitude only)
morning_temp = 45.5
evening_temp = 38.2
difference = abs(evening_temp - morning_temp)
print(f"Temperature changed by {difference} degrees")
# Output: Temperature changed by 7.3 degrees
# Calculate distance between two points (always positive)
position1 = 10
position2 = 25
distance = abs(position2 - position1)
print(f"Distance: {distance}") # Output: Distance: 15
# Works the same regardless of order
distance = abs(position1 - position2)
print(f"Distance: {distance}") # Output: Distance: 154.6.2) 使用 min() 和 max() 查找最小值和最大值
min() 函数返回若干参数中的最小值,max() 返回最大值:
# min_max_basic.py
# Find minimum of two numbers
smallest = min(10, 25)
print(smallest) # Output: 10
# Find maximum of two numbers
largest = max(10, 25)
print(largest) # Output: 25
# Works with more than two arguments
smallest = min(5, 12, 3, 18, 7)
print(smallest) # Output: 3
largest = max(5, 12, 3, 18, 7)
print(largest) # Output: 18
# Works with floats and mixed types
smallest = min(3.5, 2, 4.1, 1.9)
print(smallest) # Output: 1.9这些函数在数据中查找极值时非常有用:
# practical_min_max.py
# Find the best and worst test scores
test1 = 85
test2 = 92
test3 = 78
test4 = 95
highest_score = max(test1, test2, test3, test4)
lowest_score = min(test1, test2, test3, test4)
print(f"Highest score: {highest_score}") # Output: Highest score: 95
print(f"Lowest score: {lowest_score}") # Output: Lowest score: 78
# Clamp a value within a range
value = 150
minimum = 0
maximum = 100
# Ensure value is not below minimum
value = max(value, minimum)
# Ensure value is not above maximum
value = min(value, maximum)
print(f"Clamped value: {value}") # Output: Clamped value: 100使用 max() 和 min() 组合实现“夹紧 (clamp)”模式,在需要把数值限制在某个范围内时非常常见:
# clamping_pattern.py
def clamp(value, min_value, max_value):
"""Constrain value to be within [min_value, max_value]"""
return max(min_value, min(value, max_value))
# Test the clamp function
print(clamp(150, 0, 100)) # Output: 100 (too high, clamped to max)
print(clamp(-10, 0, 100)) # Output: 0 (too low, clamped to min)
print(clamp(50, 0, 100)) # Output: 50 (within range, unchanged)4.6.3) 使用 pow() 进行幂运算
pow() 函数用于将一个数提升到某个幂。Python 还提供了更常用的幂运算符 **,但 pow() 还提供了一些额外功能:
# exponentiation.py
# Using the ** operator (most common)
result = 2 ** 3 # 2 to the power of 3
print(result) # Output: 8
result = 5 ** 2 # 5 squared
print(result) # Output: 25
# Using pow() function (equivalent)
result = pow(2, 3)
print(result) # Output: 8
result = pow(5, 2)
print(result) # Output: 25
# Negative exponents give fractions
result = 2 ** -3 # 1 / (2^3) = 1/8
print(result) # Output: 0.125
# Fractional exponents give roots
result = 9 ** 0.5 # Square root of 9
print(result) # Output: 3.0
result = 8 ** (1/3) # Cube root of 8
print(result) # Output: 2.0pow() 函数还可以接受第三个可选参数用于模幂运算 (modular exponentiation)(在密码学和数论中很有用,但超出基础算术的范围):
# modular_exponentiation.py
# pow(base, exponent, modulus) computes (base ** exponent) % modulus efficiently
result = pow(2, 10, 100) # (2^10) % 100
print(result) # Output: 24
# This is more efficient than computing separately for large numbers:
# result = (2 ** 10) % 100 # Same result, but less efficient for large numbers在大多数日常场景中,** 运算符比 pow() 更方便:
# practical_exponentiation.py
# Calculate compound interest: A = P(1 + r)^t
principal = 1000 # Initial amount
rate = 0.05 # 5% interest rate
years = 10
amount = principal * (1 + rate) ** years
print(f"Amount after {years} years: ${amount:.2f}")
# Output: Amount after 10 years: $1628.89
# Calculate area of a square
side_length = 5
area = side_length ** 2
print(f"Area: {area}") # Output: Area: 25
# Calculate volume of a cube
side_length = 3
volume = side_length ** 3
print(f"Volume: {volume}") # Output: Volume: 274.6.4) 组合使用内置函数
这些函数可以组合使用,用来解决常见问题:
# combining_functions.py
# Find the range (difference between max and min)
values = [15, 42, 8, 23, 37]
value_range = max(values) - min(values)
print(f"Range: {value_range}") # Output: Range: 34
# Note: We're using a list here (we'll learn about lists in detail in Chapter 13)
# For now, just understand that max() and min() can work with a list of values
# Calculate distance between two points in 2D space
x1, y1 = 3, 4
x2, y2 = 6, 8
# Distance formula: sqrt((x2-x1)^2 + (y2-y1)^2)
# We'll use ** for squaring (square root comes in section 4.11)
distance_squared = (x2 - x1) ** 2 + (y2 - y1) ** 2
distance = distance_squared ** 0.5 # Square root via fractional exponent
print(f"Distance: {distance}") # Output: Distance: 5.0这些内置函数是 Python 编程中的基础工具。它们高效、可靠,对不同数值类型的行为一致。只要需要执行这些常见操作,都应优先使用它们,而不是自己重写。
4.7) 使用 round() 对数字进行四舍五入
在处理浮点数时,你经常需要把结果四舍五入到特定的小数位数,以便展示、进一步计算或存储。Python 提供了 round() 函数来实现这一功能。
4.7.1) 使用 round() 的基本四舍五入
round() 函数接收一个数字,并将它四舍五入到最接近的整数:
# basic_rounding.py
# Round to nearest integer
result = round(3.7)
print(result) # Output: 4
result = round(3.2)
print(result) # Output: 3
# Exactly halfway rounds to nearest even number (banker's rounding)
result = round(2.5)
print(result) # Output: 2 (rounds to even)
result = round(3.5)
print(result) # Output: 4 (rounds to even)
# Negative numbers work too
result = round(-3.7)
print(result) # Output: -4
result = round(-3.2)
print(result) # Output: -3注意,当被舍入的值刚好在两个整数的正中间(如 2.5 或 3.5)时,Python 采用“就近取偶 (round half to even)”的规则(也叫“银行家舍入 (banker's rounding)”),即舍入到最近的偶数。这种做法可以在大量重复舍入运算中减少系统性偏差。在大多数日常编程场景中,这个细节影响不大,但了解它是有好处的。
4.7.2) 舍入到指定位数的小数
round() 可以接受第二个可选参数,用来指定保留的小数位数:
# decimal_places.py
# Round to 2 decimal places
result = round(3.14159, 2)
print(result) # Output: 3.14
# Round to 1 decimal place
result = round(3.14159, 1)
print(result) # Output: 3.1
# Round to 3 decimal places
result = round(2.71828, 3)
print(result) # Output: 2.718
# You can round to 0 decimal places (same as omitting the argument)
result = round(3.7, 0)
print(result) # Output: 4.0 (note: returns float, not int)当你指定小数位数时,round() 返回浮点数(即使你舍入到 0 位小数);当你省略第二个参数时,它返回整数。
4.7.3) 舍入的实际用途
在显示金额、测量值以及其他不需要或不希望过高精度的数字时,舍入尤其重要:
# practical_rounding.py
# Display prices with 2 decimal places
price = 19.99
tax_rate = 0.08
total = price * (1 + tax_rate)
print(f"Total (unrounded): ${total}") # Output: Total (unrounded): $21.5892
print(f"Total (rounded): ${round(total, 2)}") # Output: Total (rounded): $21.59
# Calculate and display average
total_score = 456
num_tests = 7
average = total_score / num_tests
print(f"Average (unrounded): {average}") # Output: Average (unrounded): 65.14285714285714
print(f"Average (rounded): {round(average, 2)}") # Output: Average (rounded): 65.14
# Round measurements to reasonable precision
distance_meters = 123.456789
distance_rounded = round(distance_meters, 1)
print(f"Distance: {distance_rounded} meters") # Output: Distance: 123.5 meters4.7.4) 舍入 vs 截断 vs 向下/向上取整
要注意,舍入与截断(直接去掉小数部分)或地板/天花板操作是不同的:
# rounding_vs_others.py
value = 3.7
# Rounding: nearest integer
rounded = round(value)
print(f"Rounded: {rounded}") # Output: Rounded: 4
# Truncating: remove decimal part (convert to int)
truncated = int(value)
print(f"Truncated: {truncated}") # Output: Truncated: 3
# We'll see floor and ceil in section 4.11, but briefly:
# Floor: largest integer <= value (always rounds down)
# Ceiling: smallest integer >= value (always rounds up)对负数来说,这些差异更加明显:
# negative_rounding.py
value = -3.7
# Rounding: nearest integer
rounded = round(value)
print(f"Rounded: {rounded}") # Output: Rounded: -4
# Truncating: toward zero
truncated = int(value)
print(f"Truncated: {truncated}") # Output: Truncated: -3
# Floor (rounds down toward negative infinity): -4
# Ceiling (rounds up toward positive infinity): -34.7.5) 使用舍入时的重要注意事项
浮点精度可能带来意外结果:
由于计算机表示浮点数的方式(我们将在 4.10 小节讨论),舍入结果有时不会完全符合你的直觉:
# rounding_surprises.py
# Sometimes rounding doesn't give the exact decimal you expect
value = 2.675
rounded = round(value, 2)
print(rounded) # Output: 2.67 (not 2.68 as you might expect)
# This happens because 2.675 can't be represented exactly in binary floating-point
# The actual stored value is slightly less than 2.675在大多数实际使用中,这不是问题。但如果你在做对精确小数要求很高的金融计算,可能需要使用 Python 的 decimal 模块(本书不详细介绍,但值得知道它的存在)。
用于显示的舍入 vs 用于计算的舍入:
很多时候你只想在“显示时”进行舍入,而在内部计算中继续保留全部精度:
# rounding_for_display.py
price1 = 19.99
price2 = 15.49
tax_rate = 0.08
# Calculate with full precision
subtotal = price1 + price2
tax = subtotal * tax_rate
total = subtotal + tax
# Round only for display
print(f"Subtotal: ${round(subtotal, 2)}") # Output: Subtotal: $35.48
print(f"Tax: ${round(tax, 2)}") # Output: Tax: $2.84
print(f"Total: ${round(total, 2)}") # Output: Total: $38.32
# The variables still contain full precision
print(f"Total (full precision): ${total}")
# Output: Total (full precision): $38.3184round() 是 Python 中使用最频繁的内置函数之一。只要你需要以可读的格式呈现数值结果,或在特定场景下限制精度,都可以使用它。
4.8) 常见数值模式(计数器、总和、平均值)
现在你已经了解了 Python 的数值运算和函数,我们来看看真实程序中会反复使用的一些常见模式。这些模式是处理数值数据的构建块。
4.8.1) 计数器:记录“发生了多少次”
计数器 (counter) 是一个用来记录某件事发生次数的变量。你将它初始化为 0,每次事件发生时把它加 1:
# basic_counter.py
# Count how many numbers we've processed
count = 0 # Initialize counter
# Process first number
count += 1
print(f"Processed {count} number(s)") # Output: Processed 1 number(s)
# Process second number
count += 1
print(f"Processed {count} number(s)") # Output: Processed 2 number(s)
# Process third number
count += 1
print(f"Processed {count} number(s)") # Output: Processed 3 number(s)在真实程序中,你通常会在循环 (loop) 中使用计数器(我们会在第 10、11 章学习循环)。目前只要理解模式本身:从 0 开始,每次加 1。
计数器可以用来跟踪不同类型的事件:
# multiple_counters.py
# Track different categories
even_count = 0
odd_count = 0
# Check number 1
number = 4
if number % 2 == 0:
even_count += 1
else:
odd_count += 1
# Check number 2
number = 7
if number % 2 == 0:
even_count += 1
else:
odd_count += 1
# Check number 3
number = 10
if number % 2 == 0:
even_count += 1
else:
odd_count += 1
print(f"Even numbers: {even_count}") # Output: Even numbers: 2
print(f"Odd numbers: {odd_count}") # Output: Odd numbers: 14.8.2) 累加器:累积总和
累加器 (accumulator) 是一个用于维护“运行总和”的变量。和计数器一样,它也是从 0 初始化,但每次增加的值可以不同:
# basic_accumulator.py
# Calculate total sales
total_sales = 0 # Initialize accumulator
# First sale
sale = 19.99
total_sales += sale
print(f"Total so far: ${total_sales}") # Output: Total so far: $19.99
# Second sale
sale = 34.50
total_sales += sale
print(f"Total so far: ${total_sales}") # Output: Total so far: $54.49
# Third sale
sale = 12.00
total_sales += sale
print(f"Total so far: ${total_sales}") # Output: Total so far: $66.49累加器是数据处理的基础。它让你可以在处理数据的同时“边走边统计”:
# multiple_accumulators.py
# Track both total and count to calculate average later
total_score = 0
count = 0
# Process score 1
score = 85
total_score += score
count += 1
# Process score 2
score = 92
total_score += score
count += 1
# Process score 3
score = 78
total_score += score
count += 1
print(f"Total score: {total_score}") # Output: Total score: 255
print(f"Number of scores: {count}") # Output: Number of scores: 34.8.3) 计算平均值
平均值模式结合了计数器和累加器:你需要一个总和(累加器)和一个数量(计数器),然后进行除法:
# calculating_average.py
# Calculate average of test scores
total_score = 0
count = 0
# Add scores
total_score += 85
count += 1
total_score += 92
count += 1
total_score += 78
count += 1
total_score += 88
count += 1
# Calculate average
average = total_score / count
print(f"Average score: {average}") # Output: Average score: 85.75
# Often you'll want to round the average
average_rounded = round(average, 2)
print(f"Average (rounded): {average_rounded}") # Output: Average (rounded): 85.75重要:计算平均值前一定要检查是否会除以 0:
# safe_average.py
total_score = 0
count = 0
# If no scores were added, count is still 0
# Dividing by zero causes an error!
if count > 0:
average = total_score / count
print(f"Average: {average}")
else:
print("No scores to average")
# Output: No scores to average我们将在第 8 章进一步学习如何处理类似这种条件逻辑。
4.8.4) 记录当前最大值和最小值
有时你需要跟踪“截至目前为止”看到的最大值或最小值:
你可以使用 max() 和 min() 函数来实现这一点:
# running_max_min_simplified.py
# Track highest and lowest using max() and min()
highest_temp = 72
lowest_temp = 72
# Update with new temperature
current_temp = 85
highest_temp = max(highest_temp, current_temp)
lowest_temp = min(lowest_temp, current_temp)
# Update with another temperature
current_temp = 68
highest_temp = max(highest_temp, current_temp)
lowest_temp = min(lowest_temp, current_temp)
print(f"High: {highest_temp}, Low: {lowest_temp}")
# Output: High: 85, Low: 684.9) 整数的按位运算符:&、|、^、<<、>>(简要概览)
按位运算符 (bitwise operator) 作用于整数的各个二进制位 (bit)。虽然在日常编程中你不会像使用算术运算符那样频繁地使用它们,但在一些场景中(如标志位、权限、底层数据操作、性能优化)它们非常重要。
本节只是简单概览。要完整理解按位运算,需要先理解二进制表示,这是一个比本章更深入的话题。
4.9.1) 理解二进制表示(快速介绍)
计算机将整数存储为一串比特(0 和 1)。例如:
- 十进制 5 的二进制是
101(1×4 + 0×2 + 1×1) - 十进制 12 的二进制是
1100(1×8 + 1×4 + 0×2 + 0×1)
Python 的 bin() 函数可以显示整数的二进制表示:
# binary_representation.py
# See binary representation of integers
print(bin(5)) # Output: 0b101
print(bin(12)) # Output: 0b1100
print(bin(255)) # Output: 0b11111111
# The 0b prefix indicates binary notation4.9.2) 按位与 (&)
& 运算符执行按位与 (bitwise AND):只有当两个操作数对应位置上的比特都为 1 时,结果中的该比特才为 1:
# bitwise_and.py
# 12 is 1100 in binary
# 10 is 1010 in binary
result = 12 & 10
print(result) # Output: 8
print(bin(result)) # Output: 0b1000
# How it works:
# 1100 (12)
# & 1010 (10)
# ------
# 1000 (8)按位与经常用于检查某个标志位是否被设置:
# checking_flags.py
# File permissions example (simplified)
READ = 4 # 100 in binary
WRITE = 2 # 010 in binary
EXECUTE = 1 # 001 in binary
permissions = 6 # 110 in binary (READ + WRITE)
# Check if READ permission is set
has_read = (permissions & READ) != 0
print(f"Has read: {has_read}") # Output: Has read: True
# Check if EXECUTE permission is set
has_execute = (permissions & EXECUTE) != 0
print(f"Has execute: {has_execute}") # Output: Has execute: False4.9.3) 按位或 (|)
| 运算符执行按位或 (bitwise OR):只要两个操作数中任一在对应位置上的比特为 1,结果中的该比特就为 1:
# bitwise_or.py
# 12 is 1100 in binary
# 10 is 1010 in binary
result = 12 | 10
print(result) # Output: 14
print(bin(result)) # Output: 0b1110
# How it works:
# 1100 (12)
# | 1010 (10)
# ------
# 1110 (14)按位或常用于组合标志:
# combining_flags.py
READ = 4 # 100 in binary
WRITE = 2 # 010 in binary
EXECUTE = 1 # 001 in binary
# Grant READ and WRITE permissions
permissions = READ | WRITE
print(f"Permissions: {permissions}") # Output: Permissions: 6
print(bin(permissions)) # Output: 0b110
# Add EXECUTE permission
permissions = permissions | EXECUTE
print(f"Permissions: {permissions}") # Output: Permissions: 7
print(bin(permissions)) # Output: 0b1114.9.4) 按位异或 (^)
^ 运算符执行按位异或 (bitwise XOR, exclusive OR):只有当两个操作数在对应位置上的比特不同,结果中的该比特才为 1:
# bitwise_xor.py
# 12 is 1100 in binary
# 10 is 1010 in binary
result = 12 ^ 10
print(result) # Output: 6
print(bin(result)) # Output: 0b110
# How it works:
# 1100 (12)
# ^ 1010 (10)
# ------
# 0110 (6)异或有一些有趣的性质,例如切换 (toggle) 指定位或交换数值:
# xor_properties.py
# XOR with itself gives 0
result = 5 ^ 5
print(result) # Output: 0
# XOR with 0 gives the original number
result = 5 ^ 0
print(result) # Output: 5
# XOR is its own inverse (useful for simple encryption)
original = 42
key = 123
encrypted = original ^ key
decrypted = encrypted ^ key
print(f"Original: {original}, Encrypted: {encrypted}, Decrypted: {decrypted}")
# Output: Original: 42, Encrypted: 81, Decrypted: 424.9.5) 左移 (<<) 和右移 (>>)
<< 运算符将比特向左移动,>> 将比特向右移动:
# bit_shifting.py
# Left shift: multiply by powers of 2
result = 5 << 1 # Shift left by 1 bit
print(result) # Output: 10
# 5 is 101 in binary, shifted left becomes 1010 (10)
result = 5 << 2 # Shift left by 2 bits
print(result) # Output: 20
# 5 is 101 in binary, shifted left becomes 10100 (20)
# Right shift: divide by powers of 2 (floor division)
result = 20 >> 1 # Shift right by 1 bit
print(result) # Output: 10
# 20 is 10100 in binary, shifted right becomes 1010 (10)
result = 20 >> 2 # Shift right by 2 bits
print(result) # Output: 5
# 20 is 10100 in binary, shifted right becomes 101 (5)向左移动 n 位,相当于乘以 2 的 n 次方;向右移动 n 位,相当于用 2 的 n 次方进行地板除:
# shift_as_multiplication.py
# Left shift multiplies by powers of 2
print(3 << 1) # Output: 6 (3 * 2^1 = 3 * 2)
print(3 << 2) # Output: 12 (3 * 2^2 = 3 * 4)
print(3 << 3) # Output: 24 (3 * 2^3 = 3 * 8)
# Right shift divides by powers of 2
print(24 >> 1) # Output: 12 (24 // 2^1 = 24 // 2)
print(24 >> 2) # Output: 6 (24 // 2^2 = 24 // 4)
print(24 >> 3) # Output: 3 (24 // 2^3 = 24 // 8)4.9.6) 何时使用按位运算符
按位运算符在一些特定场景很有用:
- 处理二进制标志和权限(如 Unix/Linux 文件权限)
- 底层数据操作(将多个值打包到一个整数中)
- 网络编程(处理 IP 地址、协议标志)
- 图形和游戏编程(颜色处理、碰撞检测)
- 性能优化(按位移位通常比乘除以 2 的幂更快)
对于大多数日常编程任务,你不会频繁用到按位运算符。但在进行系统编程、网络编程或性能敏感代码时,它们会非常有用。
注意:本节只是基础概览。对于负数时的按位运算(涉及补码表示),情况会复杂得多,超出了本章范围。现在请先理解它们的存在以及大致作用即可。
4.10) 浮点精度与舍入误差(简明解释)
Python(以及大多数编程语言)中的浮点数可以表示非常广泛的数值范围,从极小的小数到极大的数。然而它们有一个常常让初学者困惑的限制:并不能精确表示所有十进制小数。本节将解释这是为什么,以及这对你的程序意味着什么。
4.10.1) 为什么浮点数并不总是精确的
计算机以二进制(base 2)而不是十进制(base 10)存储浮点数。有些对我们来说看起来很简单的十进制小数,在二进制中却无法精确表示,就像 1/3 无法用有限位数的十进制来精确表示(0.333333... 无限循环)。
来看一个令人惊讶的例子:
# floating_point_surprise.py
# This seems like it should be exactly 0.3
result = 0.1 + 0.2
print(result) # Output: 0.30000000000000004 (not exactly 0.3!)
# Check if it equals 0.3
print(result == 0.3) # Output: False这不是 Python 的 bug——而是计算机表示浮点数方式的根本限制。十进制的 0.1 在二进制浮点格式中无法精确表示,就像 1/3 无法在十进制中精确表示一样。
4.10.2) 理解表示问题
再看一些类似的例子:
# more_precision_examples.py
# Simple decimal values that aren't exact in binary
print(0.1) # Output: 0.1 (Python rounds for display)
print(repr(0.1)) # Output: 0.1 (still rounded)
# But the actual stored value has tiny errors
print(0.1 + 0.1 + 0.1) # Output: 0.30000000000000004
# Multiplication can accumulate these errors
result = 0.1 * 3
print(result) # Output: 0.30000000000000004
# Some numbers are exact (powers of 2)
print(0.5) # Output: 0.5 (exact)
print(0.25) # Output: 0.25 (exact)
print(0.125) # Output: 0.125 (exact)像 0.5、0.25、0.125 这样的 2 的幂或若干个 2 的幂之和可以被精确表示,但大多数十进制小数不能。
4.10.3) 实际影响
对大多数日常编程来说,这些微小误差无关紧要。但在某些场景下,你需要意识到它们的存在:
比较浮点数:
# comparing_floats.py
# Direct equality comparison can fail
a = 0.1 + 0.2
b = 0.3
print(a == b) # Output: False (due to tiny difference)
# Better: check if they're close enough
difference = abs(a - b)
tolerance = 0.0001 # How close is "close enough"?
print(difference < tolerance) # Output: True (they're close enough)
# Python 3.5+ provides math.isclose() for this (we'll see in section 4.11)在重复计算中累积误差:
# accumulated_errors.py
# Adding 0.1 ten times
total = 0.0
for i in range(10):
total += 0.1
print(total) # Output: 0.9999999999999999 (not exactly 1.0)
print(total == 1.0) # Output: False
# The error accumulates with each addition金融计算:
# financial_calculations.py
# Money calculations can have surprising results
price = 0.10
quantity = 3
total = price * quantity
print(total) # Output: 0.30000000000000004
# For financial calculations, consider rounding to cents
total_cents = round(total * 100) # Convert to cents
total_dollars = total_cents / 100
print(total_dollars) # Output: 0.3
# Or use Python's decimal module for exact decimal arithmetic
# (beyond the scope of this chapter, but worth knowing about)4.10.4) 与浮点数打交道的策略
在显示时进行舍入:
# rounding_for_display.py
result = 0.1 + 0.2
print(f"Result: {round(result, 2)}") # Output: Result: 0.3
# Or use formatted output (we'll learn more in Chapter 6)
print(f"Result: {result:.2f}") # Output: Result: 0.30不要直接用“相等”比较浮点数:
# safe_float_comparison.py
a = 0.1 + 0.2
b = 0.3
# Instead of: if a == b:
# Use: if they're close enough
if abs(a - b) < 0.0001:
print("Close enough to equal")
# Output: Close enough to equal在精度很重要时要特别小心:
对于科学计算、金融计算或其他需要精确十进制运算的领域,你可能需要:
- 使用 Python 的
decimal模块进行精确十进制运算 - 使用
fractions模块进行精确有理数运算 - 在合适的计算节点进行小心的舍入
这些模块超出了本章范围,但知道它们的存在很有价值。
4.10.5) 什么时候浮点精度足够用
对于大多数日常编程任务,浮点精度已经绰绰有余:
# when_precision_is_fine.py
# Calculating area
length = 5.5
width = 3.2
area = length * width
print(f"Area: {area:.2f} square meters") # Output: Area: 17.60 square meters
# Converting temperature
fahrenheit = 98.6
celsius = (fahrenheit - 32) * 5 / 9
print(f"Temperature: {celsius:.1f}°C") # Output: Temperature: 37.0°C
# Calculating average
total = 456.78
count = 7
average = total / count
print(f"Average: {average:.2f}") # Output: Average: 65.25当你在显示时进行舍入,或者这些微小误差远小于实际测量误差时,浮点运算是完全够用的。
4.10.6) 关键要点
浮点数是对实数的近似。对大多数编程任务来说,它们足够精确。但请记住:
- 不要用精确相等来比较浮点数——应该检查它们是否“足够接近”
- 在显示时进行舍入,避免展示毫无意义的高精度
- 注意重复计算中误差的累积
- 在金融计算中,考虑舍入到合适精度或使用
decimal模块
理解这些限制可以帮助你写出更健壮的程序,避免意外的 bug。好消息是:对于绝大多数编程任务,只要遵守这些简单规则,浮点运算“基本就是好用的”。
4.11)(可选)使用 math 模块的基础数学函数(sqrt、floor、ceil、pi)
Python 的内置运算符和函数覆盖了基础算术,但对于更高级的数学运算,Python 提供了 math 模块。模块 (module) 是一组相关函数和数值的集合,你可以在程序中使用。我们会在第 22 章更系统地学习模块,但现在先介绍使用 math 模块所需的基础知识。
4.11.1) 导入 math 模块
要使用 math 模块,你需要在程序开头导入它:
# importing_math.py
import math
# Now you can use functions from the math module
# by writing math.function_name()导入模块后,你就能访问其中所有函数和常量。使用时写“模块名.函数名”。
4.11.2) 数学常数:pi 和 e
math 模块提供了一些重要数学常数的精确值:
# math_constants.py
import math
# Pi (π): ratio of circle's circumference to diameter
print(math.pi) # Output: 3.141592653589793
# Euler's number (e): base of natural logarithms
print(math.e) # Output: 2.718281828459045
# Using pi to calculate circle properties
radius = 5
circumference = 2 * math.pi * radius
area = math.pi * radius ** 2
print(f"Circumference: {circumference:.2f}") # Output: Circumference: 31.42
print(f"Area: {area:.2f}") # Output: Area: 78.544.11.3) 使用 sqrt() 计算平方根
sqrt() 函数用于计算一个数的平方根:
# square_root.py
import math
# Square root of perfect squares
result = math.sqrt(16)
print(result) # Output: 4.0
result = math.sqrt(25)
print(result) # Output: 5.0
# Square root of non-perfect squares
result = math.sqrt(2)
print(result) # Output: 1.4142135623730951
# Using sqrt in calculations
# Distance formula: sqrt((x2-x1)^2 + (y2-y1)^2)
x1, y1 = 3, 4
x2, y2 = 6, 8
distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
print(f"Distance: {distance}") # Output: Distance: 5.0注意,sqrt() 返回的总是浮点数,即使输入的是完全平方数。此外,你不能对负数取平方根(否则会报错)。
4.11.4) 使用 floor() 和 ceil() 进行向下/向上取整
floor() 函数向下取整到最近的整数(朝负无穷方向);ceil() 函数向上取整到最近的整数(朝正无穷方向):
# floor_and_ceil.py
import math
# Floor: rounds down
result = math.floor(3.7)
print(result) # Output: 3
result = math.floor(3.2)
print(result) # Output: 3
# Ceiling: rounds up
result = math.ceil(3.7)
print(result) # Output: 4
result = math.ceil(3.2)
print(result) # Output: 4
# With negative numbers
result = math.floor(-3.7)
print(result) # Output: -4 (rounds toward negative infinity)
result = math.ceil(-3.7)
print(result) # Output: -3 (rounds toward positive infinity)这些函数在需要确保数值在某个范围内,或者需要带特定取整方向的整数结果时很有用:
# practical_floor_ceil.py
import math
# How many boxes needed to pack all items?
items = 47
items_per_box = 12
# Use ceil to round up (need a full box even if not completely filled)
boxes_needed = math.ceil(items / items_per_box)
print(f"Boxes needed: {boxes_needed}") # Output: Boxes needed: 4
# How many complete pages for a document?
total_lines = 250
lines_per_page = 30
# Use floor to count only complete pages
complete_pages = math.floor(total_lines / lines_per_page)
print(f"Complete pages: {complete_pages}") # Output: Complete pages: 84.11.5) 比较 floor()、ceil()、round() 和 int()
理解这些不同取整方式的差异很重要:
# comparing_rounding_methods.py
import math
value = 3.7
print(f"Original: {value}")
print(f"floor(): {math.floor(value)}") # Output: floor(): 3
print(f"ceil(): {math.ceil(value)}") # Output: ceil(): 4
print(f"round(): {round(value)}") # Output: round(): 4
print(f"int(): {int(value)}") # Output: int(): 3
# With negative numbers, the differences are more apparent
value = -3.7
print(f"\nOriginal: {value}")
print(f"floor(): {math.floor(value)}") # Output: floor(): -4
print(f"ceil(): {math.ceil(value)}") # Output: ceil(): -3
print(f"round(): {round(value)}") # Output: round(): -4
print(f"int(): {int(value)}") # Output: int(): -3下面是这些函数行为的可视化示意:
4.11.6) 其他有用的 math 模块函数
math 模块中还有许多其他函数。这里再列出一些常用的:
# other_math_functions.py
import math
# Absolute value (also available as built-in abs())
result = math.fabs(-5.5)
print(result) # Output: 5.5
# Power function (also available as ** operator)
result = math.pow(2, 3)
print(result) # Output: 8.0
# Trigonometric functions (angles in radians)
result = math.sin(math.pi / 2) # sin(90 degrees)
print(result) # Output: 1.0
result = math.cos(0) # cos(0 degrees)
print(result) # Output: 1.0
# Logarithms
result = math.log(math.e) # Natural log (base e)
print(result) # Output: 1.0
result = math.log10(100) # Log base 10
print(result) # Output: 2.0
# Check if a float is close to another (Python 3.5+)
a = 0.1 + 0.2
b = 0.3
result = math.isclose(a, b)
print(result) # Output: Trueisclose() 函数在比较浮点数时尤其有用(正如我们在 4.10 小节讨论的):
# isclose_example.py
import math
# Instead of direct equality comparison
a = 0.1 + 0.2
b = 0.3
# Don't do this:
# if a == b: # This would be False
# Do this instead:
if math.isclose(a, b):
print("Values are close enough")
# Output: Values are close enough
# You can specify the tolerance
if math.isclose(a, b, rel_tol=1e-9): # Very strict tolerance
print("Values are very close")
# Output: Values are very close4.11.7) 何时使用 math 模块
在以下场景中使用 math 模块:
- 需要数学常数(π、e)
- 需要平方根或其他根号运算
- 需要三角函数(sin、cos、tan)
- 需要对数和指数函数
- 需要更精细的取整控制(floor、ceil)
- 需要比较浮点数是否“足够接近”(isclose)
对于基础算术(+、-、*、/、//、%、**),使用 Python 内置运算符即可。对于更高级的数学运算,则导入并使用 math 模块。
math 模块属于 Python 标准库的一部分,这意味着它总是可用的——你只需要导入它。我们会在第 22 章中学习更多模块以及导入系统的细节。
在本章中,你学习了如何在 Python 中使用数字:执行算术运算、理解不同类型的除法、使用运算符优先级、在表达式中混用整数和浮点数、使用数值内置函数、应用常见数值模式、使用按位运算符、理解浮点精度限制,以及通过 math 模块进行进阶数学运算。
这些数值操作构成了无数编程任务的基础,从简单计算到复杂数据分析皆是如此。随着你继续学习 Python,你会不断使用这些操作,它们也会与后续章节介绍的控制流结构和数据集合结合在一起使用。
多练习这些操作,直到它们变成你的第二天性。可以尝试编写一些小程序来计算真实世界中的量:温度转换、面积和体积计算、处理财务数据,或分析测量结果。你练习得越多,就会越熟悉 Python 的数值能力。