Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ chapter of the documentation.
return bitmath.parse_string(value)
except ValueError:
raise argparse.ArgumentTypeError(
f"{value!r} is not a recognised bitmath unit string"
f"{value!r} is not a recognized bitmath unit string"
)

parser = argparse.ArgumentParser()
Expand Down
4 changes: 2 additions & 2 deletions bitmath/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1516,8 +1516,8 @@ def parse_string(s, system=NIST, strict=True):

.. versionchanged:: 2.0.0
Added ``strict`` and ``system`` parameters. When ``strict=True``
(default) behaviour is identical to the original function.
When ``strict=False`` the behaviour of the former
(default) behavior is identical to the original function.
When ``strict=False`` the behavior of the former
``parse_string_unsafe`` is applied. The ``system`` parameter
defaults to ``bitmath.NIST`` and is ignored when ``strict=True``.
"""
Expand Down
78 changes: 58 additions & 20 deletions docsite/source/appendices/mixed_math.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,37 +69,57 @@ operation.
The result will be of the type of the LHS.

*Multiplication*
Supported, but yields strange results.
Supported, but yields results which may not be intuitive. The math is
performed at the byte-level (ignoring prefix units). The result is in the
unit of the LHS. Technically speaking, if the LHS of the equation is a byte
unit, then the result should be squared. You are advised to call the
:py:meth:`best_prefix` method on the result to get a useful value back.

.. code-block:: python
:linenos:
:emphasize-lines: 6,9

In [10]: first = MiB(5)
>>> bitmath.kB(3).bytes, bitmath.MiB(5).bytes
(3000.0, 5242880.0)
>>> bitmath.best_prefix(3000 * 5242880)
GiB(14.6484375)

In [11]: second = kB(2)
>>> bitmath.Byte(3000) * bitmath.MiB(5)
B(15728640000.0)

In [12]: first * second
Out[12]: MiB(10000.0)
>>> (bitmath.Byte(3000) * bitmath.MiB(5)).best_prefix()
GiB(14.6484375)

In [13]: (first * second).best_prefix()
Out[13]: GiB(9.765625)
The final result represents how many “GiB-sized” chunks of bytes are in that
total. It's weird, but it works.

As we can see on lines **6** and **9**, multiplying even two
relatively small quantities together (``MiB(5)`` and ``kB(2)``) yields
quite large results.
If the LHS is larger than a byte prefix unit then you get a result matching the
unit of the LHS of the equation.

Internally, this is implemented as:

.. math::
.. code-block:: python

(5 \cdot 2^{20}) \cdot (2 \cdot 10^{3}) = 10,485,760,000 B
>>> bitmath.MiB(5) * bitmath.kB(3)
MiB(15000.0)
# LHS was MiB, result is MiB

10,485,760,000 B \cdot \dfrac{1 MiB}{1,048,576 B} = 10,000 MiB
# And if we wrap it with best_prefix() we get the earlier result back
>>> (bitmath.MiB(5) * bitmath.kB(3)).best_prefix()
GiB(14.6484375)

*Division*
The result will be a number type due to unit cancellation.

.. code-block:: python

>>> bitmath.kB(3) / bitmath.MiB(5)
0.00057220458984375

>>> bitmath.kB(3).bytes / bitmath.MiB(5).bytes
0.00057220458984375

Above you can see that the math is performed on the bytes of each operand. As
noted above, the units cancel out. This is the opposite of the multiplication
case where the units square together if you do not coerce them into a larger
prefix unit.

.. _appendix_math_mixed_types:

Mixed Types: Addition and Subtraction
Expand All @@ -123,7 +143,7 @@ function works correctly with iterables of bitmath objects, since
>>> sum([bitmath.Byte(1), bitmath.MiB(1), bitmath.GiB(1)])
Byte(1074790401.0)

For all non-zero numeric operands the behaviour (returning a number)
For all non-zero numeric operands the behavior (returning a number)
applies.

**Discussion:** Why do ``100 - KiB(90)`` and ``KiB(100) - 90`` both
Expand Down Expand Up @@ -251,7 +271,25 @@ yourself what you would expect to get if you did this:

.. math::

\dfrac{100}{kB(33)} = x
\dfrac{100}{kB(33)}

Unless you're representing rates that doesn't mean much at all. The units of
operands when expressing rates is going to be context sensitive and can be very
non-intuitive without additional knowledge. For example:

.. code-block:: python

>>> 100/bitmath.kB(33)
3.0303030303030303

This is functionally equivalent to writing:

.. math::

\dfrac{1}{kB(33)} \cdot 100

This might mean something to you, but we can't express that as a prefix unit. We
let you do it, but it is up to you to determine the significance of the result.



Expand Down Expand Up @@ -326,6 +364,6 @@ Footnotes
<https://www.programiz.com/python-programming/precedence-associativity>`_

.. [#datamodel] `Python Datamodel Customization Methods
<https://docs.python.org/2.7/reference/datamodel.html#basic-customization>`_
<https://docs.python.org/3/reference/datamodel.html#basic-customization>`_

.. [#significance] https://en.wikipedia.org/wiki/Significance_arithmetic
2 changes: 1 addition & 1 deletion docsite/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ chapter of the documentation.
return bitmath.parse_string(value)
except ValueError:
raise argparse.ArgumentTypeError(
f"{value!r} is not a recognised bitmath unit string"
f"{value!r} is not a recognized bitmath unit string"
)

parser = argparse.ArgumentParser()
Expand Down
81 changes: 77 additions & 4 deletions docsite/source/integration_examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ directly.
return bitmath.parse_string(value)
except ValueError:
raise argparse.ArgumentTypeError(
f"{value!r} is not a recognised bitmath unit string "
f"{value!r} is not a recognized bitmath unit string "
"(examples: 10MiB, 1.5GiB, 500kB)"
)

Expand Down Expand Up @@ -72,7 +72,80 @@ Example run:
In KiB: 10240.00 KiB

$ python script.py --block-size bad
error: argument --block-size: 'bad' is not a recognised bitmath unit string (examples: 10MiB, 1.5GiB, 500kB)
error: argument --block-size: 'bad' is not a recognized bitmath unit string (examples: 10MiB, 1.5GiB, 500kB)

argparse validation
===================

Now say you want to perform some additional validation on this custom
``BitmathType`` unit. This is best done **after** the parsing is complete, not
as part of the ``BitmathType`` implementation. This follows the advice outlined
in the upstream :mod:`argparse` documentation.

In general, the ``type`` keyword is a convenience that should only be used for
simple conversions that can only raise one of the three supported exceptions.
Anything with more interesting error-handling or resource management should be
done downstream after the arguments are parsed.

First, parse the unit, allow ``BitmathType`` to handle the parsing validation.
Second, perform your own context-aware validation. For example, you might set
minimum or maximums and need to compare the parsed argument against them.

.. code-block:: python
:linenos:
:emphasize-lines: 17,29-30

import argparse
import bitmath


def BitmathType(value):
"""Convert a command-line string such as '10MiB' into a bitmath object."""
try:
return bitmath.parse_string(value)
except ValueError:
raise argparse.ArgumentTypeError(
f"{value!r} is not a recognized bitmath unit string "
"(examples: 10MiB, 1.5GiB, 500kB)"
)


def main():
max_block_size = bitmath.GiB(1)
parser = argparse.ArgumentParser(
description="Example script using a bitmath argument type"
)
parser.add_argument(
"--block-size",
type=BitmathType,
required=True,
help="Block size with unit, e.g. 10MiB",
)
args = parser.parse_args()

if args.block_size > bitmath.GiB(1):
raise ValueError(f"Provided block size {args.block_size} exceeds maximum {max_block_size}")

print(f"Block size: {args.block_size}")
print(f"In KiB: {args.block_size.to_KiB():.2f}")


if __name__ == "__main__":
main()

Example run:

.. code-block:: bash

$ python script.py --block-size 42GiB
Traceback (most recent call last):
File "script.py", line 37, in <module>
main()
~~~~^^
File "script.py", line 30, in main
raise ValueError(f"Provided block size {args.block_size} exceeds maximum {max_block_size}")
ValueError: Provided block size 42.0 GiB exceeds maximum 1.0 GiB



.. _integration_examples_click:
Expand Down Expand Up @@ -108,7 +181,7 @@ Install click before use:
return bitmath.parse_string(value)
except ValueError:
self.fail(
f"{value!r} is not a recognised bitmath unit string "
f"{value!r} is not a recognized bitmath unit string "
"(examples: 10MiB, 1.5GiB, 500kB)",
param,
ctx,
Expand Down Expand Up @@ -143,7 +216,7 @@ Example run:
In KiB: 10240.00 KiB

$ python script.py --block-size bad
Error: Invalid value for '--block-size': 'bad' is not a recognised bitmath unit string (examples: 10MiB, 1.5GiB, 500kB)
Error: Invalid value for '--block-size': 'bad' is not a recognized bitmath unit string (examples: 10MiB, 1.5GiB, 500kB)


.. _integration_examples_progressbar2:
Expand Down
24 changes: 21 additions & 3 deletions docsite/source/module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,24 @@ bitmath.listdir()
The **total** size of the files in this tree is **1337 + 13370 =
14707** bytes.

.. versionadded:: 2.0.0

By far the simplest way to sum all of the results is using the built-in
:py:func:`sum` function, or :py:func:`bitmath.sum` for additional control
(complete docs on that following this section).

.. code-block:: python

>>> discovered_files = [f[1] for f in bitmath.listdir('./some_files')]
>>> print(discovered_files)
[Byte(1337.0), Byte(13370.0)]
>>> print(sum(discovered_files))
14707.0 B
>>> print(sum(discovered_files).best_prefix())
14.3623046875 KiB



Let's call :py:func:`bitmath.listdir` on the ``some_files/``
directory and see what the results look like. First we'll use all
the default parameters, then we'll set ``relpath`` to ``True``:
Expand Down Expand Up @@ -391,7 +409,7 @@ bitmath.parse_string()

.. versionchanged:: 2.0.0
Added ``strict`` and ``system`` parameters. The default
``strict=True`` behaviour is identical to earlier versions.
``strict=True`` behavior is identical to earlier versions.
``system`` defaults to :py:data:`bitmath.NIST` and is only
consulted when ``strict=False``.

Expand All @@ -406,7 +424,7 @@ parse_string with ``strict=False``
When ``strict=False`` the parser accepts ambiguous input that does not
conform to exact bitmath type names — for example, the single-letter
units produced by tools like ``ls -h``, ``df``, and ``qemu-img``. This
is the behaviour previously provided by the now-deprecated
is the behavior previously provided by the now-deprecated
:py:func:`bitmath.parse_string_unsafe`.

All inputs are treated as **byte-based**. Bit-based units are not
Expand Down Expand Up @@ -524,7 +542,7 @@ bitmath.parse_string_unsafe()
.. function:: parse_string_unsafe(repr[, system=bitmath.NIST])

A deprecated thin wrapper around
``parse_string(repr, strict=False, system=system)``. All behaviour,
``parse_string(repr, strict=False, system=system)``. All behavior,
parameters, and caveats are identical to
:ref:`parse_string with strict=False <parse-string-non-strict>`.

Expand Down
46 changes: 46 additions & 0 deletions docsite/source/simple_examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,52 @@ Unit Conversion
>>> fourty_two_mib.KiB
KiB(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:

.. code-block:: python

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

.. code-block:: python

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

.. code-block:: python

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

.. code-block:: python

>>> convert_demo.GB
GB(2.048)

Because we aren't shifting between different base number systems.

Rich Comparison
***************

Expand Down
Loading