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

bm

A bitmath object is required

num

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

bm1 + bm2

type(bm1)

KiB(1) + MiB(2) = 2049.0KiB

Addition

bm + num

type(num)

KiB(1) + 1 = 2.0

Addition

num + bm

type(num)

1 + KiB(1) = 2.0

Subtraction

bm1 - bm2

type(bm1)

KiB(1) - Byte(2048) = -1.0KiB

Subtraction

bm - num

type(num)

KiB(4) - 1 = 3.0

Subtraction

num - bm

type(num)

10 - KiB(1) = 9.0

Multiplication

bm1 * bm2

type(bm1)

KiB(1) * KiB(2) = 2048.0KiB

Multiplication

bm * num

type(bm)

KiB(2) * 3 = 6.0KiB

Multiplication

num * bm

type(bm)

2 * KiB(3) = 6.0KiB

Division

bm1 / bm2

type(num)

KiB(1) / KiB(2) = 0.5

Division

bm / num

type(bm)

KiB(6) / 4 = KiB(1.5)

Division

num / bm

type(num)

3 / KiB(2) = 1.5

Floor Division

bm1 // bm2

int

GiB(1) // MiB(300) = 3

Floor Division

bm // num

type(bm)

KiB(6) // 4 = KiB(1.5)2

Modulo

bm1 % bm2

type(bm1)

GiB(1) % MiB(300) = GiB(0.12...)

Modulo

bm % num

type(bm)

GiB(1) % 1000 = GiB(2.23e-7)2

divmod

divmod(bm1, bm2)

(int, type(bm1))

divmod(GiB(1), MiB(300)) = (3, GiB(0.12...))

  1. For // and % with a scalar RHS, the scalar operates on the internal byte count (consistent with how / is defined for bm / num). This is mostly useful for bm op bm forms; the bm op num forms 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

bm << num

type(bm)

MiB(1) << 2 = MiB(4.0)

Right Shift

bm >> num

type(bm)

MiB(1) >> 2 = MiB(0.25)

AND

bm & num

type(bm)

MiB(13.37) & 1337 = MiB(0.000126...)

OR

bm | num

type(bm)

MiB(13.37) | 1337 = MiB(13.3700...)

XOR

bm ^ num

type(bm)

MiB(13.37) ^ 1337 = MiB(13.369...)

  1. 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 1315):

 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 str()

Printing, debugging, logging

You need custom precision or layout

instance.format()

Full control over layout using any instance attribute

You only need to format the number

f-strings / format()

Inline formatting in modern Python; columnar output

You need unit-aware attributes beyond value

bitmath.format() context manager

Consistent formatting across a block of code; threaded code

A one-off format on a single value

bitmath.format_string global

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.