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.
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.
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.0is6.0./always returns a float, even when the division comes out even:10 / 2is5.0. For a whole-number quotient usediv/2, which returned5.**returns an integer only when both sides are integers and the exponent is 0 or more, so2 ** -1is0.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
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:
# 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)}") 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/2rounds 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 partnerrem/2gives the remainder that goes with it, −1, which always has the sign of the dividenda.Integer.floor_div/2rounds 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 partnerInteger.mod/2gives 1, which always has the sign of the divisorb.Integer.ceil_div/2rounds 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
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:
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:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- 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.
- 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.
- 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.
- 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.
- 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:
# 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)}") 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
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:
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:
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.
Checkpoint
Check: Other bases and the Bitwise module · Parsing numbers from text
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:
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 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:
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:
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.
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.
-
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.
-
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.
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.divandremround towards zero,Integer.floor_divandInteger.modround down, andInteger.ceil_div, new in 1.20, rounds up; usemodfor clocks and rings andceil_divfor counts of pages or boxes.Float.round/2rounds the stored float, soFloat.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/2returns 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
- Elixir guide: Basic types (integers, floats and arithmetic) (The Elixir Team (hexdocs.pm))
- Integer (Elixir) (The Elixir Team (hexdocs.pm))
- Float (Elixir) (The Elixir Team (hexdocs.pm))
- Kernel (Elixir), the arithmetic operators, div/2, rem/2, round/1 and trunc/1 (The Elixir Team (hexdocs.pm))
- Bitwise (Elixir) (The Elixir Team (hexdocs.pm))
- Changelog for Elixir v1.20 (Integer.ceil_div/2 and Integer.popcount/1) (The Elixir Team (hexdocs.pm))
- Erlang Reference Manual: Data Types (floats have no Inf or NaN) (Erlang/OTP (erlang.org))
- Efficiency Guide: Memory Usage (small and large integers) (Erlang/OTP (erlang.org))
- erlang:float_to_binary/2 (Erlang/OTP (erlang.org))
- Decimal, arbitrary precision decimal arithmetic (a package on Hex) (Decimal (hexdocs.pm))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress