Skip to content

Numbers

This page shows how numbers behave in d/Python. Whole numbers (integers) work exactly as they do in Python: they can be as large as you like, and division, remainders, powers and bit operations all give Python's answers. Decimal numbers (floats) are only partly there, and the Floats section says exactly what works and what does not yet. After reading this page you will know which arithmetic you can rely on today.

The integer code lives in a shared support library that the compiler links into your program only when the program needs it. Every operation on this page is checked against the reference CPython build (3.9.6) that every d/Python test is compared with.

Integers grow with the result

There is no fixed upper size. A result simply grows as large as it needs to be.

print(2 ** 100)
print(10 ** 30 + 1)
print(1 << 70, -256 >> 4)
value = 1
for i in range(1, 31):
    value *= i
print(value)

Division rounds down, the remainder takes the divisor's sign

print(7 // 2, -7 // 2, 7 // -2)
print(7 % 2, -7 % 2, 7 % -2)
print(divmod(-7, 2), divmod(7, -2))
try:
    print(1 // 0)
except ZeroDivisionError as error:
    print("caught:", error)

Dividing by zero raises ZeroDivisionError, an ordinary Python exception that you can catch, with the same message CPython prints. Ordinary division with / produces a float, and floats are not yet supported: the compiler stops at that line and tells you so. See the float boundary below.

Powers

print(pow(2, 100))
print(pow(7, 1000000000000000000000123, 97))
print(pow(7, 13, -19), pow(2, 10, None))
print(3 ** 4, (-2) ** 3, 2 ** 0)

** and two-argument pow take an exponent of zero or more. Three-argument pow(base, exponent, modulus) takes a positive or negative modulus that is not zero, and it reduces the result at every step, so a huge exponent with a small modulus stays small and fast. A modulus of 1 or -1 gives 0. A negative exponent would need either a float result or a modular inverse, and neither is supported yet: the program raises NotImplementedError at that point, and the message says which piece is missing.

Bits

print(5 & 3, 5 | 3, 5 ^ 3, ~5)
print(bin(-10), oct(8), hex(255))
print(True & True, True | False, True ^ True)

Bit operations on negative numbers behave as in Python, where a negative number acts as if it had an endless run of one bits on the left. &, | and ^ on two booleans give a boolean, as in Python. Shifting by a negative count is an error, and a right shift by an enormous count works correctly instead of the count being cut down to a smaller number. bin, oct and hex keep their prefixes and the minus sign for any integer.

Conversions

print(int("42"), int("-0x1f", 16), int("1_000"), int("  12  "), int(b"7"))
print(int("١٢٣"), int("z", 36), int(True), int(False))
try:
    int("x1")
except ValueError as error:
    print("ValueError:", error)
print(str(2 ** 64), repr(-1), abs(-3), bool(0), bool(7))

int() reads text and bytes in any base from 2 to 36, or base 0, which reads the prefix to choose the base. It accepts a sign, underscores between digits, surrounding spaces, and digits from any script that Unicode counts as decimal. Text that is not a number raises ValueError, which you can catch. Converting a float to an integer, and converting objects of your own classes to integers, are not yet supported.

Booleans are integers

True counts as 1 and False as 0 wherever arithmetic asks for a number, as in Python.

print(True + True, True * 3, sum([True, False, True]))
print(3 > 2 > 1, 1 < 2 < 3 < 4, True == 1)

Reductions

print(sum(range(101)), sum([1, 2, 3], 10))
print(min(3, 1, 2), max([4, 9, 2]), min([], default=0))
print(sorted([3, -1, 2], key=abs), max(["pear", "fig"], key=len))

sum adds up numbers or a list of numbers, with an optional starting value. min and max take several arguments or one collection, accept a default for an empty collection, and accept a key function, which may be a closure or an object with a __call__ method. When two items tie, the first one wins, as in Python.

Floats

Floats are the biggest thing on this page that is not yet supported, and the compatibility table records that. Here is where they stand today. A literal such as 1.5 works, and so do +, -, * and comparisons between two floats, as long as the result fits what the number storage of the d/OS runtime, the program that runs your compiled code, can hold. float(), division with /, round(), formatting a float and printing a float are not yet supported: the compiler stops at that line and says so.

The reason is one of d/Python's promises. The runtime's number handling was inherited from QuickBASIC, and Python's float behaviour must be built and checked against the reference CPython build before it is switched on, rather than borrowing BASIC's behaviour to get code out the door. If you are doing d/OS work today, the dos module's f32 and f64 buffers and its MATH intents (an intent is a named request, such as "add these numbers", that the machine carries out however it can) already carry double-precision numbers; see The dos Module.

What the tests cover

Every claim on this page comes from a test program that runs in both d/Python and CPython and must print the same thing. The corpus page lists those programs, from arithmetic and divmod through bit operations, conversions and reductions, each with its reviewed output and its result in this browser build.