5. Getting Started
In this section we will take a high-level look at the basic things you can do with bitmath. We’ll include the following topics:
5.1. Tables of Supported Operations
The following legend describes the two operands used in the tables below.
Operand |
Description |
|---|---|
|
A bitmath object is required |
|
An integer or decimal value is required |
5.1.1. Arithmetic
Math works mostly like you expect it to, except for a few edge-cases:
Mixing bitmath types with Number types (the result varies per-operation)
Operations where two bitmath types would cancel out (such as dividing two bitmath types)
Multiplying two bitmath instances together is supported, but the results may not make much sense.
See also
- Appendix: Rules for Math
For a discussion of the behavior of bitmath and number types.
Operation |
Parameters |
Result Type |
Example |
|---|---|---|---|
Addition |
|
|
|
Addition |
|
|
|
Addition |
|
|
|
Subtraction |
|
|
|
Subtraction |
|
|
|
Subtraction |
|
|
|
Multiplication |
|
|
|
Multiplication |
|
|
|
Multiplication |
|
|
|
Division |
|
|
|
Division |
|
|
|
Division |
|
|
|
Floor Division |
|
|
|
Floor Division |
|
|
|
Modulo |
|
|
|
Modulo |
|
|
|
divmod |
|
|
|
For
//and%with a scalar RHS, the scalar operates on the internal byte count (consistent with how/is defined forbm / num). This is mostly useful forbm op bmforms; thebm op numforms are provided for completeness.
5.1.2. Bitwise Operations
See also
- Bitwise Calculator
A free online calculator for checking your math
Bitwise operations are also supported. Bitwise operations work
directly on the bits attribute of a bitmath instance, not the
number you see in an instances printed representation (value), to
maintain accuracy.
Operation |
Parameters |
Result Type |
Example1 |
|---|---|---|---|
Left Shift |
|
|
|
Right Shift |
|
|
|
AND |
|
|
|
OR |
|
|
|
XOR |
|
|
|
Give me a break here, it’s not easy coming up with compelling examples for bitwise operations…
5.2. Basic Math
bitmath supports all arithmetic operations
1>>> eighty_four_mib = fourty_two_mib + fourty_two_mib_in_kib
2>>> eighty_four_mib
3MiB(84.0)
4>>> eighty_four_mib == fourty_two_mib * 2
5True
5.3. Capacity Math: Floor Division and Modulo
Floor division (//), modulo (%), and divmod() are useful for
capacity-planning problems: “how many N-sized chunks fit into this
device, and how much is left over?”
As with the other arithmetic operators, the left-hand operand’s type
is preserved in the result. The one exception is bm1 // bm2, which
returns a unitless int — a count of whole divisions — mirroring how
bm1 / bm2 returns a unitless ratio.
1>>> from bitmath import GiB, MiB
2>>> disk = GiB(1)
3>>> chunk = MiB(300)
4
5>>> disk // chunk # how many whole 300 MiB chunks fit in 1 GiB?
63
7>>> disk % chunk # leftover, typed as the LHS (GiB)
8GiB(0.12109375)
9>>> divmod(disk, chunk) # both at once
10(3, GiB(0.12109375))
11
12>>> # Exact fits produce a zero-valued bitmath in the LHS unit:
13>>> GiB(1) % MiB(1)
14GiB(0.0)
Note
With a scalar RHS, the scalar operates on the internal byte count
— consistent with how bm / num is defined. In practice the
bm op bm forms above are what you want for capacity math; the
bm op num forms are available but rarely useful.
The fundamental identity holds — (a // b) * b + (a % b) == a:
>>> q, r = divmod(GiB(1), MiB(300))
>>> (q * MiB(300)) + r == GiB(1)
True
5.3.1. Coercing the Remainder with best_prefix
Because % preserves the LHS type, a small remainder can display
with an awkwardly tiny value. Use .best_prefix() to re-express
the result in a more human-readable unit without changing its size:
1>>> leftover = GiB(1) % MiB(300)
2>>> leftover
3GiB(0.12109375)
4>>> leftover.best_prefix()
5MiB(124.0)
6
7>>> # Force a specific unit system:
8>>> import bitmath
9>>> leftover.best_prefix(system=bitmath.SI)
10MB(130.02330...)
11
12>>> # Or coerce directly to a known unit:
13>>> leftover.to_MiB()
14MiB(124.0)
5.3.2. Formatting Remainders in a bitmath.format Context
The bitmath.format context manager pairs nicely with modulo results
when you want consistent, human-readable display throughout a block of
capacity calculations — including automatic best-prefix coercion:
1>>> import bitmath
2>>> from bitmath import GiB, MiB, TiB
3
4>>> volume = TiB(1)
5>>> block = GiB(7)
6>>> with bitmath.format(fmt_str="{value:.2f} {unit}", bestprefix=True):
7... whole = volume // block
8... leftover = volume % block
9... print(f"{whole} whole blocks of {block} fit in {volume}")
10... print(f"leftover: {leftover}")
11146 whole blocks of 7.00 GiB fit in 1.00 TiB
12leftover: 2.00 GiB
13
14>>> # divmod inside a context manager, reporting in plural form.
15>>> # Note: bestprefix on a zero remainder collapses to bits ("b").
16>>> with bitmath.format(fmt_str="{value:.1f} {unit}", plural=True, bestprefix=True):
17... q, r = divmod(GiB(10), MiB(256))
18... print(f"{q} chunks, {r} remaining")
1940 chunks, 0.0 b remaining
Note
bitmath.format is thread-safe — settings are thread-local, so
concurrent contexts in different threads do not interfere.
5.4. Unit Conversion
1>>> from bitmath import *
2>>> fourty_two_mib = MiB(42)
3>>> fourty_two_mib_in_kib = fourty_two_mib.to_KiB()
4>>> fourty_two_mib_in_kib
5KiB(43008.0)
6
7>>> fourty_two_mib
8MiB(42.0)
9
10>>> fourty_two_mib.KiB
11KiB(43008.0)
Let’s convert a unit and show the differences. What happens if we convert 2048
MB into GiBs? Let’s look the MB unit in closer detail:
>>> from bitmath import *
>>> MB(2048).base, MB(2048).power, MB(2048).bytes
(10, 6, 2048000000.0)
An MB (megabyte) is an SI unit in the base-10 number system. A single megabyte
is 10 raised to the power of 6. When you raise 10 to the power of 6 and multiply
the result by 2048 you get the number of bytes in MB(2048). We can check
this by creating a byte object with that many bytes as the value and ask for the
MB equivalent:
>>> bitmath.Byte((10**6)*2048).MB
MB(2048.0)
What about that conversion though, how about we convert this into GiBs? Those are NIST units, it is a base-2 number system. Let’s convert the MB to a GiB and look at those instance attributes again:
>>> convert_demo = bitmath.MB(2048).GiB
>>> convert_demo
GiB(1.9073486328125)
>>> convert_demo.base, convert_demo.power, convert_demo.bytes
(2, 30, 2048000000.0)
We can see that the entire calculation has changed but the number of bytes has remained the same. Now we have 2 raised to the power of 6, times 1.9073486328125.
A cleaner looking conversion is possible if we convert this within the same unit system. If we look at the GB equivalent we get:
>>> convert_demo.GB
GB(2.048)
Because we aren’t shifting between different base number systems.
5.5. Rich Comparison
Rich Comparison (as per the Python Basic Customization
magic methods) <, <=, ==, !=, >, >= is fully
supported:
1>>> GB(1) < GiB(1)
2True
3>>> GB(1.073741824) == GiB(1)
4True
5>>> GB(1.073741824) <= GiB(1)
6True
7>>> Bit(1) == TiB(bits=1)
8True
9>>> kB(100) > EiB(bytes=1024)
10True
11>>> kB(100) >= EiB.from_other(kB(100))
12True
13>>> kB(100) >= EiB.from_other(kB(99))
14True
15>>> kB(100) >= EiB.from_other(kB(9999))
16False
17>>> KiB(100) != Byte(1)
18True
5.6. Sorting
bitmath natively supports sorting.
Let’s make a list of the size (in bytes) of all the files in the present working directory (lines 4 and 5) and then print them out sorted by increasing magnitude (lines 10 and 11, and 13 → 15):
1>>> from bitmath import *
2>>> import os
3>>> sizes = []
4>>> for f in os.listdir('./tests/'):
5... sizes.append(KiB(os.path.getsize('./tests/' + f)))
6
7>>> print(sizes)
8[KiB(7337.0), KiB(1441.0), KiB(2126.0), KiB(2178.0), KiB(2326.0), KiB(4003.0), KiB(48.0), KiB(1770.0), KiB(7892.0), KiB(4190.0)]
9
10>>> print(sorted(sizes))
11[KiB(48.0), KiB(1441.0), KiB(1770.0), KiB(2126.0), KiB(2178.0), KiB(2326.0), KiB(4003.0), KiB(4190.0), KiB(7337.0), KiB(7892.0)]
12
13>>> human_sizes = [s.best_prefix() for s in sizes]
14>>> print(sorted(human_sizes))
15[KiB(48.0), MiB(1.4072265625), MiB(1.728515625), MiB(2.076171875), MiB(2.126953125), MiB(2.271484375), MiB(3.9091796875), MiB(4.091796875), MiB(7.1650390625), MiB(7.70703125)]
Now print them out in descending magnitude
>>> print(sorted(human_sizes, reverse=True))
[MiB(7.70703125), MiB(7.1650390625), MiB(4.091796875), MiB(3.9091796875), MiB(2.271484375), MiB(2.126953125), MiB(2.076171875), MiB(1.728515625), MiB(1.4072265625), KiB(48.0)]
5.7. Parsing Strings
bitmath.parse_string() converts a human-readable string into
a bitmath instance. By default the unit must be an exact bitmath type
name:
>>> import bitmath
>>> bitmath.parse_string("4.7 GiB")
GiB(4.7)
>>> bitmath.parse_string("1337 MB")
MB(1337.0)
>>> bitmath.parse_string("1 Mio") # octet alias
MiB(1.0)
When the input comes from a tool that produces ambiguous output
(often-times single-letter units) use strict=False. Pass
system=bitmath.SI or system=bitmath.NIST to tell the parser
which system to use if the unit can not reliably be determined
automatically:
>>> bitmath.parse_string("4G", strict=False) # NIST default
GiB(4.0)
>>> bitmath.parse_string("4G", strict=False, system=bitmath.SI)
GB(4.0)
>>> bitmath.parse_string("100", strict=False) # plain number → bytes
Byte(100.0)
>>> bitmath.parse_string("100 GiB", strict=False, system=bitmath.SI) # i-marker wins
GiB(100.0)
See also
bitmath.parse_string() — full parameter reference and caveats.
5.8. Summing an Iterable
The built-in sum() works with bitmath objects. Because
0 + bm returns bm itself (the identity element), accumulation
starts correctly and the result type matches the first element in
the iterable:
>>> import bitmath
>>> sum([bitmath.KiB(1), bitmath.KiB(2)])
KiB(3.0)
>>> sum([bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)])
Byte(1074790401.0)
Results from mixing plain numbers and numbers with units yields a result with no units.
>>> sum([bitmath.Byte(1), 0])
1.0
>>> sum([1, bitmath.KiB(2)])
3.0
See also
Appendix: Rules for Math — for a thrilling discussion about the minute details when doing mixed-type math math. What it all boils down to is this: if we don’t provide a unit then bitmath won’t give us one back.
Use bitmath.sum() when you need the result normalised to a
specific unit regardless of the input types. Without a start
argument it accumulates into bitmath.Byte; pass start to
choose a different accumulator (resultant unit):
>>> bitmath.sum([bitmath.MiB(1), bitmath.GiB(1)])
Byte(1074790400.0)
>>> bitmath.sum([bitmath.KiB(1), bitmath.KiB(2)], start=bitmath.MiB(0))
MiB(0.0029296875)
>>> bitmath.sum([bitmath.MiB(100), bitmath.KiB(2000)], start=bitmath.GiB(0))
GiB(0.0995635986328125)
5.9. Rounding
bitmath represents sizes as floating-point measurements. When an integer
result is needed, Python’s math.floor(), math.ceil(),
and round() all work directly on bitmath instances and return
an instance of the same type:
>>> import math, bitmath
>>> math.floor(bitmath.KiB(1) / 3)
KiB(0)
>>> math.ceil(bitmath.KiB(1) / 3)
KiB(1)
>>> round(bitmath.MiB(1.75))
MiB(2)
>>> round(bitmath.GiB(1.23456), 2)
GiB(1.23)
Warning
Rounding intermediate results is lossy. math.floor(GiB(10) / 3) * 3
yields GiB(9), not GiB(10). Only round at the final output step.
See also
Appendix: Rules for Math — discussion of floating-point representation and when rounding is appropriate.
5.10. Choosing a Formatting Approach
bitmath offers several ways to control how instances are rendered as strings. They overlap deliberately — each suits a different situation. This section helps you pick the right one.
Quick reference
Approach |
Best for |
Avoid when |
|---|---|---|
Default |
Printing, debugging, logging |
You need custom precision or layout |
|
Full control over layout using any instance attribute |
You only need to format the number |
f-strings / |
Inline formatting in modern Python; columnar output |
You need unit-aware attributes beyond |
|
Consistent formatting across a block of code; threaded code |
A one-off format on a single value |
|
Changing the default for an entire script or session |
Anything other than a top-level script (mutates global state) |
5.10.1. Default str()
The simplest option. Just print or convert to string — no imports, no
setup. Output follows the module-level format_string (default:
"{value} {unit}"):
>>> import bitmath
>>> print(bitmath.MiB(1.5))
1.5 MiB
>>> str(bitmath.GiB(10))
'10.0 GiB'
Use this when you just need a readable value and don’t care about precision or alignment.
5.10.2. instance.format()
The most expressive option. The format string has access to every
instance attribute — {value}, {unit}, {bits}, {bytes},
{system}, {base}, {power}, and more:
>>> size = bitmath.MiB(1 / 3.0)
>>> size.format("{value:.2f} {unit} ({bits:.0f} bits)")
'0.33 MiB (2796202 bits)'
Use this when you need the unit label, bit/byte counts, or any other instance attribute woven into the output string.
See also
Instance Formatting — full attribute reference.
5.10.3. f-strings and format()
Standard Python formatting. The format spec applies to self.value
only; the unit is omitted unless you add it explicitly with
{size.unit}:
>>> size = bitmath.GiB(127.3)
>>> f'{size:.2f} {size.unit}'
'127.30 GiB'
>>> f'{size}' # no spec → same as str(size)
'127.3 GiB'
This shines for columnar output where alignment matters:
>>> rows = [("home", bitmath.GiB(127.3)), ("tmp", bitmath.MiB(843.7))]
>>> for mount, size in rows:
... print(f"{mount:<8} {size:>10.2f} {size.unit}")
home 127.30 GiB
tmp 843.70 MiB
Use this when you’re building formatted strings inline and only need the numeric value with a precision or alignment spec.
5.10.4. bitmath.format() context manager
Sets fmt_str, plural, and bestprefix for every bitmath
str() call within the block, then restores the previous state
automatically — even if an exception is raised. Safe to use in
threaded code:
>>> sizes = [bitmath.KiB(1024), bitmath.MiB(512)]
>>> with bitmath.format(fmt_str="{value:.1f} {unit}", bestprefix=True):
... for s in sizes:
... print(s)
1.0 MiB
512.0 MiB
Use this when you want a consistent format across multiple
print() or str() calls without touching each one individually,
or when you’re in a threaded environment.
See also
bitmath.format() — full parameter reference.
5.10.5. bitmath.format_string global
Sets the default representation for all bitmath instances for the remainder of the process. Useful at the top of a script; a poor choice inside a library or threaded code:
>>> import bitmath
>>> bitmath.format_string = "{value:.2f} {unit}"
>>> print(bitmath.MiB(1.5))
1.50 MiB
Use this when you control the entire script and want a single format everywhere without wrapping everything in a context manager. Prefer the context manager for anything more targeted.
Warning
Mutating bitmath.format_string directly affects all threads.
Use the bitmath.format() context manager instead in
concurrent code.