Python introduces two new builtins:
intu— a unit-aware integer (a subclass ofint)floatu— a unit-aware float (a subclass offloat)
Together, these are called u-numbers.
They behave like normal numbers but carry units, and arithmetic automatically respects those units.
U-numbers can be created either with literals or with constructors:
>>> 5cm
5cm
>>> 90deg
90deg
>>> intu(80, unit="cm")
80cmTaking the absolute value of a u-number preserves its unit.
>>> abs(-5m)
5mNegating a u-number preserves its unit.
>>> -5m
-5mApplying the unary plus operator does nothing and preserves the unit.
>>> +5m
5mBitwise inversion is only valid for intu. It preserves the unit.
>>> ~5count
-6countWhen two u-numbers with the same unit are added or subtracted, the result keeps that unit.
>>> 10s + 12s
22sWhen two u-numbers with different but compatible units are added or subtracted, the right operand is converted to the unit of the left operand.
>>> 6MiB - 6MB
0.27795410156 MiBWhen a u-number is added to or subtracted from a plain number, the unit is dropped and the result is a plain number.
>>> 10s + 12
22
>>> 10s + 0
10When a u-number is multiplied by a plain number, the result is a u-number with the same unit.
>>> 10s * 2
20sWhen two u-numbers are multiplied, their units are combined into a new derived unit.
>>> 5m * 2s
10(m*s)When a u-number is divided by a plain number, the result is a u-number with the same unit.
>>> 10m / 2
5mWhen a plain number is divided by a u-number, the result is a u-number with the reciprocal unit.
>>> 10 / 2s
5(1/s)When two u-numbers are divided, their units are divided as well. If the units cancel out, the result is a plain number.
>>> 10m / 2s
5(m/s)
>>> 10m / 2m
5Floor division works like true division, but the result is floored. Units divide in the same way.
>>> 7m // 2s
3(m/s)The modulo operator returns the remainder, and the result keeps the unit of the left operand.
>>> 7m % 2m
1mThe divmod function returns a tuple where the quotient is a plain number and the remainder keeps the unit.
>>> divmod(7m, 2m)
(3, 1m)When a u-number is raised to an integer power, the unit is raised to that power as well.
>>> 2m ** 2
4(m**2)When a u-number is raised to a negative integer power, the unit is inverted.
>>> (2m) ** -1
0.5(1/m)When a u-number is raised to a fractional power, the operation is only allowed if the unit is consistent. For example, the square root of an area is a length.
>>> 9(m**2) ** 0.5
3mWhen a plain number is raised to a u-number, the unit is ignored and the exponent is treated as a plain number.
>>> 2 ** 3m
8Bitwise operators are only valid for intu.
When two intu values with the same or compatible units are combined with &, |, or ^, the result keeps the unit.
>>> 5count & 3count
1count
>>> 10m & 6m
2mWhen a u-number is shifted left, the result is equivalent to multiplying by a power of two, and the unit is preserved.
>>> 1count << 3
8count
>>> 5m << 1
10mWhen a u-number is shifted right, the result is equivalent to floor-dividing by a power of two, and the unit is preserved.
>>> 8count >> 2
2countBitwise inversion preserves the unit.
>>> ~5count
-6countU-numbers with the same dimension can be compared after conversion.
>>> 1m == 100cm
True
>>> 1m > 50cm
TrueIf the units are incompatible, the comparison is always false.
>>> 1m == 1s
FalseU-numbers and plain numbers can only be compared if the unit is dimensionless.
>>> 5count == 5
TrueThe truth value of a u-number is False if its value is zero, and True otherwise.
>>> bool(0m)
False
>>> bool(3m)
TrueThe index function is only allowed if the unit is dimensionless. It returns a plain integer.
>>> index(5count)
5If the unit is not dimensionless, calling index raises a TypeError.
>>> index(5m)
TypeErrorOther sequence-related operators such as getitem, setitem, delitem, concat, contains, countOf, indexOf, and length_hint are not applicable to scalars and always raise TypeError.
Matrix multiplication is not defined for scalar u-numbers. Using @ or @= with scalars raises a TypeError.
>>> (5m) @ (2s)
TypeErrorFor matrices of u-numbers (for example, in NumPy), matrix multiplication is valid and units propagate through the dot product.
>>> [[1m, 2m]] @ [[3s], [4s]]
[[11(m*s)]]When two intu values are combined, the result is an intu if no fractional conversion is needed.
>>> 1m + 1m
2m # intuWhen an intu and a floatu are combined, the result is a floatu.
>>> 1m + 0.5m
1.5m # floatuAny operation that requires fractional conversion promotes the result to a floatu.
>>> 1m + 50cm
1.5m # floatuThe number 1 is unit-neutral in multiplication and division.
>>> 10s * 1
10s
>>> 10s / 1
10sDimensionless units such as rad or count behave like plain numbers in most contexts.
>>> 2rad + 3
5When a u-number is combined with an unsupported type, the operation returns NotImplemented. This allows the foreign object to handle the operation via its reflected method. If neither side supports the operation, Python raises a TypeError.
>>> 5m + "hello"
TypeError: unsupported operand type(s) for +: 'intu' and 'str'
>>> class Box:
... def __radd__(self, other):
... return f"Boxed {other}"
...
>>> 5m + Box()
'Boxed 5m'| Operation | u-number + u-number | u-number + plain | plain + u-number |
|---|---|---|---|
| Addition/Subtraction | convert to left unit | drop unit | drop unit |
| Multiplication | combine units | keep u-number unit | keep u-number unit |
| Division | divide units | keep u-number unit | reciprocal unit |
| Exponentiation | power units | power units | drop unit |
| Bitwise | preserve unit (intu only) | n/a | n/a |
Addition and subtraction with plain numbers drop units because conversions can force a change from intu to floatu.
For example, adding 10 MB and 12 MiB requires conversion, and the result is a floatu:
>>> 10MB + 12MiB
22.582912MB # floatuThis breaks the Liskov substitution principle: a function that promises to return an int could instead return a float.