Language Atlas (mini-courses for more languages) Module 5 – Ada with Alire and GNAT
Ada for C and Java developers: strong types
Map C and Java habits to Ada: integer types with ranges, Constraint_Error, rem and mod, arrays that keep their bounds, reductions and parameter modes.
What you will learn
- Declare integer types and subtypes with ranges, and predict when a value raises Constraint_Error
- Predict what /, rem and mod give for negative numbers in Ada, C and Java, and what an overflow does in each
- Use arrays indexed by any range and their attributes First, Last, Length, Range and Image
- Use an Ada 2022 reduction expression to sum an array, and choose a type for its accumulator
- Split code into a package specification and body, and return results through out and in out parameters
Before you start
What you’ll learn · Before you start
What you will learn
- Declare integer types and subtypes with ranges, and predict when a value raises Constraint_Error
- Predict what /, rem and mod give for negative numbers in Ada, C and Java, and what an overflow does in each
- Use arrays indexed by any range and their attributes First, Last, Length, Range and Image
- Use an Ada 2022 reduction expression to sum an array, and choose a type for its accumulator
- Split code into a package specification and body, and return results through out and in out parameters
Before you start
On this page
In Java Integer.MAX_VALUE + 1 is −2,147,483,648, and nothing reports it. In Ada Integer'Last + 1 raised
Constraint_Error, and so did storing 103 in a variable whose type allows marks from 0 to 100: a type in Ada says
which values it may hold, and the compiler and the running program hold it to that.
This lesson maps what you write in C and Java onto Ada, one difference at a time, each shown by a recorded run: integer types with their own ranges, the three division operators, arrays that keep their bounds, the reductions of Ada 2022, and packages whose parameters say which way data flows.
A type says which values it may hold
In C a mark is an int, and the C standard says a typedef introduces no new type, only a synonym; in Java it is an
int as well. In Ada type Mark is range 0 .. 100; declares a new integer type, and the Reference Manual says the
declaration also defines its first subtype, constrained to those bounds. A
An Ada subtype is a type with an extra constraint, such as a narrower range: values of the subtype are values of its type, and can be mixed with them, but storing one outside the constraint is checked.
narrows a type without making a new one: subtype Pass_Mark is Mark range 40 .. 100;. This program reads marks from
its command line and adds 5 grace marks to each:
with Ada.Command_Line; use Ada.Command_Line;
with Ada.Exceptions; use Ada.Exceptions;
with Ada.Text_IO; use Ada.Text_IO;
procedure Marks is
type Mark is range 0 .. 100;
subtype Pass_Mark is Mark range 40 .. 100;
Grace : constant Mark := 5;
Given : Mark;
begin
Put_Line ("Mark is" & Mark'First'Image & " .." & Mark'Last'Image
& ", Mark'Base is " & Mark'Base'First'Image & " .."
& Mark'Base'Last'Image);
for I in 1 .. Argument_Count loop
Put (Argument (I));
begin
Given := Mark'Value (Argument (I));
Put (if Given in Pass_Mark then " is a pass" else " is below 40");
Put (", plus grace" & Mark'Image (Given + Grace));
Given := Given + Grace;
Put_Line (", stored");
exception
when E : Constraint_Error =>
Put_Line (", Constraint_Error, " & Exception_Message (E));
end;
end loop;
end Marks; The session runs it on five arguments, then builds a version in which two lines break the rules:
from session import run
# marks.adb on five arguments, from a pass to text that is not a number; then the version in wrong/, which mixes a
# Mark with a Paise and gives a Pass_Mark the value 35, built in its place.
run("""
cd types
alr build
./bin/marks 72 38 98 101 abc
cp ../wrong/marks.adb src/marks.adb
!alr build
""") Output
$ cd types $ alr build Note: Synchronizing workspace... Nothing to update. Note: Building types=0.1.0-dev/types.gpr... Setup [mkdir] object directory for project Types [mkdir] exec directory for project Types Compile [Ada] marks.adb Bind [gprbind] marks.bexch [Ada] marks.ali Link [link] marks.adb Success: Build finished successfully in … seconds. $ ./bin/marks 72 38 98 101 abc Mark is 0 .. 100, Mark'Base is -128 .. 127 72 is a pass, plus grace 77, stored 38 is below 40, plus grace 43, stored 98 is a pass, plus grace 103, Constraint_Error, marks.adb:21 range check failed 101, Constraint_Error, marks.adb:18 range check failed abc, Constraint_Error, bad input for 'Value: "abc" $ cp ../wrong/marks.adb src/marks.adb $ alr build Note: Building types=0.1.0-dev/types.gpr... Compile [Ada] marks.adb marks.adb:10:35: error: invalid operand types for operator "+" marks.adb:10:35: error: left operand has type "Paise" defined at line 6 marks.adb:10:35: error: right operand has type "Mark" defined at line 4 marks.adb:11:35: warning: value not in range of type "Pass_Mark" defined at line 5 [enabled by default] marks.adb:11:35: warning: Constraint_Error will be raised at run time [enabled by default] compilation of marks.adb failed gprbuild: *** compilation phase failed ERROR: Command ["gprbuild", "-s", "-j0", "-p", "-P", "types/types.gpr"] exited with code 4 ERROR: Compilation failed. [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 types_session.py
72 and 38 behave as in any language. Mark'Value ("101") stopped with Constraint_Error: The predefined Ada exception raised when a value leaves the range of its subtype or the base range of its type, when an array index is out of bounds, or when an integer is divided by zero.,
range check failed, because 101 is not a Mark, and abc with bad input for 'Value. The surprise is 98. The
program printed the sum 103 and only then failed, on the line that stores it. Arithmetic on a Mark happens in
Mark'Base, the base range: The range an Ada integer type's arithmetic works in, chosen by the compiler to include at least the declared range and to be symmetric about zero: -128 .. 127 for a type declared 0 .. 100 with GNAT 16. the compiler picks for the type, which the program printed
as −128 .. 127, and Mark'Image takes any value of Mark'Base, so printing 103 checks nothing. Storing it in
Given is where the range check runs. Step through the three sums of this lesson and the check each one meets:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Sums happen in the base type. Given + Grace is computed in Mark'Base, the range the compiler chose for the type's arithmetic. The program printed it: Mark'Base is -128 .. 127, eight bits, though a Mark only allows 0 .. 100.
- 72 plus 5. The sum 77 is inside -128 .. 127, so the overflow check passes, and it is also inside 0 .. 100, so storing it in Given passes the range check. The run printed 72 is a pass, plus grace 77, stored.
- 98 plus 5, printed. The sum 103 fits the base range, so no overflow check fails. Mark'Image takes a value of Mark'Base, so it printed 103 without any range check.
- 98 plus 5, stored. Given := Given + Grace stores 103 in a Mark. Here the range check runs, and 103 is not in 0 .. 100: Constraint_Error, marks.adb:21 range check failed.
- 72 plus 95. In totals.adb the reduction adds 72 and 95 in a Mark accumulator. 167 does not even fit -128 .. 127, so the overflow check fails first: Constraint_Error, overflow check failed.
- Two checks, two places. The overflow check guards every operation, against the base range; the range check guards every store into a subtype, against its own range. Both raise Constraint_Error, and the message says which one failed.
The version in wrong/ did not compile at all. Fee + Maths adds a Paise to a Mark, and GNAT refused it with
invalid operand types for operator "+", naming both types; to mix them you convert one explicitly, as in
Paise (Maths). And Lowest : constant Pass_Mark := 35; was caught before the program ever ran: the compiler
warned that the value is not in the range of Pass_Mark and that Constraint_Error will be raised at run time.
Expecting a range check on every expression
Put_Line (Mark'Image (Given + Grace)) printed 103 for a Mark of 98. Checks run when a value is stored, passed as
a parameter or converted, and the base range guards the arithmetic in between. Store the result in a Mark, or
write Mark (Given + Grace), when it must be a Mark.
Checkpoint
Check: Integer types, subtypes and Constraint_Error
Division truncates as in C and Java, while mod follows the divisor
Ada has three operators where C and Java have two. The Reference Manual defines / and rem by the relation
A = (A/B)*B + (A rem B), where A rem B has the sign of A, and it defines mod so that the result is zero or
has the sign of B. Here they are on the four combinations of signs, followed by 18 % of 1,003 rupees in whole
rupees and one more than Integer'Last:
with Ada.Exceptions; use Ada.Exceptions;
with Ada.Text_IO; use Ada.Text_IO;
procedure Arith is
function Cell (N : Integer) return String is
Image : constant String := N'Image;
begin
return (1 .. 5 - Image'Length => ' ') & Image;
end Cell;
type Pair is record
A, B : Integer;
end record;
Pairs : constant array (1 .. 4) of Pair :=
[(7, 2), (-7, 2), (7, -2), (-7, -2)];
Price : constant Natural := 1_003;
Big : Integer := Integer'Last;
begin
Put_Line (" A B A/B rem mod");
for P of Pairs loop
Put_Line (Cell (P.A) & Cell (P.B) & Cell (P.A / P.B)
& Cell (P.A rem P.B) & Cell (P.A mod P.B));
end loop;
Put_Line ("18% of" & Price'Image & " with /:"
& Natural'Image (Price * 18 / 100));
Put_Line ("Rounded half up, (x + 50) / 100:"
& Natural'Image ((Price * 18 + 50) / 100));
Big := Big + Integer'Value ("1");
Put_Line ("Never printed:" & Big'Image);
exception
when E : Constraint_Error =>
Put_Line ("Integer'Last + 1: Constraint_Error, "
& Exception_Message (E));
end Arith; This crate has two programs, so the session builds it with alr build -- -j1: whatever follows -- goes to
GPRbuild, and -j1 makes it work on one file at a time, so its progress lines come in the same order on every run.
from session import run
# The four signs of / rem and mod, 18 % of 1003 rupees in whole rupees, one more than Integer'Last, and the paise
# amounts that the lesson's exercise models. The crate has two programs, so GPRbuild gets -j1 after --: one job at a
# time, and its progress lines come in the same order on every run.
run("""
cd arith
alr build -- -j1
./bin/arith
./bin/rupees 1205 99 100000 5 0
""") Output
$ cd arith
$ alr build -- -j1
Note: Synchronizing workspace...
Nothing to update.
Note: Building arith=0.1.0-dev/arith.gpr...
Setup
[mkdir] object directory for project Arith
[mkdir] exec directory for project Arith
Compile
[Ada] arith.adb
[Ada] rupees.adb
Bind
[gprbind] arith.bexch
[Ada] arith.ali
[gprbind] rupees.bexch
[Ada] rupees.ali
Link
[link] arith.adb
[link] rupees.adb
Success: Build finished successfully in … seconds.
$ ./bin/arith
A B A/B rem mod
7 2 3 1 1
-7 2 -3 -1 1
7 -2 -3 1 -1
-7 -2 3 -1 -1
18% of 1003 with /: 180
Rounded half up, (x + 50) / 100: 181
Integer'Last + 1: Constraint_Error, arith.adb:30 overflow check failed
$ ./bin/rupees 1205 99 100000 5 0
1205 paise = Rs 12.05
99 paise = Rs 0.99
100000 paise = Rs 1000.00
5 paise = Rs 0.05
0 paise = Rs 0.00
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 arith_session.py
The same divisions in Java and C, run on their own toolchains:
public class Division {
public static void main(String[] args) {
int[][] pairs = {{7, 2}, {-7, 2}, {7, -2}, {-7, -2}};
System.out.println(" a b a/b a%b floorMod");
for (int[] p : pairs) {
System.out.printf(
"%5d%5d%5d%5d%5d%n", p[0], p[1], p[0] / p[1], p[0] % p[1], Math.floorMod(p[0], p[1]));
}
int price = 1003;
System.out.println("18% of " + price + " with /: " + price * 18 / 100);
int big = Integer.MAX_VALUE;
big = big + 1;
System.out.println("Integer.MAX_VALUE + 1 = " + big);
int[] marks = {72, 95, 38, 48};
try {
System.out.println("marks[4] = " + marks[4]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("marks[4]: " + e.getMessage());
}
}
} Output
a b a/b a%b floorMod
7 2 3 1 1
-7 2 -3 -1 1
7 -2 -3 1 -1
-7 -2 3 -1 -1
18% of 1003 with /: 180
Integer.MAX_VALUE + 1 = -2147483648
marks[4]: Index 4 out of bounds for length 4
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Division.java
#include <stdio.h>
int main(void) {
int pairs[4][2] = {{7, 2}, {-7, 2}, {7, -2}, {-7, -2}};
printf(" a b a/b a%%b\n");
for (int i = 0; i < 4; i++) {
int a = pairs[i][0], b = pairs[i][1];
printf("%5d%5d%5d%5d\n", a, b, a / b, a % b);
}
int price = 1003;
printf("18%% of %d with /: %d\n", price, price * 18 / 100);
return 0;
} Output
a b a/b a%b
7 2 3 1
-7 2 -3 -1
7 -2 -3 1
-7 -2 3 -1
18% of 1003 with /: 180
Recorded with Apple clang version 21.0.0 (clang-2100.3.34.2) on macOS 26 arm64. To run it yourself: clang -std=c23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined division.c -o division && ./division
| Expression | Ada | C | Java |
|---|---|---|---|
| −7 divided by 2 | -7 / 2 = −3 |
-7 / 2 = −3 |
-7 / 2 = −3 |
| remainder, sign of the dividend | -7 rem 2 = −1 |
-7 % 2 = −1 |
-7 % 2 = −1 |
| remainder, sign of the divisor | -7 mod 2 = 1 |
no operator | Math.floorMod(-7, 2) = 1 |
| one more than the largest int | Constraint_Error | undefined behaviour | wraps to −2,147,483,648 |
All three languages use truncating division: Integer division that drops the fraction, rounding towards zero, so -7 divided by 2 gives -3. C, Objective-C and Visual Basic's \ divide this way, and their remainder takes the dividend's sign. More in “A one-week plan for learning any language”: C’s standard calls the result the
algebraic quotient with any fractional part discarded, and the Java Language Specification says integer division
rounds toward 0. They part company on overflow. The Java specification says the integer operators do not indicate
overflow in any way, and the C standard leaves the behaviour undefined; in Ada the Reference Manual raises
Constraint_Error for an operation that cannot deliver the correct result within the base range, and the GNAT User’s
Guide says overflow checking is on by default. The run said so: arith.adb:30 overflow check failed.
GST in whole rupees
Whole-number tax shows why the division rule matters. 18 % GST on ₹1,003 is ₹180.54, and 1003 * 18 / 100 drops
the fraction, so the Ada, C and Java programs all printed 180; a bill that must round half up adds 50 before
dividing and gets 181.
Key idea
Ada’s / and rem truncate like / and % in C and Java; mod takes the divisor’s sign like Java’s
Math.floorMod; and an integer overflow raises Constraint_Error instead of wrapping.
Checkpoint
Check: /, rem and mod, and overflow, against C and Java
An array keeps its bounds, and they can be any range
A C array starts at 0 and does not know its own length; a Java array starts at 0 and knows it. An Ada array type
names its index type, so an array can start at 1, at roll number 101 or at Monday, and it carries its bounds with it.
The A property of a type or an object, written with an apostrophe after its name, such as Class'First, Class'Length or Mark'Image.
'First, 'Last, 'Length and 'Range read them, so a loop never needs a separate count:
with Ada.Exceptions; use Ada.Exceptions;
with Ada.Text_IO; use Ada.Text_IO;
procedure Bounds is
type Mark is range 0 .. 100;
type Weekday is (Mon, Tue, Wed, Thu, Fri);
type Scores is array (Positive range <>) of Mark;
Class : constant Scores (1 .. 4) := [72, 95, 38, 48];
By_Roll : constant array (101 .. 104) of Mark := [72, 95, 38, 48];
Present : constant array (Weekday) of Natural := [38, 40, 39, 36, 40];
Seat : constant Positive := Positive'Value ("5");
begin
Put_Line ("Class'First =" & Class'First'Image & ", Class'Last ="
& Class'Last'Image & ", Class'Length =" & Class'Length'Image);
Put_Line ("By_Roll'First =" & By_Roll'First'Image & ", By_Roll'Last ="
& By_Roll'Last'Image);
for Roll in By_Roll'Range loop
Put_Line (" roll" & Roll'Image & ":" & By_Roll (Roll)'Image);
end loop;
for Day in Present'Range loop
Put_Line (" " & Day'Image & ":" & Present (Day)'Image & " present");
end loop;
Put_Line ("Class'Image:" & Class'Image);
Put_Line ("Class (5) is" & Class (Seat)'Image);
exception
when E : Constraint_Error =>
Put_Line ("Class (5): Constraint_Error, " & Exception_Message (E));
end Bounds; from session import run
# Arrays that carry their bounds, an index one past the end, two reductions of the same marks, and the version in
# wrong/ that applies 'Image to a reduction expression directly. -j1, after --, builds the two programs one at a time.
run("""
cd arrays
alr build -- -j1
./bin/bounds
./bin/totals
cp ../wrong/totals.adb src/totals.adb
!alr build -- -j1
""") Output
$ cd arrays $ alr build -- -j1 Note: Synchronizing workspace... Nothing to update. Note: Building arrays=0.1.0-dev/arrays.gpr... Setup [mkdir] object directory for project Arrays [mkdir] exec directory for project Arrays Compile [Ada] bounds.adb [Ada] totals.adb Bind [gprbind] bounds.bexch [Ada] bounds.ali [gprbind] totals.bexch [Ada] totals.ali Link [link] bounds.adb [link] totals.adb Success: Build finished successfully in … seconds. $ ./bin/bounds Class'First = 1, Class'Last = 4, Class'Length = 4 By_Roll'First = 101, By_Roll'Last = 104 roll 101: 72 roll 102: 95 roll 103: 38 roll 104: 48 MON: 38 present TUE: 40 present WED: 39 present THU: 36 present FRI: 40 present Class'Image: [ 72, 95, 38, 48] Class (5): Constraint_Error, bounds.adb:25 index check failed $ ./bin/totals Sum of 4 marks: 253 Mean, as / gives it: 63 Class'Reduce: Constraint_Error, totals.adb:14 overflow check failed $ cp ../wrong/totals.adb src/totals.adb $ alr build -- -j1 Note: Building arrays=0.1.0-dev/arrays.gpr... Compile [Ada] totals.adb totals.adb:9:61: error: prefix of attribute must be a name totals.adb:9:61: error: qualify expression to turn it into a name gprbuild: *** compilation phase failed ERROR: Command ["gprbuild", "-s", "-j0", "-p", "-P", "arrays/arrays.gpr", "-j1"] exited with code 4 ERROR: Compilation failed. [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 arrays_session.py
The program’s first part printed the bounds of three arrays:
| Attribute | Class (1 .. 4) |
By_Roll (101 .. 104) |
Present (Weekday) |
|---|---|---|---|
'First |
1 | 101 | MON |
'Last |
4 | 104 | FRI |
'Length |
4 | 4 | 5 |
for I in X'Range |
1, 2, 3, 4 | 101 … 104 | MON … FRI |
The weekdays printed in capitals because the Reference Manual makes the image of an enumeration value its identifier
in upper case. Class'Image is new in Ada 2022: it printed the whole array as [ 72, 95, 38, 48], each element
with the leading space of its own image, after a line break that the manual allows an implementation to add for
readability. And Class (5) stopped with index check failed, where Java throws
ArrayIndexOutOfBoundsException and in C reading past the end is undefined behaviour.
Loop over the array's own range
Write for I in Class'Range loop, or for M of Class loop when you need only the elements. The loop then follows
the bounds when the array changes, and a slip like C’s i <= n cannot happen.
Checkpoint
Check: Arrays that keep their bounds
’Reduce folds an array, and its accumulator’s type decides what fits
Summing marks is a loop in C and a stream in Java. Ada 2022 has
reduction expression: An Ada 2022 expression with the attribute Reduce: it starts an accumulator at an initial value and combines it with each value in turn, so [for M of A => M]'Reduce ("+", 0) sums A.: the Reference Manual says one starts an accumulator at
the initial value, then calls the reducer with the accumulator and each value in order, and yields the accumulator’s
final value. totals.adb sums the same four marks twice:
with Ada.Exceptions; use Ada.Exceptions;
with Ada.Text_IO; use Ada.Text_IO;
procedure Totals is
type Mark is range 0 .. 100;
type Mark_List is array (Positive range <>) of Mark;
Class : constant Mark_List := [72, 95, 38, 48];
Sum : constant Natural := [for M of Class => Natural (M)]'Reduce ("+", 0);
begin
Put_Line ("Sum of" & Class'Length'Image & " marks:" & Sum'Image);
Put_Line ("Mean, as / gives it:" & Natural'Image (Sum / Class'Length));
declare
Wrong : constant Mark := Class'Reduce ("+", 0);
begin
Put_Line ("Never printed:" & Wrong'Image);
end;
exception
when E : Constraint_Error =>
Put_Line ("Class'Reduce: Constraint_Error, " & Exception_Message (E));
end Totals; The second half of the session above ran it. The first reduction converts each mark to Natural and printed 253;
the second, Class'Reduce ("+", 0), uses the "+" of Mark, so its accumulator is a Mark too. Step through both:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- The initial value. [for M of Class => Natural (M)]'Reduce ("+", 0) starts its accumulator at the initial value 0. Every value is converted to Natural first, so the accumulator is a Natural.
- First value. "+" is called with the accumulator and the first value: 0 + 72 = 72.
- Second value. 72 + 95 = 167. A Natural goes up to Integer'Last, 2147483647, so 167 is no trouble.
- Third and fourth values. 167 + 38 = 205 and 205 + 48 = 253, the value the program printed as the sum of 4 marks.
- The result. The reduction yields the accumulator's last value, 253; the program bound it to the constant Sum, and Sum / 4 gave a mean of 63.
- The same marks in a Mark. Class'Reduce ("+", 0) uses the "+" of Mark, so its accumulator is a Mark, starting at 0.
- The second value does not fit. 0 + 72 = 72, then 72 + 95 = 167, outside even Mark'Base, -128 .. 127: Constraint_Error, totals.adb:14 overflow check failed. The other two marks are never added.
The Mark accumulator failed at the second value: 72 + 95 is 167, outside Mark'Base, so the run printed
overflow check failed, the check of the first section. The mean, Sum / Class'Length, printed 63, because
253 / 4 truncates 63.25. Choose the accumulator’s type for the total, not for the elements: a Natural holds the total
of more than 21 million marks of 100.
The last two commands of the session built a version with a common slip: Put_Line ([for M of Class => Natural (M)]'Reduce ("+", 0)'Image);. GNAT refused it with prefix of attribute must be a name, because an attribute applies
to a name and a reduction is an expression, and its second line names a fix, qualifying the expression with a type, as
in Natural'(…)'Image. Binding the result to a constant first, as totals.adb does with Sum, works too and reads
better.
Checkpoint
Check: Reduction expressions and their accumulator
A package splits what callers may use from how it works
A C header lists functions, and a Java class marks members public or private. An Ada package does it in two files:
the package specification: The part of an Ada package that other units may use, kept in a .ads file: its types, constants and subprogram declarations. The package body, in a .adb file, holds the code., grades.ads, lists what callers may use, and the
body, grades.adb, holds the code. Each parameter states its parameter mode: The direction of an Ada parameter: in, the default, which the subprogram can only read; out, which brings a result back; or in out, which is read and written.. The
Reference Manual makes in the default and a constant view, so the subprogram can only read it; out carries a
result back to the caller; in out is read and written. So a procedure returns two results through out
parameters without pointers, as Summary does:
grades/src/grades.ads
package Grades is
type Mark is range 0 .. 100;
type Mark_List is array (Positive range <>) of Mark;
function Passed (M : Mark) return Boolean is (M >= 40);
procedure Add_Grace (M : in out Mark; By : Mark);
-- Raise M by By marks, but never above 100.
procedure Summary
(List : Mark_List; Passes : out Natural; Best : out Mark);
-- Count the marks of 40 or more, and find the highest.
end Grades; grades/src/grades.adb
package body Grades is
procedure Add_Grace (M : in out Mark; By : Mark) is
begin
M := (if M > Mark'Last - By then Mark'Last else M + By);
end Add_Grace;
procedure Summary
(List : Mark_List; Passes : out Natural; Best : out Mark) is
begin
Passes := 0;
Best := Mark'First;
for M of List loop
if Passed (M) then
Passes := Passes + 1;
end if;
Best := Mark'Max (Best, M);
end loop;
end Summary;
end Grades; grades/src/report.adb
with Ada.Text_IO; use Ada.Text_IO;
with Grades; use Grades;
procedure Report is
Class : Mark_List := [72, 38, 98, 36];
Passes : Natural;
Best : Mark;
begin
Summary (Class, Passes, Best);
Put_Line ("Before grace:" & Passes'Image & " passes, best" & Best'Image);
for M of Class loop
Add_Grace (M, By => 5);
end loop;
Summary (Class, Passes, Best);
Put_Line ("After grace:" & Passes'Image & " passes, best" & Best'Image);
Put_Line ("Class now:" & Class'Image);
end Report; from session import run
# A package with an in out and two out parameters, used by report.adb; then the body in wrong/, which assigns to an
# in parameter.
run("""
cd grades
alr build
./bin/report
cp ../wrong/grades.adb src/grades.adb
!alr build
""") Output
$ cd grades $ alr build Note: Synchronizing workspace... Nothing to update. Note: Building grades=0.1.0-dev/grades.gpr... Setup [mkdir] object directory for project Grades [mkdir] exec directory for project Grades Compile [Ada] report.adb [Ada] grades.adb Bind [gprbind] report.bexch [Ada] report.ali Link [link] report.adb Success: Build finished successfully in … seconds. $ ./bin/report Before grace: 2 passes, best 98 After grace: 4 passes, best 100 Class now: [ 77, 43, 100, 41] $ cp ../wrong/grades.adb src/grades.adb $ alr build Note: Building grades=0.1.0-dev/grades.gpr... Compile [Ada] grades.adb grades.adb:6:07: error: assignment to "in" mode parameter not allowed compilation of grades.adb failed gprbuild: *** compilation phase failed ERROR: Command ["gprbuild", "-s", "-j0", "-p", "-P", "grades/grades.gpr"] exited with code 4 ERROR: Compilation failed. [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 grades_session.py
Step through the calls of report.adb and watch which of its variables each mode lets a procedure change:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Before the calls. report.adb declares Class with four marks and two variables with no value yet, Passes and Best.
- An in parameter. Summary (Class, Passes, Best) passes Class as List, in mode, the default: the procedure may read it and the compiler rejects any assignment to it.
- Two out parameters. Passes and Best are out parameters: Summary writes them before it returns. The program printed before grace: 2 passes, best 98.
- An in out parameter. The loop for M of Class passes each mark to Add_Grace as M, in out: the procedure reads the mark and writes the new one back, 72 becoming 77.
- Every mark, capped at 100. After four calls the class holds 77, 43, 100 and 41: 98 + 5 would pass 100, so Add_Grace stored Mark'Last instead.
- Summary again. A second Summary call writes Passes and Best afresh: 4 passes, best 100.
Summary read the class and wrote two answers, 2 passes and best 98; the loop then passed each mark to Add_Grace
in out, which capped 98 + 5 at Mark'Last, and the second summary found 4 passes and a best of 100. The broken body
in wrong/ assigned to By, an in parameter, and GNAT refused it: assignment to "in" mode parameter not allowed. The mode is part of the contract a caller reads in the specification, so a C reader no longer has to
guess whether a function writes through a pointer.
Key idea
The specification is all a caller sees, and its parameter modes say which way every value flows: an in
parameter cannot be changed, an out parameter brings a result back, and an in out parameter does both.
Checkpoint
Check: Package specifications and parameter modes
Practise: compute as Ada does, in Python
The exercise ports this lesson’s arithmetic to Python, which divides the other way: Ada’s /, rem, mod,
'Image and the Rupees_Of function of rupees.adb, whose run printed 1205 paise = Rs 12.05. Its sample tests
are the lines the lesson’s Ada programs printed, so a function that passes them reproduces Ada’s answers.
Exercise · Easy · Python
Compute as Ada does, in Python
You are porting an Ada report to Python and want Python to print exactly what the Ada program printed. Python's // and % round towards minus infinity, and str() adds no space, so write five small functions in ada_ops.py that work the way Ada does. The divisor b is never 0.
ada_div(a, b): Ada'sa / bon integers, which drops the fraction, so-7 / 2is-3.ada_rem(a, b): Ada'sa rem b, with the sign ofa, so thata == ada_div(a, b) * b + ada_rem(a, b).ada_mod(a, b): Ada'sa mod b, zero or with the sign ofb, so-7 mod 2is1and7 mod -2is-1.image(n): Ada'sInteger'Image (n): the digits with a leading space whennis zero or more, and with a leading minus sign when it is negative.rupees_of(paise): what the lesson'sRupees_Ofreturns forpaiseof zero or more:"Rs", the image of the whole rupees, a dot, and the last two digits of the image of100 + paise rem 100. So 1205 paise is"Rs 12.05".
The sample tests are the lines that arith.adb and rupees.adb printed in the lesson's session.
Starter code · ada_ops.py
def ada_div(a, b):
"""Ada's a / b on integers."""
return a // b # Python rounds down; Ada drops the fraction
def ada_rem(a, b):
"""Ada's a rem b: the sign of a."""
return a % b
def ada_mod(a, b):
"""Ada's a mod b: zero or the sign of b."""
return a % b
def image(n):
"""Ada's Integer'Image (n)."""
return str(n)
def rupees_of(paise):
"""What the lesson's Rupees_Of returns, for paise of zero or more."""
return "Rs " + str(paise / 100) The sample tests · test_ada_ops.py
from ada_ops import ada_div, ada_mod, ada_rem, image, rupees_of
def test_the_four_signs():
"""The table arith.adb printed: A, B, A/B, rem and mod"""
rows = [(7, 2, 3, 1, 1), (-7, 2, -3, -1, 1), (7, -2, -3, 1, -1), (-7, -2, 3, -1, -1)]
for a, b, quotient, remainder, modulus in rows:
assert ada_div(a, b) == quotient
assert ada_rem(a, b) == remainder
assert ada_mod(a, b) == modulus
def test_the_identity():
"""a = (a / b) * b + (a rem b) for every sign, and mod differs from rem only across signs"""
for a in (-9, -8, -1, 0, 1, 8, 9):
for b in (-4, -3, 3, 4):
assert a == ada_div(a, b) * b + ada_rem(a, b)
m = ada_mod(a, b)
assert m == 0 or (m > 0) == (b > 0)
assert ada_mod(-9, 4) == 3
assert ada_rem(-9, 4) == -1
assert ada_mod(9, -4) == -3
assert ada_mod(8, -4) == 0
def test_gst_in_whole_rupees():
"""18% of 1003 rupees with /, and rounded half up with (x + 50) / 100"""
assert ada_div(1003 * 18, 100) == 180
assert ada_div(1003 * 18 + 50, 100) == 181
def test_image():
"""Integer'Image puts a space before a number of zero or more"""
assert image(2147483647) == " 2147483647"
assert image(0) == " 0"
assert image(-7) == "-7"
assert image(-128) + " .." + image(127) == "-128 .. 127"
def test_rupees():
"""The five lines of ./bin/rupees 1205 99 100000 5 0"""
assert rupees_of(1205) == "Rs 12.05"
assert rupees_of(99) == "Rs 0.99"
assert rupees_of(100000) == "Rs 1000.00"
assert rupees_of(5) == "Rs 0.05"
assert rupees_of(0) == "Rs 0.00" A hint
Work with the sizes first: abs(a) // abs(b) is the size of Ada's quotient, and the quotient is negative exactly when a and b have different signs. rem then follows from a == quotient * b + rem, and mod differs from rem only when the two operands have different signs and the remainder is not 0.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Then check the five ideas of the lesson. The idea you have answered worst comes up first, and every choice explains itself as soon as you pick it:
Practice that adapts to you
Practise this lesson
12 questions about 5 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 mid-level): How do rem and mod differ in Ada, and which one matches C’s %?
Both give the remainder of an integer division; they differ only when the operands have different signs. A rem B
has the sign of A, the dividend, and goes with Ada’s /, which truncates toward zero, so that
A = (A / B) * B + (A rem B). A mod B is zero or has the sign of B, the divisor. So -7 rem 2 is −1 and
-7 mod 2 is 1, while 7 rem -2 is 1 and 7 mod -2 is −1. C’s % and Java’s % behave like rem; Java’s
Math.floorMod behaves like mod. Use mod when the result must stay in a range such as 0 to N − 1, a clock or a
ring buffer, whatever the sign of the input, and rem when you want C’s behaviour.
Two questions that come up when C or Java code meets Ada:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to senior
You port a C function that computes a check digit from an account number's digits with / and %, including adjustments that can go negative. The Ada port agrees on most inputs but not all. Where do you look first, and how do you make the port provably match?
-
Fresher to senior
A station timetable holds platform numbers, departure times as minutes past midnight and train numbers, all plain integers in the C code you inherit. Which Ada types do you declare, and how do you show a reviewer that a platform can no longer be passed where a time is expected?
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What does type Mark is range 0 .. 100; give you that a C typedef does not?
A new type, not a synonym: mixing a Mark with another integer type needs a conversion or the code does not compile, and storing 101 in a Mark raises Constraint_Error.
-
A Mark of 98 gets 5 grace marks. Where does Ada's range check stop it?
Only when 103 is stored in a Mark. The sum itself is computed in Mark'Base, −128 .. 127 with GNAT here, so printing it with Mark'Image showed 103.
-
What are -7 / 2, -7 rem 2 and -7 mod 2 in Ada?
−3, −1 and 1. As in C and Java, / and rem truncate; mod follows the sign of the divisor, as Java's Math.floorMod does.
-
What does Integer'Last + 1 do in Ada, in Java and in C?
Ada raises Constraint_Error, because GNAT checks overflow by default; Java silently wraps to −2,147,483,648; C leaves it undefined.
-
How does Ada code read an array's bounds, and what happens at an index past the end?
With 'First, 'Last, 'Length and 'Range, as in for I in Class'Range loop, so no separate count is needed. Class (5) on an array indexed 1 .. 4 stopped with index check failed.
-
Why did Class'Reduce ("+", 0) fail on the marks 72 and 95?
Its accumulator is a Mark, since it uses the "+" of Mark, and 72 + 95 = 167 is outside Mark'Base. Converting each mark to Natural first gave the total of all four, 253.
-
What do the parameter modes in, out and in out let a procedure do with a value?
in: only read it, and an assignment to it does not compile. out: hand a result back. in out: read it and write it. The mode is part of the specification a caller sees.
-
How do an Ada package's specification and body divide the work?
The specification, a .ads file, lists everything callers may use; the body, a .adb file, holds the code. Callers depend on the specification alone.
Key takeaways
type Mark is range 0 .. 100;makes a new type: mixing it with another integer type does not compile, storing 101 in it raises Constraint_Error, and its arithmetic runs in a wider base range, −128 .. 127 with GNAT here.- Ada’s
/andremtruncate like C’s and Java’s/and%,modtakes the divisor’s sign, and an overflow raised Constraint_Error where Java wraps and C has undefined behaviour. - An array keeps its bounds:
'First,'Last,'Lengthand'Rangeread them, an index past the end raisedindex check failed, and Ada 2022 can print a whole array with'Image. - A reduction’s accumulator has a type of its own: summing in a Natural gave 253, while the Mark accumulator failed at 72 + 95.
- A package specification lists what callers may use, and the parameter modes
in,outandin outsay which way each value flows; assigning to aninparameter does not compile.
Terms in this lesson
- base range
- The range an Ada integer type's arithmetic works in, chosen by the compiler to include at least the declared range and to be symmetric about zero: -128 .. 127 for a type declared 0 .. 100 with GNAT 16.
- Constraint_Error
- The predefined Ada exception raised when a value leaves the range of its subtype or the base range of its type, when an array index is out of bounds, or when an integer is divided by zero.
- package specification
- The part of an Ada package that other units may use, kept in a .ads file: its types, constants and subprogram declarations. The package body, in a .adb file, holds the code.
- parameter mode
- The direction of an Ada parameter: in, the default, which the subprogram can only read; out, which brings a result back; or in out, which is read and written.
- reduction expression
- An Ada 2022 expression with the attribute Reduce: it starts an accumulator at an initial value and combines it with each value in turn, so [for M of A => M]'Reduce ("+", 0) sums A.
References
- Ada Reference Manual 3.5.4: Integer Types (Ada Information Clearinghouse)
- Ada Reference Manual 3.2.2: Subtype Declarations (Ada Information Clearinghouse)
- Ada Reference Manual 4.5.5: Multiplying Operators (Ada Information Clearinghouse)
- Ada Reference Manual 11.5: Suppressing Checks (the language-defined checks) (Ada Information Clearinghouse)
- Ada Reference Manual 3.6.2: Operations of Array Types (Ada Information Clearinghouse)
- Ada Reference Manual 4.10: Image Attributes (Ada Information Clearinghouse)
- Ada Reference Manual 4.5.10: Reduction Expressions (Ada Information Clearinghouse)
- Ada Reference Manual 6.1: Subprogram Declarations (parameter modes) (Ada Information Clearinghouse)
- Ada Reference Manual 6.2: Formal Parameter Modes (Ada Information Clearinghouse)
- Ada Reference Manual 7.1: Package Specifications and Declarations (Ada Information Clearinghouse)
- GNAT User's Guide: Building executable programs with GNAT (-gnato and overflow modes) (AdaCore)
- The Java Language Specification, Java SE 25: 15.17 Multiplicative Operators (Oracle)
- The Java Language Specification, Java SE 25: 4.2.2 Integer Operations (Oracle)
- Java SE 25 API: Math.floorMod (Oracle)
- ISO/IEC 9899:2024 working draft N3220 (6.5 Expressions, 6.7.9 Type definitions) (ISO/IEC JTC 1/SC 22/WG 14)
- Introduction to Ada: Strongly typed language (AdaCore)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress