Rounding Functions¶
NumPy provides several functions for rounding floating-point numbers.
Mental Model
np.round rounds to the nearest value (with banker's rounding for .5 cases), np.floor always rounds down, and np.ceil always rounds up. Choose based on the direction you need: floor for safe lower bounds, ceil for safe upper bounds, and round for minimum overall error.
At its core, rounding is a discretization operator — it maps continuous values onto a discrete grid. This framing explains why rounding matters beyond formatting: binning sensor data, quantizing weights in neural networks, and snapping coordinates to pixel grids are all discretization.
Rounding in the Ufunc System
np.round, np.floor, np.ceil, and np.trunc are all ufuncs — they follow the same broadcast-then-apply-elementwise pattern as + and np.exp. They are a specialized subset: unary ufuncs that map floats to floats while enforcing integer-like semantics. Understanding them as ufuncs means you get out=, where=, and in-place operations for free.
np.round¶
1. Basic Usage¶
```python import numpy as np
def main(): x = np.array([1.4, 1.5, 1.6, 2.5, 3.5])
print(f"x = {x}")
print(f"np.round(x) = {np.round(x)}")
if name == "main": main() ```
Output:
x = [1.4 1.5 1.6 2.5 3.5]
np.round(x) = [1. 2. 2. 2. 4.]
2. Decimal Places¶
```python import numpy as np
def main(): x = np.array([3.14159, 2.71828, 1.41421])
print(f"Original: {x}")
print(f"Round to 2: {np.round(x, 2)}")
print(f"Round to 3: {np.round(x, 3)}")
if name == "main": main() ```
3. Negative Decimals¶
```python import numpy as np
def main(): x = np.array([1234, 5678, 9012])
print(f"Original: {x}")
print(f"Round to -1: {np.round(x, -1)}") # Tens
print(f"Round to -2: {np.round(x, -2)}") # Hundreds
print(f"Round to -3: {np.round(x, -3)}") # Thousands
if name == "main": main() ```
Banker's Rounding¶
1. Round Half to Even¶
```python import numpy as np
def main(): # NumPy uses "round half to even" (banker's rounding) x = np.array([0.5, 1.5, 2.5, 3.5, 4.5])
print(f"x = {x}")
print(f"np.round(x) = {np.round(x)}")
# Note: 0.5 -> 0, 1.5 -> 2, 2.5 -> 2, 3.5 -> 4
if name == "main": main() ```
2. Why Banker's Rounding¶
Reduces systematic bias in statistical calculations.
3. Traditional Rounding¶
```python import numpy as np
def traditional_round(x): """Round half away from zero""" return np.sign(x) * np.floor(np.abs(x) + 0.5)
def main(): x = np.array([0.5, 1.5, 2.5, 3.5, -0.5, -1.5])
print(f"x = {x}")
print(f"np.round (banker's): {np.round(x)}")
print(f"Traditional round: {traditional_round(x)}")
if name == "main": main() ```
np.floor¶
1. Basic Usage¶
```python import numpy as np
def main(): x = np.array([1.7, 2.3, -1.7, -2.3])
print(f"x = {x}")
print(f"np.floor(x) = {np.floor(x)}")
if name == "main": main() ```
Output:
x = [ 1.7 2.3 -1.7 -2.3]
np.floor(x) = [ 1. 2. -2. -3.]
2. Floor Rounds Toward -∞¶
```python import numpy as np
def main(): # Floor always rounds toward negative infinity print(f"floor(2.9) = {np.floor(2.9)}") # 2 print(f"floor(-2.1) = {np.floor(-2.1)}") # -3
if name == "main": main() ```
np.ceil¶
1. Basic Usage¶
```python import numpy as np
def main(): x = np.array([1.1, 2.9, -1.1, -2.9])
print(f"x = {x}")
print(f"np.ceil(x) = {np.ceil(x)}")
if name == "main": main() ```
Output:
x = [ 1.1 2.9 -1.1 -2.9]
np.ceil(x) = [ 2. 3. -1. -2.]
2. Ceil Rounds Toward +∞¶
```python import numpy as np
def main(): # Ceil always rounds toward positive infinity print(f"ceil(2.1) = {np.ceil(2.1)}") # 3 print(f"ceil(-2.9) = {np.ceil(-2.9)}") # -2
if name == "main": main() ```
np.trunc¶
1. Basic Usage¶
```python import numpy as np
def main(): x = np.array([1.7, 2.3, -1.7, -2.3])
print(f"x = {x}")
print(f"np.trunc(x) = {np.trunc(x)}")
if name == "main": main() ```
Output:
x = [ 1.7 2.3 -1.7 -2.3]
np.trunc(x) = [ 1. 2. -1. -2.]
2. Truncate Toward Zero¶
```python import numpy as np
def main(): # Trunc removes decimal part (rounds toward zero) print(f"trunc(2.9) = {np.trunc(2.9)}") # 2 print(f"trunc(-2.9) = {np.trunc(-2.9)}") # -2
if name == "main": main() ```
np.fix¶
1. Equivalent to trunc¶
```python import numpy as np
def main(): x = np.array([1.7, 2.3, -1.7, -2.3])
print(f"x = {x}")
print(f"np.fix(x) = {np.fix(x)}")
print(f"np.trunc(x) = {np.trunc(x)}")
print(f"Equal: {np.array_equal(np.fix(x), np.trunc(x))}")
if name == "main": main() ```
2. Historical Note¶
np.fix and np.trunc produce identical results; both exist for historical reasons.
Comparison¶
1. All Functions¶
```python import numpy as np
def main(): x = np.array([1.5, 2.5, -1.5, -2.5])
print(f"{'x':>8} {'round':>8} {'floor':>8} {'ceil':>8} {'trunc':>8}")
print("-" * 44)
for val in x:
r = np.round(val)
f = np.floor(val)
c = np.ceil(val)
t = np.trunc(val)
print(f"{val:8.1f} {r:8.1f} {f:8.1f} {c:8.1f} {t:8.1f}")
if name == "main": main() ```
2. Visualization¶
```python import numpy as np import matplotlib.pyplot as plt
def main(): x = np.linspace(-2.5, 2.5, 201)
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
funcs = [
('np.round', np.round),
('np.floor', np.floor),
('np.ceil', np.ceil),
('np.trunc', np.trunc)
]
for ax, (name, func) in zip(axes.flat, funcs):
ax.plot(x, func(x), 'b.', markersize=2)
ax.plot(x, x, 'r--', alpha=0.3, label='y=x')
ax.axhline(0, color='gray', alpha=0.3)
ax.axvline(0, color='gray', alpha=0.3)
ax.set_title(name)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
if name == "main": main() ```
2D Arrays¶
1. Array Rounding¶
```python import numpy as np
def main(): x = np.array([[3.14159, 2.71828], [1.41421, 1.73205]])
print("Original:")
print(x)
print()
print("np.round(x, 2):")
print(np.round(x, 2))
if name == "main": main() ```
2. With NaN Values¶
```python import numpy as np
def main(): x = np.array([[1.5, np.nan], [2.5, 3.5]])
print("Original:")
print(x)
print()
print("np.round(x):")
print(np.round(x)) # NaN preserved
if name == "main": main() ```
Applications¶
1. Discretization¶
```python import numpy as np
def main(): # Continuous values to discrete bins values = np.array([0.3, 1.7, 2.2, 3.8, 4.1])
discrete = np.floor(values).astype(int)
print(f"Continuous: {values}")
print(f"Discrete: {discrete}")
if name == "main": main() ```
2. Price Rounding¶
```python import numpy as np
def main(): prices = np.array([19.994, 29.995, 39.996])
# Round to cents
rounded = np.round(prices, 2)
print(f"Raw prices: {prices}")
print(f"Rounded prices: {rounded}")
if name == "main": main() ```
3. Grid Snapping¶
```python import numpy as np
def snap_to_grid(x, grid_size): """Snap values to nearest grid point""" return np.round(x / grid_size) * grid_size
def main(): points = np.array([0.3, 1.7, 2.2, 3.8]) grid_size = 0.5
snapped = snap_to_grid(points, grid_size)
print(f"Original: {points}")
print(f"Snapped: {snapped}")
if name == "main": main() ```
Summary Table¶
1. Rounding Directions¶
| Function | Direction | Example (2.7) | Example (-2.7) |
|---|---|---|---|
np.round |
Nearest (even) | 3.0 | -3.0 |
np.floor |
Toward -∞ | 2.0 | -3.0 |
np.ceil |
Toward +∞ | 3.0 | -2.0 |
np.trunc |
Toward 0 | 2.0 | -2.0 |
2. Key Differences¶
floorvstrunc: Differ for negative numbersround: Uses banker's rounding (half to even)fix=trunc: Identical functions
Exercises¶
Exercise 1. Apply np.floor, np.ceil, np.round, and np.trunc to the array [-2.7, -0.5, 0.5, 2.3, 3.8]. Print all results.
Solution to Exercise 1
```python import numpy as np
arr = np.array([-2.7, -0.5, 0.5, 2.3, 3.8]) print("floor:", np.floor(arr)) # [-3. -1. 0. 2. 3.] print("ceil:", np.ceil(arr)) # [-2. 0. 1. 3. 4.] print("round:", np.round(arr)) # [-3. 0. 0. 2. 4.] print("trunc:", np.trunc(arr)) # [-2. 0. 0. 2. 3.] ```
Exercise 2. Predict the output of np.round(2.5) and np.round(3.5). Explain the banker's rounding behavior.
Solution to Exercise 2
python
import numpy as np
print(np.round(2.5)) # 2.0
print(np.round(3.5)) # 4.0
NumPy uses banker's rounding (round half to even). When the value is exactly halfway, it rounds to the nearest even number. So 2.5 rounds to 2 and 3.5 rounds to 4.
Exercise 3. Write a function that rounds all elements of an array to the nearest multiple of 5 (e.g., 12 becomes 10, 13 becomes 15).
Solution to Exercise 3
```python import numpy as np
def round_to_nearest(arr, multiple): return np.round(arr / multiple) * multiple
arr = np.array([12, 13, 17, 22, 28]) print(round_to_nearest(arr, 5)) # [10. 15. 15. 20. 30.] ```
Exercise 4. Given prices [19.99, 24.50, 9.999], round to 2 decimal places, then compute the total using np.sum.
Solution to Exercise 4
```python import numpy as np
prices = np.array([19.99, 24.50, 9.999]) rounded = np.round(prices, 2) print(f"Rounded: {rounded}") print(f"Total: {np.sum(rounded):.2f}") ```