Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Elixir & Erlang/OTP  Module 2 – Values and types

Numbers: integers, floats and arithmetic

Elixir integer and float arithmetic: which operators return floats, div and rem against floor_div, mod and ceil_div, float rounding and money in paise.

  • Beginner
  • 15 minutes
  • Examples run with elixir 1.20.4, erlang 29.1.1 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Predict whether an Elixir arithmetic expression returns an integer or a float
  • Choose between div/rem, Integer.floor_div/mod and Integer.ceil_div when a number is negative
  • Explain why Float.round(2.675, 2) returns 2.67, and compare floats without ==
  • Store money as whole paise and parse numbers from text with Integer.parse/2
  • Write integers in binary, octal and hex, and combine bits with the Bitwise module

Before you start

What you’ll learn · Before you start

What you will learn

  • Predict whether an Elixir arithmetic expression returns an integer or a float
  • Choose between div/rem, Integer.floor_div/mod and Integer.ceil_div when a number is negative
  • Explain why Float.round(2.675, 2) returns 2.67, and compare floats without ==
  • Store money as whole paise and parse numbers from text with Integer.parse/2
  • Write integers in binary, octal and hex, and combine bits with the Bitwise module

Before you start

On this page

In Elixir, 2 ** 100 prints all 31 digits of 1,267,650,600,228,229,401,496,703,205,376, while 0.1 + 0.2 prints 0.30000000000000004 and div(-7, 2) is −3 where Integer.floor_div(-7, 2) is −4. Each answer is right for its type and its rounding rule, and confusing two of those rules is how a bill comes out one paisa short.

Below, you will predict which operators return integers and which return floats, pick the integer division that a clock or a page count needs, see why Float.round(2.675, 2) gives 2.67, keep money exact in whole paise, parse numbers out of text and work with bits. Every result on this page was printed by Elixir 1.20.4 running on Erlang/OTP 29.1.1.

Integers have no fixed size, and floats are 64-bit

Type a few expressions into IEx and watch the type of each answer: whole numbers stay whole, and one float anywhere makes the result a float.

Integers and floats in IEx Python · numbers_session.py
from session import shell

# Integers and floats in IEx: which operators give which type, and what happens past the largest float.
shell([
    "2 ** 100",
    "0.1 + 0.2",
    "10 / 2",
    "div(10, 2)",
    "3 * 2.0",
    "2 ** -1",
    "1.0e308 * 10",
    "1_000_000 + 2.5e3",
    "System.halt()",
])

Output

$ iex
Erlang/OTP 29 [erts-17.1] [source] [64-bit] [smp:18:18] [ds:18:18:10] [async-threads:1] [jit]

Interactive Elixir (1.20.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 2 ** 100
1267650600228229401496703205376
iex(2)> 0.1 + 0.2
0.30000000000000004
iex(3)> 10 / 2
5.0
iex(4)> div(10, 2)
5
iex(5)> 3 * 2.0
6.0
iex(6)> 2 ** -1
0.5
iex(7)> 1.0e308 * 10
** (ArithmeticError) bad argument in arithmetic expression: 1.0e308 * 10
    :erlang.*(1.0e308, 10)
    iex:7: (file)
iex(7)> 1_000_000 + 2.5e3
1002500.0
iex(8)> System.halt()

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 numbers_session.py

2 ** 100 came back with every digit. An Elixir integer: A whole number such as 42 or -7. Elixir integers have no fixed size: one that outgrows a machine word moves to a larger representation, so integer arithmetic never overflows. has no fixed size: on a 64-bit machine the VM keeps an integer of up to 60 bits in one memory word, and a larger one in at least three words, as Erlang’s Efficiency Guide lists, without your program noticing the switch. A result never wraps around to a wrong value as a fixed-size integer does: the integer simply grows.

A float: A number stored as a 64-bit binary floating-point value, such as 0.5 or 2.5e3. Most decimal fractions are stored as the nearest binary fraction, so 0.1 + 0.2 gives 0.30000000000000004. is different. The Elixir guide says that “Floats in Elixir are 64-bit precision”, and most decimal fractions have no exact 64-bit binary form, so 0.1 and 0.2 are stored as the nearest binary fractions and their sum prints as 0.30000000000000004. The section on rounding below shows what that does to money.

Three rules decide the type of a result:

  • +, - and * return an integer when both sides are integers, and a float as soon as one side is a float: 3 * 2.0 is 6.0.
  • / always returns a float, even when the division comes out even: 10 / 2 is 5.0. For a whole-number quotient use div/2, which returned 5.
  • ** returns an integer only when both sides are integers and the exponent is 0 or more, so 2 ** -1 is 0.5.

Erlang’s floats break one habit from other languages: the reference manual says they “do not exactly match IEEE 754 floats, in that neither Inf nor NaN are supported”. So 1.0e308 * 10, which is infinity in many languages, raised ArithmeticError, and IEx went on at the same prompt number. Number literals may also put underscores between digits, 1_000_000, and a float may carry an exponent: 2.5e3 is 2,500.0, which is why the last line gave a float.

Checkpoint

Check: Integers, floats and the type of a result

  1. Question 1 of 2 Which of these expressions returns an integer?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: div(10, 2)

    +, - and * return an integer only when both sides are integers, / always returns a float, and ** returns an integer only for integers with an exponent of 0 or more. Of the four, only div(10, 2) stays an integer.

  2. Question 2 of 2 What does 1.0e308 * 10 do in Elixir?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It raises ArithmeticError

    The BEAM's floats do not support Inf or NaN. Any operation that would produce one raises ArithmeticError, so 1.0e308 * 10 stops with "bad argument in arithmetic expression".

Which way does integer division round?

Dividing 7 by 2 has the exact answer 3.5, so an integer division must round, and once a number is negative there are three directions to round in. Elixir 1.20 has a function for each. This program prints all five functions for the same four pairs of numbers, with the signs changed:

division.exs: five divisions, four pairs Elixir · division.exs
# The five integer divisions of Elixir 1.20 on the same four pairs: the signs change, the rounding shows.
pairs = [{7, 2}, {-7, 2}, {7, -2}, {-7, -2}]

heads = ["a", "b", "div", "rem", "floor_div", "mod", "ceil_div"]
IO.puts(Enum.map_join(heads, "", &String.pad_leading(&1, 10)))

for {a, b} <- pairs do
  row = [a, b, div(a, b), rem(a, b), Integer.floor_div(a, b), Integer.mod(a, b), Integer.ceil_div(a, b)]
  IO.puts(Enum.map_join(row, "", &String.pad_leading(Integer.to_string(&1), 10)))
end

# Each pair of quotient and remainder rebuilds a: a == q * b + r.
truncated = Enum.all?(pairs, fn {a, b} -> div(a, b) * b + rem(a, b) == a end)
floored = Enum.all?(pairs, fn {a, b} -> Integer.floor_div(a, b) * b + Integer.mod(a, b) == a end)
IO.puts("div * b + rem == a: #{truncated}")
IO.puts("floor_div * b + mod == a: #{floored}")

# Two everyday uses: the hour 5 hours before 02:00, and pages of 10 for 101 results.
IO.puts("rem(2 - 5, 24) = #{rem(2 - 5, 24)}")
IO.puts("Integer.mod(2 - 5, 24) = #{Integer.mod(2 - 5, 24)}")
IO.puts("Integer.ceil_div(101, 10) = #{Integer.ceil_div(101, 10)}")
Truncated, floored and ceiled division side by side Python · division_session.py
from session import run

run("""
elixir division.exs
""")

Output

$ elixir division.exs
         a         b       div       rem floor_div       mod  ceil_div
         7         2         3         1         3         1         4
        -7         2        -3        -1        -4         1        -3
         7        -2        -3         1        -4        -1        -3
        -7        -2         3        -1         3        -1         4
div * b + rem == a: true
floor_div * b + mod == a: true
rem(2 - 5, 24) = -3
Integer.mod(2 - 5, 24) = 21
Integer.ceil_div(101, 10) = 11

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 division_session.py

Read the rows with a negative number. The exact quotient of −7 by 2 is −3.5, and:

  • div/2 rounds towards zero and gives −3: this is truncated division: Integer division that rounds the quotient towards zero, as div/2 does: div(-7, 2) is -3. Its remainder, from rem/2, has the sign of the number being divided.. Its partner rem/2 gives the remainder that goes with it, −1, which always has the sign of the dividend a.
  • Integer.floor_div/2 rounds down, towards negative infinity, and gives −4: this is floored division: Integer division that rounds the quotient down, towards negative infinity, as Integer.floor_div/2 does: -7 by 2 gives -4. Its remainder, from Integer.mod/2, has the sign of the divisor.. Its partner Integer.mod/2 gives 1, which always has the sign of the divisor b.
  • Integer.ceil_div/2 rounds up, towards positive infinity: −3 here, and 4 for 7 by 2.

When both numbers are positive, div agrees with floor_div, and rem with mod; they part ways only when a sign is negative. Each quotient belongs with its own remainder, which the two true lines check: q * b + r gives back a only when q and r come from the same family.

The last three lines are why the choice matters. Five hours before 02:00 a clock reads 21:00. Integer.mod(2 - 5, 24) gives 21, while rem(2 - 5, 24) gives −3, an hour that does not exist. And 101 search results at 10 a page need 11 pages, which Integer.ceil_div(101, 10) returns without a detour through floats.

rem/2 for clock and calendar arithmetic

rem(2 - 5, 24) is −3, because rem/2 keeps the sign of the number being divided. Whenever the answer must lie between 0 and the divisor, as an hour, a weekday or a position in a ring does, use Integer.mod/2, which gave 21.

So choose by the direction the answer must round in. The table puts the three families side by side, with what each gave for −7 by 2:

You need Use −7 by 2
the quotient rounded towards zero, and its remainder div/2 and rem/2 −3 and −1
the quotient rounded down, and a remainder from 0 up to the divisor Integer.floor_div/2 and Integer.mod/2 −4 and 1
a count of groups that leaves nothing out: pages, boxes, buses Integer.ceil_div/2 −3

Key idea

Choose the division by the way it must round: div and rem towards zero, floor_div and mod downwards, ceil_div upwards, and keep each quotient with its own remainder.

Checkpoint

Check: Truncated, floored and ceiled division

  1. Question 1 of 2 A clock shows 01:00. What does Integer.mod(1 - 4, 24), the hour four hours earlier, return?

    Type a number.

    A hint

    Integer.mod/2 gives a result from 0 up to the divisor, 24 here.

    Show the answer: Question 1 of 2

    Answer: 21

    1 - 4 is -3, and Integer.mod/2 rounds the quotient down, so its remainder has the sign of the divisor and lands between 0 and 23: 21, which is 21:00. rem(1 - 4, 24) would give -3, an hour that does not exist.

  2. Question 2 of 2 A course has 47 lessons, and a learner does 10 a week. Which call gives the number of weeks needed?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Integer.ceil_div(47, 10)

    A count of groups that must leave nothing out, such as weeks, pages or boxes, rounds up: Integer.ceil_div(47, 10) is 5, the same as Integer.ceil_div(101, 10) giving 11 pages for 101 results.

Float.round/2 rounds the stored float, not the digits you typed

Float.round(2.675, 2) looks like a halfway case that should round up to 2.68. It returns 2.67, while Float.round(2.665, 2), another halfway case, rounds up:

Rounding floats in IEx Python · rounding_session.py
from session import shell

# Rounding floats: what Float.round/2 sees, and the whole-number functions of Kernel.
shell([
    "Float.round(2.675, 2)",
    "Float.round(2.665, 2)",
    ":erlang.float_to_binary(2.675, decimals: 20)",
    ":erlang.float_to_binary(2.665, decimals: 20)",
    "{round(2.5), round(-2.5), trunc(-2.7), floor(-2.7), ceil(-2.7)}",
    "0.1 + 0.2 == 0.3",
    "abs(0.1 + 0.2 - 0.3) < 1.0e-9",
    "1.0e9 + 1.0e-9 == 1.0e9",
    "System.halt()",
])

Output

$ iex
Erlang/OTP 29 [erts-17.1] [source] [64-bit] [smp:18:18] [ds:18:18:10] [async-threads:1] [jit]

Interactive Elixir (1.20.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Float.round(2.675, 2)
2.67
iex(2)> Float.round(2.665, 2)
2.67
iex(3)> :erlang.float_to_binary(2.675, decimals: 20)
"2.67499999999999982236"
iex(4)> :erlang.float_to_binary(2.665, decimals: 20)
"2.66500000000000003553"
iex(5)> {round(2.5), round(-2.5), trunc(-2.7), floor(-2.7), ceil(-2.7)}
{3, -3, -2, -3, -2}
iex(6)> 0.1 + 0.2 == 0.3
false
iex(7)> abs(0.1 + 0.2 - 0.3) < 1.0e-9
true
iex(8)> 1.0e9 + 1.0e-9 == 1.0e9
true
iex(9)> System.halt()

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rounding_session.py

Printing each float with 20 decimals, as :erlang.float_to_binary/2 did, shows why. The float nearest to 2.675 is 2.67499999999999982236, a little below halfway, and the one nearest to 2.665 is 2.66500000000000003553, a little above. The documentation of Float.round/2 says the rounding “is performed on the binary floating point, without a conversion to decimal”, so each rounds the way its stored value points. Step through the first case:

Interactive · stage by stage Float.round(2.675, 2): from the digits you typed to 2.67 Step through the four stages. The value that gets rounded is the float in memory, not the digits in the source.
Float.round(2.675, 2): from the digits you typed to 2.67, step 5 of 5. Showing You write 2.675, The nearest 64-bit float, Float.round looks at it, The result is 2.67 (rounded up for 2.665). In focus: The nearest 64-bit float, The result is 2.67.2.665 is stored above halfYou write 2.675between 2.67 and 2.68The nearest 64-bit float2.67499999999999982236…Float.round looks at itthe next digit is a 4The result is 2.67rounded up for 2.6652.665… rounds up

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The digits. The source says 2.675. Read as a decimal it sits exactly halfway between 2.67 and 2.68, so a rule that rounds halves up would give 2.68.
  2. The stored float. 2.675 has no exact binary form, so the compiler stores the 64-bit float nearest to it. Printed with 20 decimals, that float is 2.67499999999999982236, a little below 2.675.
  3. Rounding the stored value. Float.round/2 works on the binary value, without turning it back into decimal digits. At two decimals the digit that decides is the third, and in the stored value it is a 4.
  4. Rounded down. Below halfway means down, so Float.round(2.675, 2) returns 2.67. No rule was broken; the halfway number never reached the function.
  5. The opposite case. 2.665 is stored as 2.66500000000000003553, a little above halfway, so Float.round(2.665, 2) rounds up to 2.67. Two halfway cases went two ways, each decided by its stored float.

Both results come from the stored floats, as the last step of the figure showed: two halfway numbers, two stored values, two directions. The functions that return whole numbers have rules of their own, as the fifth line showed. round/1 rounds halves away from zero, so round(2.5) is 3 and round(-2.5) is −3. trunc/1 drops the fraction, so trunc(-2.7) is −2. floor/1 rounds down to −3 and ceil/1 up to −2.

The same stored values make 0.1 + 0.2 == 0.3 false, as the sixth line showed, and that catches out every test that compares a computed float with ==:

Comparing computed floats with ==

A check such as total == 0.3 fails although the arithmetic looks right. Compare two computed floats within a tolerance instead: abs(0.1 + 0.2 - 0.3) < 1.0e-9 was true. Or keep the values as integers, as the next section does for money.

How close is close enough depends on the size of the numbers. A tolerance of 1.0e-9 suits values near 1, but at a billion it is smaller than the gap between two neighbouring floats: the eighth line showed that adding 1.0e-9 to 1.0e9 changes nothing at all. For money, the next section avoids the question altogether.

IEEE 754 Floating-Point Converter Enter 2.675 and see the 64-bit float it is stored as, bit by bit.

Key idea

A float is the nearest binary fraction to the number you wrote, and every operation on it, Float.round/2 included, works on that stored value, so never expect exact decimal answers from floats.

Keep money in whole paise, and round once at the edge

A canteen bill has four items, at 19.99, 4.35, 1.10 and 2.20 rupees. This program adds them as floats in rupees, then as whole numbers of paise, the hundredths of a rupee:

paise.exs: one bill, two ways Elixir · paise.exs
# A bill of four items, totalled two ways: as floats in rupees, and as whole paise.
defmodule Money do
  # 2764 paise -> "₹27.64": the only place where paise become a decimal number again.
  def format(paise) do
    rupees = div(paise, 100)
    paisa = String.pad_leading(Integer.to_string(rem(paise, 100)), 2, "0")
    "₹#{rupees}.#{paisa}"
  end
end

IO.puts("As floats in rupees")
IO.puts("  19.99 + 4.35 + 1.10 + 2.20 = #{19.99 + 4.35 + 1.10 + 2.20}")
IO.puts("  19.99 * 100 = #{19.99 * 100}")
IO.puts("  trunc(19.99 * 100) = #{trunc(19.99 * 100)}")
IO.puts("  ten items at 0.10 = #{Enum.sum(List.duplicate(0.10, 10))}")

IO.puts("As whole paise")
total = 1999 + 435 + 110 + 220
IO.puts("  1999 + 435 + 110 + 220 = #{total}")
IO.puts("  ten items at 10 paise = #{Enum.sum(List.duplicate(10, 10))}")
IO.puts("  shown to the customer: #{Money.format(total)}")
Floats in rupees against whole paise Python · paise_session.py
from session import run

run("""
elixir paise.exs
""")

Output

$ elixir paise.exs
As floats in rupees
  19.99 + 4.35 + 1.10 + 2.20 = 27.639999999999997
  19.99 * 100 = 1998.9999999999998
  trunc(19.99 * 100) = 1998
  ten items at 0.10 = 0.9999999999999999
As whole paise
  1999 + 435 + 110 + 220 = 2764
  ten items at 10 paise = 100
  shown to the customer: ₹27.64

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 paise_session.py

Three things went wrong with floats. The total printed as 27.639999999999997. Converting 19.99 rupees to paise with trunc(19.99 * 100) gave 1998, one paisa short, because the product was 1998.9999999999998 and trunc dropped the fraction. And ten items at 0.10 added up to 0.9999999999999999, not 1. The integer version was exact every time: 2,764 paise, which Money.format/1 turned into 27.64 rupees only for the person reading the bill.

So store money as a whole number of its minor unit: The smallest unit of a currency, such as the paisa of the rupee or the cent of the dollar. Keeping amounts as whole numbers of it makes sums of money exact.: paise for rupees, cents for dollars. Add, subtract and compare those integers, and turn them into a decimal string only to show them. When a calculation does produce a fraction of a paisa, such as a 5 % discount on 1,999 paise, which is 99.95 paise, choose the rounding rule once, write it down, and apply it with integer arithmetic at that one place. Split.shares/2, the practice at the end of this lesson, is that kind of rule.

Some work needs exact decimals with many places, such as interest rates. The Decimal package on Hex, a third-party library outside the standard library, describes itself as “Arbitrary precision decimal arithmetic” and keeps every digit it is given.

Key idea

Keep money as integers of the smallest unit, and convert to rupees with a decimal point only at the edge, when the amount is shown.

Checkpoint

Check: Rounding the stored float · Money in whole paise

  1. Question 1 of 2 Float.round(2.675, 2) and Float.round(2.665, 2) both return 2.67. Which explanation fits both results?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Each rounds its stored float, and the float for 2.675 is just below halfway while the one for 2.665 is just above

    Neither number exists as a 64-bit float. The compiler stores the nearest float, which is a hair below 2.675 and a hair above 2.665, and Float.round/2 rounds those stored values, so both come out as 2.67.

  2. Question 2 of 2 Why did trunc(19.99 * 100) return 1998 instead of 1999?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 19.99 * 100 is 1998.9999999999998 as a float, and trunc drops the fraction

    Converting rupees held in a float to paise with trunc(price * 100) loses a paisa whenever the stored float is a little below the price, as 19.99 and 4.35 are. Keeping the amount as 1999 paise from the start avoids the conversion.

Integer.parse/2 tells you what it could not read

Numbers often arrive as text: a form field, a line of a file, a command-line argument. Integer.parse/2 reads as many digits as it can from the start of the text and hands back the rest:

Parsing numbers from text in IEx Python · parse_session.py
from session import shell

# Turning text into numbers: what Integer.parse/2 returns, and the text it leaves over.
shell([
    'Integer.parse("42")',
    'Integer.parse("12abc")',
    'Integer.parse("abc")',
    'Integer.parse("1_000")',
    'Integer.parse("42\\n")',
    'String.to_integer("12abc")',
    'Float.parse("3.14 kg")',
    'Integer.parse("ff", 16)',
    "System.halt()",
])

Output

$ iex
Erlang/OTP 29 [erts-17.1] [source] [64-bit] [smp:18:18] [ds:18:18:10] [async-threads:1] [jit]

Interactive Elixir (1.20.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Integer.parse("42")
{42, ""}
iex(2)> Integer.parse("12abc")
{12, "abc"}
iex(3)> Integer.parse("abc")
:error
iex(4)> Integer.parse("1_000")
{1, "_000"}
iex(5)> Integer.parse("42\n")
{42, "\n"}
iex(6)> String.to_integer("12abc")
** (ArgumentError) errors were found at the given arguments:

  * 1st argument: not a textual representation of an integer

    :erlang.binary_to_integer("12abc")
    iex:6: (file)
iex(6)> Float.parse("3.14 kg")
{3.14, " kg"}
iex(7)> Integer.parse("ff", 16)
{255, ""}
iex(8)> System.halt()

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 parse_session.py

It returned {42, ""} for “42” and {12, "abc"} for “12abc”: it found the number 12 and stopped at the first character that was not a digit. Text with no digits at the start, such as “abc”, gives :error. Two results surprise people. “1_000” gives {1, "_000"}, because underscores are allowed in the number literals of your source code, not in the text you parse. And “42\n”, a line read from a file or the keyboard with its newline, leaves “\n” over; trim the line first with String.trim/1.

So when the whole text must be a number, accept the result only when the text left over is the empty string, "". The pattern-matching module of this track shows the usual way to write that check. String.to_integer/1 is the strict alternative: it raised ArgumentError for “12abc”. Use it when bad input would be a bug in your own program, and Integer.parse/2 when bad input is expected and you want to tell the user what was wrong. Float.parse/1 works the same way, {3.14, " kg"}, and Integer.parse/2 takes a base as its second argument: Integer.parse("ff", 16) is {255, ""}.

Keeping the number and ignoring the rest

Taking the 12 out of {12, "abc"} and moving on accepts “12abc”, “12 apples” and “12.5” as 12. Check that the text left over is "", and treat anything else as invalid input.

Integers in other bases, and the Bitwise module

Integers can be written in binary, octal or hexadecimal with the prefixes 0b, 0o and 0x, and with underscores for readability. The Bitwise module works on the bits of an integer. Its functions can be called with the module name, as in Bitwise.band(12, 10), and import Bitwise, as the second line does, lets you write them and its operators, such as <<<, without it:

Literals in other bases, and bit operations Python · bits_session.py
from session import shell

# Integer literals in other bases, and the bit operations of the Bitwise module.
shell([
    "{0b1010, 0o17, 0xFF, 1_000_000}",
    "import Bitwise",
    "{band(0b1100, 0b1010), bor(0b1100, 0b1010), bxor(0b1100, 0b1010)}",
    "{1 <<< 10, 1024 >>> 3}",
    "Integer.popcount(255)",
    "Integer.to_string(10, 2)",
    "System.halt()",
])

Output

$ iex
Erlang/OTP 29 [erts-17.1] [source] [64-bit] [smp:18:18] [ds:18:18:10] [async-threads:1] [jit]

Interactive Elixir (1.20.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> {0b1010, 0o17, 0xFF, 1_000_000}
{10, 15, 255, 1000000}
iex(2)> import Bitwise
Bitwise
iex(3)> {band(0b1100, 0b1010), bor(0b1100, 0b1010), bxor(0b1100, 0b1010)}
{8, 14, 6}
iex(4)> {1 <<< 10, 1024 >>> 3}
{1024, 128}
iex(5)> Integer.popcount(255)
8
iex(6)> Integer.to_string(10, 2)
"1010"
iex(7)> System.halt()

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 bits_session.py

0b1010 is 10, 0o17 is 15 and 0xFF is 255: three spellings of ordinary integers, chosen for the reader. With import Bitwise, band/2 keeps the bits set in both numbers (12 and 10 share only the bit worth 8), bor/2 the bits set in either (14), and bxor/2 the bits set in exactly one (6). <<< and >>> shift bits left and right, so 1 <<< 10 is 1,024 and 1024 >>> 3 is 128. Integer.popcount/1 counts the bits set to 1: 255 is eight 1s in binary, so it gives 8. To see the bits of a number, Integer.to_string(10, 2) writes it in base 2, “1010”.

Bits matter when several yes-or-no flags are packed into one integer, as file permissions are (0o644), and in binary network protocols, which a later module of this track parses.

New in Elixir 1.20

Integer.ceil_div/2 and Integer.popcount/1 were added in Elixir 1.20.0, as its changelog lists. On Elixir 1.19 and older, code that calls them stops with UndefinedFunctionError, so a library that must run on older versions needs another way to write them.

Number Base Converter Convert a number between binary, octal, decimal and hex to check a literal.

Checkpoint

Check: Other bases and the Bitwise module · Parsing numbers from text

  1. Question 1 of 2 After import Bitwise, what does band(0b1100, 0b0110) return?

    Type a number.

    A hint

    Write the two numbers one above the other and keep only the bits that are 1 in both.

    Show the answer: Question 1 of 2

    Answer: 4

    0b1100 and 0b0110 share only the bit worth 4, so band/2 returns 0b0100, which is 4. bor/2 would give 0b1110, 14, and bxor/2 0b1010, 10.

  2. Question 2 of 2 A form sends the text "25 kg" in a field that should hold an age. What does Integer.parse("25 kg") return, and what should the code do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: {25, " kg"}, and the code rejects it because the rest is not ""

    Integer.parse("25 kg") returns {25, " kg"}. The number came from the start of the text, and the rest is what it could not read; accept the input only when the rest is "".

Practise: split a bill into exact shares

Write Split.shares/2, which divides an amount of paise among n people. Every share is a whole number of paise, the shares differ by at most one paisa, the larger shares come first, and together they add up to the amount exactly: 1,000 rupees among three people is 100,000 paise, so the shares are 33,334, 33,333 and 33,333. Copy the two files below into an empty folder:

practice/split.ex: the starter Elixir · practice/split.ex
defmodule Split do
  @moduledoc "Splits an amount of paise among people so that the shares add up exactly."

  @doc """
  Divides `amount` paise (a whole number, 0 or more) among `n` people.
  The shares differ by at most 1 paisa and the larger shares come first.
  """
  def shares(_amount, _n) do
    # Drop the underscores once you use the arguments. Work the shares out with
    # div/2 and rem/2, never with /: every share is a whole number of paise.
    []
  end
end
practice/split_test.exs: the tests Elixir · practice/split_test.exs
Code.require_file("split.ex", __DIR__)
ExUnit.start(seed: 0)

defmodule SplitTest do
  use ExUnit.Case

  test "an amount that divides evenly" do
    assert Split.shares(900, 3) == [300, 300, 300]
  end

  test "the remainder goes to the first people" do
    # 1,000 rupees among 3 people is 100,000 paise
    assert Split.shares(100_000, 3) == [33_334, 33_333, 33_333]
  end

  test "the shares add up to the amount" do
    assert Enum.sum(Split.shares(1_000, 7)) == 1_000
    assert Enum.sum(Split.shares(99_999, 4)) == 99_999
  end

  test "every share is a whole number, and shares differ by at most 1" do
    shares = Split.shares(1_001, 4)
    assert length(shares) == 4
    assert Enum.all?(shares, &is_integer/1)
    assert Enum.max(shares) - Enum.min(shares) <= 1
  end
end

Run the tests with elixir split_test.exs. With the starter, all four fail, and each failure shows what the test expected:

The tests against the starter Python · practice_session.py
from session import run

# The tests against the starter, whose shares/2 returns an empty list.
run("""
cd practice
!elixir split_test.exs
""")

Output

$ cd practice
$ elixir split_test.exs
Running ExUnit with seed: 0, max_cases: 36



  1) test an amount that divides evenly (SplitTest)
     split_test.exs:7
     Assertion with == failed
     code:  assert Split.shares(900, 3) == [300, 300, 300]
     left:  []
     right: [300, 300, 300]
     stacktrace:
       split_test.exs:8: (test)



  2) test the remainder goes to the first people (SplitTest)
     split_test.exs:11
     Assertion with == failed
     code:  assert Split.shares(100_000, 3) == [33334, 33333, 33333]
     left:  []
     right: [33334, 33333, 33333]
     stacktrace:
       split_test.exs:13: (test)



  3) test the shares add up to the amount (SplitTest)
     split_test.exs:16
     Assertion with == failed
     code:  assert Enum.sum(Split.shares(1000, 7)) == 1000
     left:  0
     right: 1000
     stacktrace:
       split_test.exs:17: (test)



  4) test every share is a whole number, and shares differ by at most 1 (SplitTest)
     split_test.exs:21
     Assertion with == failed
     code:  assert length(shares) == 4
     left:  0
     right: 4
     stacktrace:
       split_test.exs:23: (test)


Finished in 0.03 seconds (0.03s on load, 0.00s async, 0.00s sync)

Result: 0/4 passed
Failed: 4 tests
[exit status 2]

This output changes from run to run: ExUnit prints how long the tests took, and max_cases follows the computer's processors

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 practice_session.py

Two functions do most of the work: div(amount, n) is the share everyone gets, and rem(amount, n) is how many people get one paisa more. List.duplicate(value, count) builds a list of count copies of a value, and ++ joins two lists. When your function is right, the same command passes all four tests, as it did for a finished version:

The tests against a finished shares/2 Python · finished_session.py
from session import run

# The same tests against a finished shares/2.
run("""
cd finished
cp ../practice/split_test.exs .
elixir split_test.exs
""")

Output

$ cd finished
$ cp ../practice/split_test.exs .
$ elixir split_test.exs
Running ExUnit with seed: 0, max_cases: 36

....
Finished in 0.02 seconds (0.02s on load, 0.00s async, 0.00s sync)

Result: 4 passed

This output changes from run to run: ExUnit prints how long the tests took, and max_cases follows the computer's processors

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 finished_session.py

Last, a self-check on the six ideas of the lesson: types, division, rounding, money, parsing and bits. Your shakiest idea comes first, and every choice you make is explained on the spot:

Practice that adapts to you

Practise this lesson

15 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 15 What does 5 + 0.0 return?

    Choose one answer.

    Show the answer: Question 1 of 15

    Answer: 5.0, a float

    +, - and * return a float as soon as either side is a float, so 5 + 0.0 is 5.0, just as 3 * 2.0 was 6.0.

  2. Question 2 of 15 What does this print?

    Read the code, then choose one answer.

    IO.inspect({2 ** 3, 2 ** -3, 2.0 ** 3})
    Show the answer: Question 2 of 15

    Answer: {8, 0.125, 8.0}

    The power operator returns an integer only when both sides are integers and the exponent is 0 or more. 2 -3 is 0.125 and 2.0 3 is 8.0.

  3. Question 3 of 15 A program multiplies the integers 1 to 30 together. In C or Java with 64-bit integers that overflows. What does Elixir return?

    Choose one answer.

    Show the answer: Question 3 of 15

    Answer: The exact 33-digit product, 265252859812191058636308480000000

    An integer that outgrows one memory word, 60 bits on a 64-bit machine, moves to a larger representation without the program noticing, so the product of 1 to 30 comes out exact.

  4. Question 4 of 15 Whose remainder always has the sign of the divisor?

    Choose one answer.

    Show the answer: Question 4 of 15

    Answer: Integer.mod/2

    rem(-7, 2) is -1, with the sign of the dividend, and Integer.mod(-7, 2) is 1, with the sign of the divisor. Each remainder belongs with its own quotient: rem with div, mod with floor_div.

  5. Question 5 of 15 What does div(-9, 4) return?

    Type a number.

    A hint

    The exact quotient is -2.25. Which way does div/2 round?

    Show the answer: Question 5 of 15

    Answer: -2

    div/2 rounds towards zero, so -2.25 becomes -2. Integer.floor_div(-9, 4) would round down to -3.

  6. Question 6 of 15 A carousel has 5 slides, numbered 0 to 4. On slide 0 the user presses Back. Which expression gives the slide to show?
    Show the answer: Question 6 of 15

    Answer: Integer.mod(0 - 1, 5), which is 4

    A position in a ring, like an hour on a clock, needs a remainder from 0 up to the size of the ring: Integer.mod/2. Integer.mod(0 - 1, 5) is 4, the last slide.

  7. Question 7 of 15 What does round(-2.5) return?

    Choose one answer.

    Show the answer: Question 7 of 15

    Answer: -3

    round/1 rounds to the nearest integer and, for a value exactly halfway, away from zero: round(-2.5) is -3.

  8. Question 8 of 15 A test checks price * 3 == 3.30 with price = 1.10, and it fails. Why?

    Choose one answer.

    Show the answer: Question 8 of 15

    Answer: 1.10 * 3 is 3.3000000000000003, because 1.10 is stored as the nearest binary fraction

    Most decimal fractions have no exact binary form. Compare computed floats within a tolerance, such as abs(price * 3 - 3.30) < 1.0e-9, or keep amounts as integer paise.

  9. Question 9 of 15 Which call returns -3 for -2.2?

    Choose one answer.

    Show the answer: Question 9 of 15

    Answer: floor(-2.2)

    For negative numbers, rounding down and dropping the fraction differ: floor(-2.2) is -3, while trunc(-2.2), ceil(-2.2) and round(-2.2) are all -2.

  10. Question 10 of 15 How should a program store the amount 149.50 rupees?

    Choose one answer.

    Show the answer: Question 10 of 15

    Answer: As the integer 14950, a count of paise

    Store money as a whole number of its minor unit, paise for rupees, and format it as rupees only for display.

  11. Question 11 of 15 A 5 % discount on 1,999 paise is 99.95 paise. Which way of applying it keeps the amount exact and predictable?

    Choose one answer.

    Show the answer: Question 11 of 15

    Answer: Compute it with integers and one written rounding rule, such as div(1999 * 5 + 50, 100), which gives 100 paise

    Money stays in integers, and a fraction of a paisa is rounded once, by a rule that is written down. Adding 50 before dividing by 100 rounds halves up for positive amounts, so the discount is 100 paise.

  12. Question 12 of 15 What does this return?

    Read the code, then choose one answer.

    Integer.parse("7up")
    Show the answer: Question 12 of 15

    Answer: {7, "up"}

    Integer.parse("7up") is {7, "up"}: the number 7, and "up", the part it could not read. Text that is wholly a number leaves "" over.

  13. Question 13 of 15 A program reads "42\n" from the keyboard and rejects it, although the user typed 42. Which change fixes it?

    Choose one answer.

    Show the answer: Question 13 of 15

    Answer: Trim the text with String.trim/1 before parsing it

    A line read from a file or the keyboard ends with its newline, so Integer.parse("42\n") returns {42, "\n"}. Trim the line first, then accept the result only when nothing is left over.

  14. Question 14 of 15 What integer is 0x1F?

    Type a number.

    Show the answer: Question 14 of 15

    Answer: 31

    0x1F is hexadecimal: 1 sixteen and 15 ones, so 16 + 15 = 31. 0b11111 and 0o37 are the same integer.

  15. Question 15 of 15 What does Integer.popcount(0b1011) return?

    Type a number.

    A hint

    Count the 1s in the binary literal.

    Show the answer: Question 15 of 15

    Answer: 3

    Integer.popcount/1, new in Elixir 1.20, counts the bits set to 1. 0b1011 has three of them, so it returns 3, as Integer.popcount(255) returned 8 for eight 1s.

Interview questions

Warm-up: what does / return in Elixir, and how do you divide two integers to get an integer?

/ always returns a float, even when the division comes out even: 10 / 2 is 5.0, not 5. For a whole-number quotient, use div/2, which rounds towards zero, with rem/2 for the remainder; div(10, 2) is 5. When a number may be negative, decide how the quotient must round: div(-7, 2) is −3, Integer.floor_div(-7, 2) is −4 and Integer.ceil_div(-7, 2) is −3, and each quotient goes with its own remainder, rem/2 for div/2 and Integer.mod/2 for Integer.floor_div/2. Dividing with / and converting back with trunc/1 or round/1 is a detour through floats that brings float rounding into integer code.

Two questions from code that handles money and time:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    An invoice service keeps prices as floats in rupees and converts them to paise with trunc(price * 100) before charging. Some customers are charged one paisa less than the price they saw. Explain the cause, and redesign how the service handles money, including how it rounds taxes and discounts.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A scheduler shows a meeting 5 hours before 02:00 as starting at hour -3, and its result list says "page 10 of 10" for 101 results at 10 a page, so the last result never appears. Find both bugs, choose the integer functions that fix them, and say which cases you would test.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Integers have no fixed size and never wrap around, while floats are 64-bit binary numbers with no infinity: an operation that would overflow raises ArithmeticError.
  • / always returns a float, ** returns one for a negative exponent, and +, - and * return one as soon as either side is a float.
  • div and rem round towards zero, Integer.floor_div and Integer.mod round down, and Integer.ceil_div, new in 1.20, rounds up; use mod for clocks and rings and ceil_div for counts of pages or boxes.
  • Float.round/2 rounds the stored float, so Float.round(2.675, 2) is 2.67; compare computed floats within a tolerance, never with ==.
  • Keep money as whole paise and format it only for display, because trunc(19.99 * 100) gives 1998 and ten items at 0.10 add up to 0.9999999999999999.
  • Integer.parse/2 returns the number and the text it could not read, so accept input only when the rest is "".

Terms in this lesson

float
A number stored as a 64-bit binary floating-point value, such as 0.5 or 2.5e3. Most decimal fractions are stored as the nearest binary fraction, so 0.1 + 0.2 gives 0.30000000000000004.
floored division
Integer division that rounds the quotient down, towards negative infinity, as Integer.floor_div/2 does: -7 by 2 gives -4. Its remainder, from Integer.mod/2, has the sign of the divisor.
integer
A whole number such as 42 or -7. Elixir integers have no fixed size: one that outgrows a machine word moves to a larger representation, so integer arithmetic never overflows.
minor unit
The smallest unit of a currency, such as the paisa of the rupee or the cent of the dollar. Keeping amounts as whole numbers of it makes sums of money exact.
truncated division
Integer division that rounds the quotient towards zero, as div/2 does: div(-7, 2) is -3. Its remainder, from rem/2, has the sign of the number being divided.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.