Skip to content

Commit c486f91

Browse files
committed
Add docstrings to __floor__/__ceil__/__round__; add test_rounding.py
Each docstring follows the existing pattern (operation summary, example, design rationale note) and references the Rules for Math appendix. 19 new tests covering floor/ceil/round across fractional, whole, negative, and cross-unit-type cases.
1 parent 4294af6 commit c486f91

2 files changed

Lines changed: 155 additions & 0 deletions

File tree

bitmath/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,13 +851,35 @@ def __float__(self):
851851
"""Return this instances prefix unit as a floating point number"""
852852
return float(self.prefix_value)
853853

854+
"""floor/ceil/round operate on the prefix value and return the same unit
855+
type. They are explicit opt-in operations for when integer prefix values are
856+
needed. See the Rules for Math appendix in the bitmath documentation for the
857+
design rationale behind floating-point representation.
858+
"""
859+
854860
def __floor__(self):
861+
"""Return the largest integer prefix value <= this instance as the same type.
862+
863+
Rounds the prefix value down. math.floor(MiB(1.9)) -> MiB(1).
864+
"""
855865
return (type(self))(math.floor(self.prefix_value))
856866

857867
def __ceil__(self):
868+
"""Return the smallest integer prefix value >= this instance as the same type.
869+
870+
Rounds the prefix value up. math.ceil(MiB(1.1)) -> MiB(2).
871+
"""
858872
return (type(self))(math.ceil(self.prefix_value))
859873

860874
def __round__(self, ndigits=None):
875+
"""Return this instance rounded to ndigits precision as the same type.
876+
877+
round(MiB(1.75)) -> MiB(2); round(KiB(1.555), 2) -> KiB(1.56).
878+
879+
Rounds the prefix value using Python's built-in round(). When ndigits
880+
is omitted the result has an integer prefix value. Only round at the
881+
final output step; rounding intermediate results loses precision.
882+
"""
861883
if ndigits is None:
862884
return (type(self))(round(self.prefix_value))
863885
return (type(self))(round(self.prefix_value, ndigits))

tests/test_rounding.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# -*- coding: utf-8 -*-
2+
# SPDX-License-Identifier: MIT
3+
# The MIT License (MIT)
4+
#
5+
# Copyright © 2014 Tim Case <timbielawa@gmail.com>
6+
#
7+
# Permission is hereby granted, free of charge, to any person
8+
# obtaining a copy of this software and associated documentation files
9+
# (the "Software"), to deal in the Software without restriction,
10+
# including without limitation the rights to use, copy, modify, merge,
11+
# publish, distribute, sublicense, and/or sell copies of the Software,
12+
# and to permit persons to whom the Software is furnished to do so,
13+
# subject to the following conditions:
14+
#
15+
# The above copyright notice and this permission notice shall be
16+
# included in all copies or substantial portions of the Software.
17+
#
18+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
22+
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
23+
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25+
# SOFTWARE.
26+
27+
"""
28+
Tests for math.floor(), math.ceil(), and round() on bitmath instances
29+
"""
30+
31+
import math
32+
from . import TestCase
33+
import bitmath
34+
35+
36+
class TestFloor(TestCase):
37+
def test_floor_fractional_returns_same_type(self):
38+
"""math.floor() returns the same unit type"""
39+
result = math.floor(bitmath.MiB(1.9))
40+
self.assertIsInstance(result, bitmath.MiB)
41+
42+
def test_floor_rounds_down(self):
43+
"""math.floor() rounds the prefix value down"""
44+
self.assertEqual(math.floor(bitmath.MiB(1.9)), bitmath.MiB(1))
45+
46+
def test_floor_whole_number_unchanged(self):
47+
"""math.floor() on a whole prefix value returns that value"""
48+
self.assertEqual(math.floor(bitmath.KiB(3)), bitmath.KiB(3))
49+
50+
def test_floor_negative_rounds_toward_negative_infinity(self):
51+
"""math.floor() on a negative value rounds toward negative infinity"""
52+
self.assertEqual(math.floor(bitmath.GiB(-1.1)), bitmath.GiB(-2))
53+
54+
def test_floor_division_result(self):
55+
"""math.floor() on a division result produces integer prefix value"""
56+
self.assertEqual(math.floor(bitmath.KiB(1) / 3), bitmath.KiB(0))
57+
58+
def test_floor_preserves_unit_across_types(self):
59+
"""math.floor() works across all unit types"""
60+
for unit in [bitmath.Byte, bitmath.KiB, bitmath.MiB, bitmath.GiB,
61+
bitmath.kB, bitmath.MB]:
62+
result = math.floor(unit(1.7))
63+
self.assertIsInstance(result, unit)
64+
self.assertEqual(result, unit(1))
65+
66+
67+
class TestCeil(TestCase):
68+
def test_ceil_fractional_returns_same_type(self):
69+
"""math.ceil() returns the same unit type"""
70+
result = math.ceil(bitmath.MiB(1.1))
71+
self.assertIsInstance(result, bitmath.MiB)
72+
73+
def test_ceil_rounds_up(self):
74+
"""math.ceil() rounds the prefix value up"""
75+
self.assertEqual(math.ceil(bitmath.MiB(1.1)), bitmath.MiB(2))
76+
77+
def test_ceil_whole_number_unchanged(self):
78+
"""math.ceil() on a whole prefix value returns that value"""
79+
self.assertEqual(math.ceil(bitmath.KiB(3)), bitmath.KiB(3))
80+
81+
def test_ceil_negative_rounds_toward_zero(self):
82+
"""math.ceil() on a negative value rounds toward zero"""
83+
self.assertEqual(math.ceil(bitmath.GiB(-1.9)), bitmath.GiB(-1))
84+
85+
def test_ceil_division_result(self):
86+
"""math.ceil() on a division result rounds up to next prefix unit"""
87+
self.assertEqual(math.ceil(bitmath.KiB(1) / 3), bitmath.KiB(1))
88+
89+
def test_ceil_preserves_unit_across_types(self):
90+
"""math.ceil() works across all unit types"""
91+
for unit in [bitmath.Byte, bitmath.KiB, bitmath.MiB, bitmath.GiB,
92+
bitmath.kB, bitmath.MB]:
93+
result = math.ceil(unit(1.2))
94+
self.assertIsInstance(result, unit)
95+
self.assertEqual(result, unit(2))
96+
97+
98+
class TestRound(TestCase):
99+
def test_round_no_ndigits_returns_same_type(self):
100+
"""round() with no ndigits returns the same unit type"""
101+
result = round(bitmath.GiB(3.7))
102+
self.assertIsInstance(result, bitmath.GiB)
103+
104+
def test_round_no_ndigits_rounds_to_nearest(self):
105+
"""round() with no ndigits rounds to the nearest integer prefix value"""
106+
self.assertEqual(round(bitmath.GiB(3.7)), bitmath.GiB(4))
107+
self.assertEqual(round(bitmath.GiB(3.2)), bitmath.GiB(3))
108+
109+
def test_round_with_ndigits_returns_same_type(self):
110+
"""round(x, ndigits) returns the same unit type"""
111+
result = round(bitmath.KiB(1.555), 2)
112+
self.assertIsInstance(result, bitmath.KiB)
113+
114+
def test_round_with_ndigits(self):
115+
"""round(x, ndigits) rounds to the specified decimal precision"""
116+
self.assertEqual(round(bitmath.KiB(1.5), 0), bitmath.KiB(2))
117+
self.assertEqual(round(bitmath.MiB(2.567), 1), bitmath.MiB(2.6))
118+
119+
def test_round_whole_number_unchanged(self):
120+
"""round() on a whole prefix value returns that value"""
121+
self.assertEqual(round(bitmath.MiB(5)), bitmath.MiB(5))
122+
123+
def test_round_negative_value(self):
124+
"""round() on a negative value rounds to nearest"""
125+
self.assertEqual(round(bitmath.GiB(-3.7)), bitmath.GiB(-4))
126+
self.assertEqual(round(bitmath.GiB(-3.2)), bitmath.GiB(-3))
127+
128+
def test_floor_ceil_round_not_equal_for_fractional(self):
129+
"""floor, ceil, and round give distinct results for fractional values"""
130+
val = bitmath.MiB(1.6)
131+
self.assertEqual(math.floor(val), bitmath.MiB(1))
132+
self.assertEqual(math.ceil(val), bitmath.MiB(2))
133+
self.assertEqual(round(val), bitmath.MiB(2))

0 commit comments

Comments
 (0)