Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

571 Commits

Repository files navigation

OSSF ScorecardOpenSSF Best PracticesOpenSSF BaselineLatest VersionPyPI - Package Downloads

PyPI - Implementation

PyPI - Python Version

https://readthedocs.org/projects/bitmath/badge/?version=latestBandit Security ScanOpen issuesOpen pull requestsGitHub Project PopularityLicense

Bitmath logo

bitmath

bitmath lets you treat files sizes like any other number in Python.

  • 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 math.floor, math.ceil, and 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 sum or bitmath.sum for unit-normalised results
  • f-string and 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.

Running the tests should be as simple as calling the ci target in the Makefile: make ci. Please file a bug report if you run into issues.

Installation

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):

pip install bitmath

Fedora and EPEL

sudo dnf install python3-bitmath

From source

git clone https://github.com/timlnx/bitmath.git
pip install ./bitmath

Documentation

The main documentation lives at https://bitmath.readthedocs.io/en/latest/.

Topics include:

  • The bitmath Module
    • Utility Functions
    • Context Managers
    • Module Variables
    • argparse integration
  • The bitmath command-line Tool
  • Classes
    • Initializing
    • Available Classes
    • Class Methods
  • Instances
    • Instance Attributes
    • Instance Methods
    • Instance Properties
    • The Formatting Mini-Language
  • Getting Started
    • Tables of Supported Operations
    • Basic Math
    • Unit Conversion
    • Rich Comparison
    • Sorting
  • Real Life Examples
    • Download Speeds
    • Calculating how many files fit on a device
    • Printing Human-Readable File Sizes in Python
    • Calculating Linux BDP and TCP Window Scaling
  • Contributing to bitmath
  • Appendices
    • Rules for Math
    • On Units
    • Who uses Bitmath
    • Related Projects
  • NEWS
  • Copyright

Examples

Arithmetic

>>>importbitmath>>>log_size=bitmath.kB(137.4)
>>>log_zipped_size=bitmath.Byte(987)
>>>print("Compression saved %s space"% (log_size-log_zipped_size))
Compressionsaved136.413kBspace>>>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).

>>>frombitmathimportGiB, 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.):

>>> (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:

>>>importbitmath>>>volume=TiB(1)
>>>block=GiB(7)
>>>withbitmath.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}")
146wholeblocksof7.00GiBfitin1.00TiBleftover: 2.00GiB

The identity (a // b) * b + (a % b) == a holds, so divmod round-trips.

Convert Units

File size unit conversion:

>>>frombitmathimport*>>>dvd_size=GiB(4.7)
>>>print("DVD Size in MiB: %s"%dvd_size.to_MiB())
DVDSizeinMiB: 4812.8MiB

Select a human-readable unit

>>>small_number=kB(100)
>>>ugly_number=small_number.to_TiB()
>>>print(ugly_number)
9.09494701773e-08TiB>>>print(ugly_number.best_prefix())
97.65625KiB

Rich Comparison

>>>cd_size=MiB(700)
>>>cd_size>dvd_sizeFalse>>>cd_size<dvd_sizeTrue>>>MiB(1) ==KiB(1024)
True>>>MiB(1) <=KiB(1024)
True

Sorting

>>>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:

>>>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))
Formattingattributesfor5.96046447754MiBThisinstancesprefixunitisMiB, whichisaNIST type unitTheunitvalueis5.96046447754Thisvaluecanbetruncatedtojust1digitofprecision: 6.0Inbinarythislookslike: 0b10111110101111000010000000Theprefixunitisderivedfromabaseof2Whichisraisedtothepower20Thereare6250000.0bytesinthisinstanceTheinstanceis50000000.0bitslargebytes/bitswithouttrailingdecimals: 6250000/50000000

Utility Functions

bitmath.getsize()

>>>print(bitmath.getsize('python-bitmath.spec'))
3.7060546875KiB

bitmath.parse_string()

Parse a string with standard units:

>>>importbitmath>>>a_dvd=bitmath.parse_string("4.7 GiB")
>>>print(type(a_dvd))
<class'bitmath.GiB'>>>>print(a_dvd)
4.7GiB

bitmath.parse_string_unsafe()

Parse a string with ambiguous units:

>>>importbitmath>>>a_gig=bitmath.parse_string_unsafe("1gb")
>>>print(type(a_gig))
<class'bitmath.GB'>>>>a_gig==bitmath.GB(1)
True>>>bitmath.parse_string_unsafe('1gb') ==bitmath.parse_string_unsafe('1g')
True

bitmath.query_device_capacity()

>>>importbitmath>>>withopen('/dev/sda') asfp:
... root_disk=bitmath.query_device_capacity(fp)
... print(root_disk.best_prefix())
...
238.474937439GiB

bitmath.listdir()

>>>foriinbitmath.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

>>>withbitmath.format(fmt_str="[{value:.3f}@{unit}]"):
... foriinbitmath.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.

importargparseimportbitmathdefBitmathType(value):
try:
returnbitmath.parse_string(value)
exceptValueError:
raiseargparse.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

About

Python module to work with file sizes like numbers - convert, compare, sort, and format across any prefix unit

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

96 stars

Watchers

5 watching

Forks

Releases

Used by

Contributors

Languages