.. image:: https://api.securityscorecards.dev/projects/github.com/timlnx/bitmath/badge :target: https://securityscorecards.dev/viewer/?uri=github.com/timlnx/bitmath :alt: OSSF Scorecard .. image:: https://www.bestpractices.dev/projects/12749/badge :target: https://www.bestpractices.dev/en/projects/12749 :alt: OpenSSF Best Practices .. image:: https://www.bestpractices.dev/projects/12749/baseline :target: https://www.bestpractices.dev/projects/12749 :alt: OpenSSF Baseline .. image:: https://github.com/timlnx/bitmath/actions/workflows/python.yml/badge.svg :target: https://github.com/timlnx/bitmath/actions/workflows/python.yml .. image:: https://img.shields.io/pypi/v/bitmath.svg :target: https://pypi.org/project/bitmath/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/bitmath?style=flat-square :target: https://pypistats.org/packages/bitmath :alt: PyPI - Package Downloads .. image:: https://img.shields.io/pypi/implementation/bitmath?style=flat-square :alt: PyPI - Implementation .. image:: https://img.shields.io/pypi/pyversions/bitmath?style=flat-square :alt: PyPI - Python Version .. image:: https://app.readthedocs.org/projects/bitmath/badge/?version=latest :target: https://bitmath.readthedocs.io/ .. image:: https://github.com/timlnx/bitmath/actions/workflows/bandit.yml/badge.svg :target: https://github.com/timlnx/bitmath/actions/workflows/bandit.yml :alt: Bandit Security Scan .. image:: https://img.shields.io/github/issues/timlnx/bitmath?style=flat-square :target: https://github.com/timlnx/bitmath/issues :alt: Open issues .. image:: https://img.shields.io/github/issues-pr/timlnx/bitmath?style=flat-square :target: https://github.com/timlnx/bitmath/pulls :alt: Open pull requests .. image:: https://img.shields.io/github/stars/timlnx/bitmath?style=flat-square :target: https://github.com/timlnx/bitmath :alt: GitHub Project Popularity .. image:: https://img.shields.io/badge/license-MIT-blue.svg :target: https://github.com/timlnx/bitmath/blob/master/LICENSE :alt: License bitmath ####### * Free software: MIT License * Documentation: https://bitmath.readthedocs.io/en/latest/ * Source: https://github.com/timlnx/bitmath * Bugs: https://github.com/timlnx/bitmath/issues * Contributing: https://bitmath.readthedocs.io/en/latest/contributing.html `bitmath `_ simplifies many facets of interacting with file sizes in various units. Originally focusing on file size unit conversion, functionality now includes: * Converting between **SI** and **NIST** prefix units (``kB`` to ``GiB``) * Converting between units of the same type (SI to SI, or NIST to NIST) * Full NIST unit coverage including **ZiB**, **YiB**, **Zib**, and **Yib** * Automatic human-readable prefix selection (like in `hurry.filesize `_) * Basic arithmetic operations (subtracting 42KiB from 50GiB) * Capacity math with floor division, modulo, and ``divmod`` (``GiB(1) // MiB(300)``, ``GiB(1) % MiB(300)``) * Rich comparison operations (``1024 Bytes == 1KiB``) * Bitwise operations (``<<``, ``>>``, ``&``, ``|``, ``^``) * Rounding via :py:func:`math.floor`, :py:func:`math.ceil`, and :py:func:`round` * Reading a device's storage capacity (Linux/macOS support only) * String parsing, including flexible non-strict parsing of ambiguous input * Sorting * Summing iterables via built-in :py:func:`sum` or :py:func:`bitmath.sum` for unit-normalised results * f-string and :py:func:`format` support via the standard Python formatting protocol * `argparse `_ integration as a custom type In addition to the conversion and math operations, `bitmath` provides human readable representations of values which are suitable for use in interactive shells as well as larger scripts and applications. The format produced for these representations is customizable via the functionality included in stdlibs `string.format `_. In discussion we will refer to the NIST units primarily. I.e., instead of "megabyte" we will refer to "mebibyte". The former is ``10^3 = 1,000,000`` bytes, whereas the second is ``2^20 = 1,048,576`` bytes. When you see file sizes or transfer rates in your web browser, most of the time what you're really seeing are the base-2 sizes/rates. **Don't Forget!** The source for bitmath `is available on GitHub `_. And did we mention there are nearly 300 unit tests? `Check them out for yourself `_. * :ref:`Examples ` after the TOC. Installation ############ .. admonition:: Seeking a Debian Maintainer bitmath is not currently packaged for Debian or Ubuntu. If you're interested in maintaining the package for those distributions, please see `issue #117 `_. Requires Python 3.9 or newer. No runtime dependencies outside the standard library. **PyPI** (the typical path): .. code-block:: bash pip install bitmath **Fedora and EPEL** .. code-block:: bash sudo dnf install python3-bitmath **From source** .. code-block:: bash git clone https://github.com/timlnx/bitmath.git pip install ./bitmath Contents ######## .. toctree:: :maxdepth: 2 :numbered: module.rst commandline.rst classes.rst instances.rst simple_examples.rst real_life_examples.rst integration_examples.rst contributing.rst appendices.rst NEWS.rst contact.rst copyright.rst .. _index_examples: Examples ======== Arithmetic ---------- .. code-block:: python >>> import bitmath >>> log_size = bitmath.kB(137.4) >>> log_zipped_size = bitmath.Byte(987) >>> print("Compression saved %s space" % (log_size - log_zipped_size)) Compression saved 136.413kB space >>> thumb_drive = bitmath.GiB(12) >>> song_size = bitmath.MiB(5) >>> songs_per_drive = thumb_drive / song_size >>> print(songs_per_drive) 2457.6 Capacity Planning ----------------- Floor division (``//``), modulo (``%``), and ``divmod()`` are handy for chunk-and-remainder capacity math. ``bm1 // bm2`` returns an ``int`` (how many whole chunks fit); ``bm1 % bm2`` returns a ``bitmath`` of the **left-hand operand's type** (the leftover). .. code-block:: python >>> from bitmath import GiB, MiB, TiB >>> disk = GiB(1) >>> chunk = MiB(300) >>> disk // chunk # how many whole 300 MiB chunks fit? 3 >>> disk % chunk # leftover, typed as the LHS (GiB) GiB(0.12109375) >>> divmod(disk, chunk) # both at once (3, GiB(0.12109375)) Re-express the remainder in a human-readable unit with ``best_prefix()`` (or coerce directly with ``to_MiB()``, etc.): .. code-block:: python >>> (GiB(1) % MiB(300)).best_prefix() MiB(124.0) Pair with the ``bitmath.format`` context manager for clean reporting across a block of capacity calculations: .. code-block:: python >>> import bitmath >>> volume = TiB(1) >>> block = GiB(7) >>> with bitmath.format(fmt_str="{value:.2f} {unit}", bestprefix=True): ... whole, leftover = divmod(volume, block) ... print(f"{whole} whole blocks of {block} fit in {volume}") ... print(f"leftover: {leftover}") 146 whole blocks of 7.00 GiB fit in 1.00 TiB leftover: 2.00 GiB The identity ``(a // b) * b + (a % b) == a`` holds, so ``divmod`` round-trips. Convert Units ------------- File size unit conversion: .. code-block:: python >>> from bitmath import * >>> dvd_size = GiB(4.7) >>> print("DVD Size in MiB: %s" % dvd_size.to_MiB()) DVD Size in MiB: 4812.8 MiB Select a human-readable unit ---------------------------- .. code-block:: python >>> small_number = kB(100) >>> ugly_number = small_number.to_TiB() >>> print(ugly_number) 9.09494701773e-08 TiB >>> print(ugly_number.best_prefix()) 97.65625 KiB Rich Comparison --------------- .. code-block:: python >>> cd_size = MiB(700) >>> cd_size > dvd_size False >>> cd_size < dvd_size True >>> MiB(1) == KiB(1024) True >>> MiB(1) <= KiB(1024) True Sorting ------- .. code-block:: python >>> sizes = [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)] >>> print(sorted(sizes)) [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)] Custom Formatting ----------------- * Use of the custom formatting system * All of the available instance properties Example: .. code-block:: python >>> longer_format = """Formatting attributes for %s ...: This instances prefix unit is {unit}, which is a {system} type unit ...: The unit value is {value} ...: This value can be truncated to just 1 digit of precision: {value:.1f} ...: In binary this looks like: {binary} ...: The prefix unit is derived from a base of {base} ...: Which is raised to the power {power} ...: There are {bytes} bytes in this instance ...: The instance is {bits} bits large ...: bytes/bits without trailing decimals: {bytes:.0f}/{bits:.0f}""" % str(ugly_number) >>> print(ugly_number.format(longer_format)) Formatting attributes for 5.96046447754 MiB This instances prefix unit is MiB, which is a NIST type unit The unit value is 5.96046447754 This value can be truncated to just 1 digit of precision: 6.0 In binary this looks like: 0b10111110101111000010000000 The prefix unit is derived from a base of 2 Which is raised to the power 20 There are 6250000.0 bytes in this instance The instance is 50000000.0 bits large bytes/bits without trailing decimals: 6250000/50000000 Utility Functions ----------------- **bitmath.getsize()** .. code-block:: python >>> print(bitmath.getsize('python-bitmath.spec')) 3.7060546875 KiB **bitmath.parse_string()** Parse a string with standard units: .. code-block:: python >>> import bitmath >>> a_dvd = bitmath.parse_string("4.7 GiB") >>> print(type(a_dvd)) >>> print(a_dvd) 4.7 GiB **bitmath.parse_string_unsafe()** Parse a string with ambiguous units: .. code-block:: python >>> import bitmath >>> a_gig = bitmath.parse_string_unsafe("1gb") >>> print(type(a_gig)) >>> a_gig == bitmath.GB(1) True >>> bitmath.parse_string_unsafe('1gb') == bitmath.parse_string_unsafe('1g') True **bitmath.query_device_capacity()** .. code-block:: python >>> import bitmath >>> with open('/dev/sda') as fp: ... root_disk = bitmath.query_device_capacity(fp) ... print(root_disk.best_prefix()) ... 238.474937439 GiB **bitmath.listdir()** .. code-block:: python >>> for i in bitmath.listdir('./tests/', followlinks=True, relpath=True, bestprefix=True): ... print(i) ... ('tests/test_file_size.py', KiB(9.2900390625)) ('tests/test_basic_math.py', KiB(7.1767578125)) ('tests/__init__.py', KiB(1.974609375)) ('tests/test_bitwise_operations.py', KiB(2.6376953125)) ('tests/test_context_manager.py', KiB(3.7744140625)) ('tests/test_representation.py', KiB(5.2568359375)) ('tests/test_properties.py', KiB(2.03125)) ('tests/test_instantiating.py', KiB(3.4580078125)) ('tests/test_future_math.py', KiB(2.2001953125)) ('tests/test_best_prefix_BASE.py', KiB(2.1044921875)) ('tests/test_rich_comparison.py', KiB(3.9423828125)) ('tests/test_best_prefix_NIST.py', KiB(5.431640625)) ('tests/test_unique_testcase_names.sh', Byte(311.0)) ('tests/.coverage', KiB(3.1708984375)) ('tests/test_best_prefix_SI.py', KiB(5.34375)) ('tests/test_to_built_in_conversion.py', KiB(1.798828125)) ('tests/test_to_Type_conversion.py', KiB(8.0185546875)) ('tests/test_sorting.py', KiB(4.2197265625)) ('tests/listdir_symlinks/10_byte_file_link', Byte(10.0)) ('tests/listdir_symlinks/depth1/depth2/10_byte_file', Byte(10.0)) ('tests/listdir_nosymlinks/depth1/depth2/10_byte_file', Byte(10.0)) ('tests/listdir_nosymlinks/depth1/depth2/1024_byte_file', KiB(1.0)) ('tests/file_sizes/kbytes.test', KiB(1.0)) ('tests/file_sizes/bytes.test', Byte(38.0)) ('tests/listdir/10_byte_file', Byte(10.0)) Formatting ---------- .. code-block:: python >>> with bitmath.format(fmt_str="[{value:.3f}@{unit}]"): ... for i in bitmath.listdir('./tests/', followlinks=True, relpath=True, bestprefix=True): ... print(i[1]) ... [9.290@KiB] [7.177@KiB] [1.975@KiB] [2.638@KiB] [3.774@KiB] [5.257@KiB] [2.031@KiB] [3.458@KiB] [2.200@KiB] [2.104@KiB] [3.942@KiB] [5.432@KiB] [311.000@Byte] [3.171@KiB] [5.344@KiB] [1.799@KiB] [8.019@KiB] [4.220@KiB] [10.000@Byte] [10.000@Byte] [10.000@Byte] [1.000@KiB] [1.000@KiB] [38.000@Byte] [10.000@Byte] ``argparse`` Integration ------------------------ A self-contained example showing how to use bitmath as an argparse argument type is available in the `Integration Examples `_ chapter of the documentation. .. code-block:: python import argparse import bitmath def BitmathType(value): try: return bitmath.parse_string(value) except ValueError: raise argparse.ArgumentTypeError( f"{value!r} is not a recognized bitmath unit string" ) parser = argparse.ArgumentParser() parser.add_argument('--block-size', type=BitmathType, required=True) args = parser.parse_args(['--block-size', '10MiB']) print(args.block_size) # 10.0 MiB