Skip to content

Instantly share code, notes, and snippets.

@johnslavik
Last active August 16, 2025 20:03
Show Gist options
  • Select an option

  • Save johnslavik/9effb3c11df410248fca9c615187bb45 to your computer and use it in GitHub Desktop.

Select an option

Save johnslavik/9effb3c11df410248fca9c615187bb45 to your computer and use it in GitHub Desktop.
CPython with native units

Native units for Python

Python introduces two new builtins:

  • intu — a unit-aware integer (a subclass of int)
  • floatu — a unit-aware float (a subclass of float)

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")
80cm

1. Core arithmetic

Unary operators

Taking the absolute value of a u-number preserves its unit.

>>> abs(-5m)
5m

Negating a u-number preserves its unit.

>>> -5m
-5m

Applying the unary plus operator does nothing and preserves the unit.

>>> +5m
5m

Bitwise inversion is only valid for intu. It preserves the unit.

>>> ~5count
-6count

Addition and subtraction

When two u-numbers with the same unit are added or subtracted, the result keeps that unit.

>>> 10s + 12s
22s

When 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 MiB

When 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
10

Multiplication and division

When a u-number is multiplied by a plain number, the result is a u-number with the same unit.

>>> 10s * 2
20s

When 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
5m

When 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
5

Floor 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
1m

The divmod function returns a tuple where the quotient is a plain number and the remainder keeps the unit.

>>> divmod(7m, 2m)
(3, 1m)

Exponentiation

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
3m

When a plain number is raised to a u-number, the unit is ignored and the exponent is treated as a plain number.

>>> 2 ** 3m
8

2. Bitwise operators

Bitwise 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
2m

When 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
10m

When 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
2count

Bitwise inversion preserves the unit.

>>> ~5count
-6count

3. Comparisons and truthiness

U-numbers with the same dimension can be compared after conversion.

>>> 1m == 100cm
True
>>> 1m > 50cm
True

If the units are incompatible, the comparison is always false.

>>> 1m == 1s
False

U-numbers and plain numbers can only be compared if the unit is dimensionless.

>>> 5count == 5
True

The truth value of a u-number is False if its value is zero, and True otherwise.

>>> bool(0m)
False
>>> bool(3m)
True

4. Indexing and sequence-related operators

The index function is only allowed if the unit is dimensionless. It returns a plain integer.

>>> index(5count)
5

If the unit is not dimensionless, calling index raises a TypeError.

>>> index(5m)
TypeError

Other sequence-related operators such as getitem, setitem, delitem, concat, contains, countOf, indexOf, and length_hint are not applicable to scalars and always raise TypeError.


5. Matrix multiplication

Matrix multiplication is not defined for scalar u-numbers. Using @ or @= with scalars raises a TypeError.

>>> (5m) @ (2s)
TypeError

For 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)]]

6. Type promotion and special cases

When two intu values are combined, the result is an intu if no fractional conversion is needed.

>>> 1m + 1m
2m   # intu

When an intu and a floatu are combined, the result is a floatu.

>>> 1m + 0.5m
1.5m   # floatu

Any operation that requires fractional conversion promotes the result to a floatu.

>>> 1m + 50cm
1.5m   # floatu

The number 1 is unit-neutral in multiplication and division.

>>> 10s * 1
10s
>>> 10s / 1
10s

Dimensionless units such as rad or count behave like plain numbers in most contexts.

>>> 2rad + 3
5

7. Foreign objects

When 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'

8. Summary table

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

9. Notes on Liskov substitution

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  # floatu

This breaks the Liskov substitution principle: a function that promises to return an int could instead return a float.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment