OCaml Module 2 – Expressions, values and functions
Integers, floats and arithmetic
Keep int and float apart with their own operators, predict 63-bit overflow, division and mod on negatives, nan and rounding, and parse numbers safely.
What you will learn
- Use int and float arithmetic with their separate operators, such as + and +.
- Predict 63-bit integer overflow, truncating division and mod on negative numbers
- Convert between strings, ints and floats safely with the _opt functions
- Choose Int64, Int32 or an arbitrary-precision library when 63 bits are not enough
Before you start
What you’ll learn · Before you start
What you will learn
- Use int and float arithmetic with their separate operators, such as + and +.
- Predict 63-bit integer overflow, truncating division and mod on negative numbers
- Convert between strings, ints and floats safely with the _opt functions
- Choose Int64, Int32 or an arbitrary-precision library when 63 bits are not enough
Before you start
On this page
max_int + 1 in OCaml 5.5.1 gives -4611686018427387904 without any error, and -7 mod 2 gives -1, not 1. The
same program prints a max_int of 2147483647 once it is compiled to JavaScript.
You will keep integers and floats apart with their own operators, predict overflow, division and mod on negative
numbers, read infinity, nan and rounding correctly, parse numbers from text without exceptions, and choose a
bigger integer type when 63 bits are not enough.
int and float are separate types with separate operators
OCaml never converts a number from one type to another by itself. Integers use +, -, *, / and mod; floats
use their own operators with a dot, +., -., *. and /., and a float literal always has a dot or an exponent,
2.0 or 2.. Mixing them is a type error, so a conversion has to be written: float_of_int (also Float.of_int)
from int to float, and truncate, int_of_float or Float.round the other way.
Text needs a conversion as well. int_of_string raises the exception Failure "int_of_string" on bad input, which
stops a program unless it is caught, while int_of_string_opt returns None instead. Both read 0x, 0o and 0b
prefixes and _ separators:
from session import toplevel
# int and float never mix without a conversion. Converting text to numbers: int_of_string raises on bad input, the _opt versions return None instead and accept
# 0x, 0o and 0b prefixes and _ separators. OCaml 5.5 adds bit counting such as Int.popcount.
toplevel("""
3 + 2.0;;
3 +. 2.0;;
float_of_int 3 +. 2.0;;
int_of_string "42";;
int_of_string "12a";;
int_of_string_opt "12a";;
int_of_string_opt "0x1F";;
int_of_string_opt "1_000_000";;
float_of_string_opt "2.5e3";;
Int.popcount 255;;
""") Output
$ ocaml
OCaml version 5.5.1
Enter #help;; for help.
# 3 + 2.0;;
Line 1, characters 4-7:
1 | 3 + 2.0;;
^^^
Error: The constant 2.0 has type float but an expression was expected of type
int
# 3 +. 2.0;;
Line 1, characters 0-1:
1 | 3 +. 2.0;;
^
Error: The constant 3 has type int but an expression was expected of type
float
Hint: Did you mean 3.?
# float_of_int 3 +. 2.0;;
- : float = 5.
# int_of_string "42";;
- : int = 42
# int_of_string "12a";;
Exception: Failure "int_of_string".
# int_of_string_opt "12a";;
- : int option = None
# int_of_string_opt "0x1F";;
- : int option = Some 31
# int_of_string_opt "1_000_000";;
- : int option = Some 1000000
# float_of_string_opt "2.5e3";;
- : float option = Some 2500.
# Int.popcount 255;;
- : int = 8
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 conversions_session.py
3 + 2.0 failed because 2.0 is a float where + wants an int, and 3 +. 2.0 failed the other way, with the hint
“Did you mean 3.?”. int_of_string_opt "0x1F" gave Some 31 and "1_000_000" gave Some 1000000. The last line
uses a function new in OCaml 5.5: Int.popcount 255 counts the bits that are set, 8.
Parsing input with the raising version
Wrong: let age = int_of_string line on input a user typed, which stops the program on 12a. Right:
match int_of_string_opt line with Some age -> … | None -> …, so bad input is a case the code handles.
A native int has 63 bits and wraps without warning
The manual’s chapter on values promises only the range −2³⁰ to 2³⁰ − 1, and adds that on 64-bit platforms the
current implementation supports −2⁶² to 2⁶² − 1: one bit of each machine word goes to the runtime, which uses it to
tell numbers from pointers. So max_int is 2⁶² − 1, and arithmetic past it
integer overflow: A result beyond max_int or min_int. OCaml's int arithmetic wraps around silently, so max_int + 1 gives min_int and no error is raised.:
from session import toplevel
# int has 63 bits on a 64-bit machine, so max_int is 2^62 - 1, and one more wraps round to min_int without any
# error. Int64 has the full 64 bits.
toplevel("""
max_int;;
min_int;;
max_int + 1;;
max_int + 1 = min_int;;
Sys.int_size;;
Int64.max_int;;
Int64.add Int64.max_int 1L;;
""") Output
$ ocaml OCaml version 5.5.1 Enter #help;; for help. # max_int;; - : int = 4611686018427387903 # min_int;; - : int = -4611686018427387904 # max_int + 1;; - : int = -4611686018427387904 # max_int + 1 = min_int;; - : bool = true # Sys.int_size;; - : int = 63 # Int64.max_int;; - : int64 = 9223372036854775807L # Int64.add Int64.max_int 1L;; - : int64 = -9223372036854775808L
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 int_limits_session.py
max_int + 1 gave exactly min_int, and Sys.int_size reported 63. Int64 has all 64 bits, up to
9223372036854775807, but it wraps the same way, as Int64.add Int64.max_int 1L showed. The size of int also
depends on where the program runs:
from session import files, run
# One program, three targets: native code, JavaScript through js_of_ocaml and WebAssembly through wasm_of_ocaml,
# each run and printing its own int size.
files("ints.ml")
run("""
ocamlfind ocamlopt -package unix ints.ml -o ints_native && ./ints_native
ocamlfind ocamlc -package js_of_ocaml -linkpkg ints.ml -o ints.byte
js_of_ocaml ints.byte -o ints.js && node ints.js
wasm_of_ocaml ints.byte -o ints_wasm.js && node ints_wasm.js
""") Output
$ ocamlfind ocamlopt -package unix ints.ml -o ints_native && ./ints_native int_size 63, max_int 4611686018427387903 $ ocamlfind ocamlc -package js_of_ocaml -linkpkg ints.ml -o ints.byte $ js_of_ocaml ints.byte -o ints.js && node ints.js int_size 32, max_int 2147483647 $ wasm_of_ocaml ints.byte -o ints_wasm.js && node ints_wasm.js int_size 31, max_int 1073741823
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 targets_session.py
Native code printed 63 bits, the JavaScript build from js_of_ocaml 32 bits and the WebAssembly build from
wasm_of_ocaml 31 bits, with a max_int of 1073741823. A program meant for the browser must not assume 63 bits. Answer
three questions about your own numbers and see which type fits:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Type | Holds very large values | Same size on every target | Fast and allocation-free | Plain operators |
|---|---|---|---|---|
| int Size: 63 bits native, 32 in JavaScript | Mixed up to 4611686018427387903 natively; wraps silently past it | Poor 63 bits native, 32 in JavaScript, 31 in WebAssembly | Good unboxed, the fastest | Good +, -, *, / and mod |
| Int32.t Size: 32 bits everywhere | Poor up to 2147483647 | Good exactly 32 bits, as C and file formats expect | Mixed boxed values cost an allocation | Poor Int32.add, Int32.mul and literals such as 5l |
| Int64.t Size: 64 bits everywhere | Good up to 9223372036854775807 | Good exactly 64 bits on every target | Mixed boxed values cost an allocation | Poor Int64.add and literals such as 1L |
| Arbitrary precision Size: as many digits as memory allows | Good no fixed limit, such as a library like Zarith | Good the same value everywhere | Poor slower, and a library to install | Mixed the library's own functions and operators |
How the answers decide
- “Can the values grow past about 4.6 × 10^18, the native int's limit?” Yes, they can: rules out int and Int32.t. Past max_int a native int wraps round to min_int with no error, and Int32 stops at 2147483647.
- “Can the values grow past about 4.6 × 10^18, the native int's limit?” Yes, they can: adds a caveat to Int64.t. Int64 stops at 9223372036854775807 and wraps the same way past it; with no known limit, use arbitrary precision.
- “Must the size match a file format, a network protocol or a C library exactly?” Yes, a fixed size: rules out int and Arbitrary precision. Only Int32 and Int64 have a fixed size; int's size depends on the target, and arbitrary precision has none.
- “Will the program also be compiled to JavaScript or WebAssembly?” Yes, for the browser: adds a caveat to int. The same program printed int_size 32 under js_of_ocaml and 31 under wasm_of_ocaml, so values that fit natively may wrap.
For counts, indexes and money in paise, int is right on a server. When values have no known limit, an
arbitrary-precision library such as Zarith keeps every digit, at a cost in speed.
Checkpoint
Check: int, float and conversions · 63-bit ints and wider types
Division truncates towards zero, and mod takes the dividend’s sign
Integer division rounds towards zero, and mod takes the sign of the number being divided, so a = (a / b) * b + (a mod b) always holds. The Int module of OCaml 5.5 adds three more ways to divide, each with its own rounding:
from session import toplevel
# Integer division truncates towards zero and mod takes the sign of the dividend; OCaml 5.5's Int.fdiv, cdiv, ediv
# and erem round down, round up or keep the remainder non-negative; erem keeps a step back from day 0 of a week
# inside 0 to 6. Dividing an int by zero raises an exception.
toplevel("""
-7 / 2;;
-7 mod 2;;
Int.fdiv (-7) 2;;
Int.cdiv (-7) 2;;
Int.ediv (-7) 2;;
Int.erem (-7) 2;;
(-1) mod 7;;
Int.erem (-1) 7;;
1 / 0;;
""") Output
$ ocaml OCaml version 5.5.1 Enter #help;; for help. # -7 / 2;; - : int = -3 # -7 mod 2;; - : int = -1 # Int.fdiv (-7) 2;; - : int = -4 # Int.cdiv (-7) 2;; - : int = -3 # Int.ediv (-7) 2;; - : int = -4 # Int.erem (-7) 2;; - : int = 1 # (-1) mod 7;; - : int = -1 # Int.erem (-1) 7;; - : int = 6 # 1 / 0;; Exception: Division_by_zero.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 division_session.py
| Expression | Result | Rounds |
|---|---|---|
-7 / 2 |
-3 | towards zero |
-7 mod 2 |
-1 | remainder with the sign of -7 |
Int.fdiv (-7) 2 |
-4 | down, towards minus infinity |
Int.cdiv (-7) 2 |
-3 | up, towards plus infinity |
Int.ediv (-7) 2, Int.erem (-7) 2 |
-4, 1 | so the remainder is never negative |
The table matters when a negative number meets mod. Testing “is n odd?” with n mod 2 = 1 fails for -7, whose
mod 2 is -1; n mod 2 <> 0 or Int.erem n 2 = 1 works for both signs. Indexes that wrap around need the same care. Counting
the days of a week from 0, one step back from day 0 is (-1) mod 7, which gave -1, not a day at all, while
Int.erem (-1) 7 gave 6, the last day of the week. And 1 / 0 raised Division_by_zero, an exception, while the next
section shows that floats behave quite differently.
Key idea
Integer division and mod follow the sign of the dividend, so test oddness with n mod 2 <> 0, and use Int.erem when
a remainder must never be negative.
Floats follow IEEE 754: infinity, nan and binary rounding
Floats are 64-bit binary numbers, as the Float module’s documentation describes, so division by zero does not raise:
it gives infinity, or nan, “not a number”, for 0. /. 0.. Comparing nan is where programs go wrong:
from session import toplevel
# Floats follow IEEE 754: dividing by zero gives infinity or nan, and nan is not equal to itself under =, though
# compare and Float.equal treat it as equal. Converting to int truncates; printing shows the binary value.
toplevel("""
1. /. 0.;;
0. /. 0.;;
nan = nan;;
compare nan nan;;
Float.equal nan nan;;
truncate 2.9;;
Float.round 2.5;;
string_of_float (0.1 +. 0.2);;
0.1 +. 0.2 = 0.3;;
Float.abs (0.1 +. 0.2 -. 0.3) < 1e-9;;
Printf.printf "%.17g %h %F\\n" (0.1 +. 0.2) 0.1 1.0;;
""") Output
$ ocaml OCaml version 5.5.1 Enter #help;; for help. # 1. /. 0.;; - : float = infinity # 0. /. 0.;; - : float = nan # nan = nan;; - : bool = false # compare nan nan;; - : int = 0 # Float.equal nan nan;; - : bool = true # truncate 2.9;; - : int = 2 # Float.round 2.5;; - : float = 3. # string_of_float (0.1 +. 0.2);; - : string = "0.3" # 0.1 +. 0.2 = 0.3;; - : bool = false # Float.abs (0.1 +. 0.2 -. 0.3) < 1e-9;; - : bool = true # Printf.printf "%.17g %h %F\n" (0.1 +. 0.2) 0.1 1.0;; 0.30000000000000004 0x1.999999999999ap-4 1. - : unit = ()
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 float_session.py
nan = nan was false, because IEEE 754 makes nan unequal to everything, itself included, while compare nan nan
gave 0 and Float.equal nan nan gave true, so sorting and tables treat it consistently. truncate 2.9 dropped
the fraction to 2, and Float.round 2.5 rounded half away from zero, to 3. Finally, printing: string_of_float
showed 0.3, while %.17g printed the stored value 0.30000000000000004, and %h gave the exact binary value of
0.1 in hexadecimal.
That stored value is why 0.1 +. 0.2 = 0.3 answered false. Neither 0.1 nor 0.2 has an exact binary form, and their
rounded sum lands one step away from the rounded 0.3. Results of float arithmetic are compared within a tolerance
chosen for the problem: Float.abs (0.1 +. 0.2 -. 0.3) < 1e-9 gave true. Amounts that must add up exactly, such as
money, belong in integers of the smallest unit instead.
Check for nan with Float.is_nan
x = nan is always false, even when x is nan. Write Float.is_nan x, or compare with Float.equal when nan must
count as equal to itself.
Checkpoint
Check: Division and mod on negatives · infinity, nan and rounding
Practise: numbers that surprise
The questions give expressions with negative numbers, overflow, mixed types and nan, and ask what OCaml 5.5.1 answers. Each answer is in one of the sessions above:
Practice that adapts to you
Practise this lesson
12 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to 2–5 years): why does 3 + 2.0 not compile in OCaml?
OCaml has no implicit conversions between numeric types, and its operators are typed: + takes two ints and +.
takes two floats. In 3 + 2.0, 2.0 is a float where + expects an int, so the type checker rejects the
expression. You write the conversion yourself, float_of_int 3 +. 2.0, which gives 5., or 3 + int_of_float 2.0
if you want an int. The design costs a few characters but removes a class of bugs where a value is silently rounded
or widened, and it is also why the compiler can keep ints unboxed and fast.
Two harder questions follow, about a sum that turns negative and about averaging prices. Settle your answer before you open each first step:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A tool adds up file sizes in bytes with plain ints. The native build is right, but the same code built with wasm_of_ocaml for a browser page shows a negative total once the files pass about one gigabyte. Explain what happens and what you would change.
-
Fresher to mid
You receive prices as strings, such as "120", "99" and "abc", and must return their average. Describe an OCaml function for it: how it parses, what it does with bad or empty input, and how it divides.
Flashcards · 7 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
Why does 3 + 2.0 not compile?
int and float are separate types with separate operators: + takes ints and +. takes floats. Write the conversion yourself, as in float_of_int 3 +. 2.0.
-
What does max_int + 1 give in native OCaml 5.5.1?
min_int, -4611686018427387904, with no error: a native int has 63 bits and wraps silently.
-
What do you use when 63 bits are not enough, or a fixed width is required?
Int64 or Int32 for fixed widths, or an arbitrary-precision library such as Zarith. Under JavaScript and WebAssembly a plain int has only 32 or 31 bits.
-
Why is -7 mod 2 equal to -1, and how should oddness be tested?
Integer division truncates towards zero and mod takes the sign of the dividend. Test oddness with n mod 2 <> 0, and use Int.erem (OCaml 5.5) for a remainder that is never negative.
-
Which OCaml 5.5 functions round an integer division down or up?
Int.fdiv rounds down and Int.cdiv rounds up; Int.ediv and Int.erem keep the remainder non-negative.
-
Why is x = nan always false, and what should you write instead?
Floats follow IEEE 754, where nan equals nothing, not even itself. Write Float.is_nan x, or compare with Float.equal when nan must equal itself.
-
How do you read a number a user typed without stopping the program on bad input?
match int_of_string_opt line with Some age -> … | None -> …; plain int_of_string raises on input such as 12a.
Key takeaways
intandfloatare separate types with separate operators, such as+and+., and every conversion between them, or from text, is written out.- A native
inthas 63 bits and wraps silently pastmax_int, and the same program has 32-bit or 31-bit ints in JavaScript or WebAssembly, so larger or fixed sizes need Int64, Int32 or an arbitrary-precision library. - Integer division truncates towards zero and
modtakes the dividend’s sign, while OCaml 5.5’sInt.fdiv,Int.cdiv,Int.edivandInt.eremround down, up or keep the remainder non-negative. - Floats follow IEEE 754, so division by zero gives
infinityornan,nan = nanis false, and%.17gshows the binary value behind a short printed float.
Terms in this lesson
- integer overflow
- A result beyond max_int or min_int. OCaml's int arithmetic wraps around silently, so max_int + 1 gives min_int and no error is raised.
References
- The OCaml Manual: module Int (Inria and the OCaml developers)
- The OCaml Manual: module Float (Inria and the OCaml developers)
- The OCaml Manual: module Stdlib (Inria and the OCaml developers)
- The OCaml Manual: Values (Inria and the OCaml developers)
- The OCaml Manual: module Int64 (Inria and the OCaml developers)
- Zarith: arbitrary-precision integers and rationals for OCaml (The Zarith developers)
Report a problem with this lesson
Kept only in this browser. Your Learn progress