# The Built-In `bin()`, `oct()` and `hex()` Functions

by Christoph Schiessl on Python

Today, I want to explore three built-ins at once: the `bin()`, the `oct()`, and the `hex()` functions. It makes sense to talk about all three at once because they are very similar, and if you learn about one, then your knowledge easily transfers to the other two.

All three take a single parameter, which must be an `int` object or interpretable as an `int` if it is a different type — we get to details of the conversion logic later.

``````Python 3.12.3 (main, Apr 21 2024, 14:06:33) [GCC 13.2.1 20230801] on linux
>>> bin(255), bin(-255)
('0b11111111', '-0b11111111')
>>> oct(255), oct(-255)
('0o377', '-0o377')
>>> hex(255), hex(-255)
('0xff', '-0xff')
``````

As you can see, `bin()` returns a string that is a binary number (i.e., a number that uses two distinct digits), `oct()` returns a string that is an octal number (i.e., a number that uses eight distinct digits), and finally, `hex()` returns a string that is a hexadecimal number (i.e., a number that uses sixteen distinct digits).

Note that we only have ten digits — 0 through 9 — available, but we need sixteen for hexadecimal numbers. Hence, these hexadecimal numbers use the letters a through f to denote the remaining six digits.

Furthermore, all returned strings are prefixed with `0b`, `0o`, or `0x`, making them valid Python expressions. For negative integers, the minus sign goes before the prefix.

``````Python 3.12.3 (main, Apr 21 2024, 14:06:33) [GCC 13.2.1 20230801] on linux
>>> 0b11111111 == 255
True
>>> -0b11111111 == -255
True
>>> 0o377 == 255
True
>>> -0o377 == -255
True
>>> 0xff == 255
True
>>> -0xff == -255
True
``````

Decimal numbers use ten distinct digits; therefore, they are also called base 10 numbers. Now, look at a decimal number like `255` and read it from the right to the left — which is, read from the least significant to the most significant digit. Then the right-most digit represents `5`, the digit in the middle represents `50`, and the left-most digit represents `200`. To get the final number, you calculate the sum: `5 + 50 + 200 == 255`.

A more generic way to look at this is the following: The right-most digit represents `5 * 10 ** 0 == 5 * 1 == 5`, the digit in the middle represents `5 * 10 ** 1 == 5 * 10 == 50`, and the left-most digit represents `2 * 10 ** 2 == 2 * 100 == 200`. So, the exponent starts with zero for the least significant digit and is then incremented each time you parse the next, more significant digit.

I'm using a 10 in this calculation because, so far, we have been talking about a base 10 number. However, the same calculation also applies to numbers with a different base. Generally speaking, for a base n number, you would replace the 10 with n in the calculation above.

We have already established that binary numbers use two distinct digits, so they are base 2. Similarly, octal numbers are base 8, and hexadecimal numbers are base 16 because they use eight and sixteen distinct digits, respectively.

Take, for example, the octal number `377`. If you have to parse this by hand, you would go from left to right and calculate as follows: `7 * 8 ** 0 == 7 * 1 == 7`, `7 * 8 ** 1 == 7 * 8 == 56`, and `3 * 8 ** 2 == 3 * 64 == 192`. This gives a final number of `7 + 56 + 192 == 255`.

Again, this calculation is not specific to any number system. If you wanted to, you could invent a base 42 number ;)

## Conversion of objects other than `int`

I mentioned before that the parameter of `bin()`, `oct()`, and `hex()` must be an `int` or interpretable as an `int`. Well, most objects are not losslessly convertible to an `int`.

``````Python 3.12.3 (main, Apr 20 2024, 16:22:09) [GCC 13.2.1 20230801] on linux
>>> bin(object())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'object' object cannot be interpreted as an integer
>>> oct(1.6) # not using normal int conversion!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'float' object cannot be interpreted as an integer
``````

Ordinary integer conversion, if available, is lossless because the digits after the floating point are truncated (i.e., `int(1.6) == 1`). Instead, a special procedure normally used for indexing is utilized. As it turns out, the `operator` module defines an `index()` operator that delegates to the object's magic `__index__()` method behind the scenes. This magic method, if implemented, is supposed to provide the ability to do lossless conversion. We can easily demonstrate this:

``````Python 3.12.3 (main, Apr 20 2024, 16:22:09) [GCC 13.2.1 20230801] on linux
>>> import operator
>>> operator.index(object()) # doesn't work with arbitrary objects
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'object' object cannot be interpreted as an integer
>>> operator.index(1.6)      # doesn't work with floats
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'float' object cannot be interpreted as an integer
>>> '__index__' in dir(255)
True
>>> operator.index(255)      # works with ints that implement __index__()
255
>>> class Foo:
...   def __index__(self) -> int:
...     return 255
...
>>> operator.index(Foo())    # works with custom objects that implement __index__()
255
``````

Last but not least, we can also demonstrate all of this together:

``````Python 3.12.3 (main, Apr 20 2024, 16:22:09) [GCC 13.2.1 20230801] on linux
>>> class Foo:
...   def __index__(self):
...     print("Foo.__index__() has been called!")
...     return 255
...
>>> bin(Foo())
Foo.__index__() has been called!
'0b11111111'
>>> oct(Foo())
Foo.__index__() has been called!
'0o377'
>>> hex(Foo())
Foo.__index__() has been called!
'0xff'
``````

That's everything for today. Thank you for reading, and see you soon!

### Hi, I'm Christoph Schiessl.

I'm available for hire as a freelance web developer, so you can take advantage of my more than a decade of experience working on many projects across several industries. Most of my clients are building web-based SaaS applications in a B2B context and depend on my expertise in various capacities.

More often than not, my involvement includes hands-on development work using technologies like Python, JavaScript, and PostgreSQL. Furthermore, if you already have an established team, I can support you as a technical product manager with a passion for simplifying complex processes. Lastly, I'm an avid writer and educator who takes pride in breaking technical concepts down into the simplest possible terms.

#### Here are a few more Articles for you ...

The Built-In `chr()` and `ord()` Functions

Discover Python's built-in functions `chr()` and `ord()` for handling Unicode characters and converting between integers and characters.

By Christoph Schiessl on Python

Function Definition Basics

Explore Python's function definition statement and discover its features with this series of articles. Get started with this simple introduction.

By Christoph Schiessl on Python

The Built-In `sum()` Function

In this article, we explore Python's built-in `sum()` function, its parameters, and some extreme use cases it wasn't even designed for.

By Christoph Schiessl on Python