Beginner

Operators in Ada

Master arithmetic, relational, logical, and string operators in Ada, including its uniquely safe approach to type-distinct arithmetic and operator overloading

Operators in Ada are the building blocks of every expression, from a simple counter increment to the complex calculations behind avionics control loops. Where languages like C silently mix integers and floats, Ada treats operators as strongly-typed subprograms — 1 + 1.0 is a compile-time error, not an implicit conversion. This safety-first design is one of the reasons Ada code can be trusted in flight control systems for decades.

As a statically and strongly typed language, Ada distinguishes carefully between operators on different numeric types. Integer division (/) truncates toward zero, mod and rem differ in their treatment of negative operands, and the exponentiation operator (**) is built into the language rather than relegated to a standard-library function. Equally important, every operator in Ada is really just a function — and you can overload them yourself for your own types.

This tutorial walks through the full set of Ada operators with runnable examples. You will see arithmetic on integers and floats, relational comparisons, short-circuit boolean logic, bitwise-style operations on modular types, string concatenation, and Ada’s distinctive mod vs. rem distinction.

Arithmetic Operators

Ada provides the familiar +, -, *, /, plus mod, rem, and ** (exponentiation). Crucially, the operands must have the same type — there is no implicit promotion from Integer to Float.

Create a file named arithmetic.adb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
with Ada.Text_IO;        use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Float_Text_IO;   use Ada.Float_Text_IO;

procedure Arithmetic is
   A : constant Integer := 17;
   B : constant Integer := 5;

   X : constant Float := 17.0;
   Y : constant Float := 5.0;
begin
   Put_Line ("Integer arithmetic:");
   Put ("  17 + 5  = "); Put (A + B, Width => 0); New_Line;
   Put ("  17 - 5  = "); Put (A - B, Width => 0); New_Line;
   Put ("  17 * 5  = "); Put (A * B, Width => 0); New_Line;
   Put ("  17 / 5  = "); Put (A / B, Width => 0); New_Line;
   Put ("  17 mod 5 = "); Put (A mod B, Width => 0); New_Line;
   Put ("  17 rem 5 = "); Put (A rem B, Width => 0); New_Line;
   Put ("  2 ** 10  = "); Put (2 ** 10, Width => 0); New_Line;

   New_Line;
   Put_Line ("Float arithmetic:");
   Put ("  17.0 / 5.0 = ");
   Put (X / Y, Fore => 1, Aft => 2, Exp => 0); New_Line;
end Arithmetic;

Two things stand out. First, integer division 17 / 5 truncates to 3 — no automatic float promotion. Second, 2 ** 10 is built into the language; you do not need to import a math package for exponentiation.

mod vs. rem: A Subtle Distinction

Ada is one of the few mainstream languages that provides both mod and rem. They differ only when the operands have different signs.

Create a file named mod_rem.adb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
with Ada.Text_IO;         use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure Mod_Rem is
   procedure Show (A, B : Integer) is
   begin
      Put ("A = "); Put (A, Width => 3);
      Put ("  B = "); Put (B, Width => 3);
      Put ("  A mod B = "); Put (A mod B, Width => 3);
      Put ("  A rem B = "); Put (A rem B, Width => 3);
      New_Line;
   end Show;
begin
   Show ( 13,  5);
   Show (-13,  5);
   Show ( 13, -5);
   Show (-13, -5);
end Mod_Rem;

The rule: rem takes the sign of the dividend (the left operand), while mod takes the sign of the divisor (the right operand). For positive operands they agree, but for negatives they differ — and for clock-style arithmetic where you want a non-negative result, mod is almost always what you want.

Relational and Logical Operators

Ada uses = and /= for equality and inequality (not == and !=), plus the usual <, <=, >, >=. Boolean operators come in two flavors: the regular and/or always evaluate both operands, while and then/or else short-circuit — they stop as soon as the result is determined.

Create a file named logical.adb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
with Ada.Text_IO; use Ada.Text_IO;

procedure Logical is
   Age      : constant Integer := 25;
   Has_ID   : constant Boolean := True;
   Is_Adult : constant Boolean := Age >= 18;
begin
   Put_Line ("Relational results:");
   Put_Line ("  Age = 25, Age >= 18 -> " & Boolean'Image (Is_Adult));
   Put_Line ("  10 /= 10            -> " & Boolean'Image (10 /= 10));
   Put_Line ("  'A' < 'B'           -> " & Boolean'Image ('A' < 'B'));

   New_Line;
   Put_Line ("Boolean combinators:");
   Put_Line ("  Has_ID and Is_Adult        -> "
             & Boolean'Image (Has_ID and Is_Adult));
   Put_Line ("  Has_ID and then Is_Adult   -> "
             & Boolean'Image (Has_ID and then Is_Adult));
   Put_Line ("  False or else (1 / 0 > 0)  -> short-circuit avoids divide-by-zero");
   Put_Line ("  not Is_Adult               -> "
             & Boolean'Image (not Is_Adult));

   --  xor is a real logical operator in Ada
   Put_Line ("  True xor False             -> "
             & Boolean'Image (True xor False));
end Logical;

The Ada Reference Manual deliberately forbids mixing and and or in the same expression without parentheses — for example, A and B or C is a syntax error. This forces you to write explicit grouping, eliminating an entire class of precedence bugs.

String Concatenation and Membership

Ada uses & for string (and array) concatenation, and the in / not in operators for range and membership tests — extremely handy for input validation.

Create a file named strings_membership.adb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
with Ada.Text_IO;         use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure Strings_Membership is
   First : constant String := "Ada";
   Last  : constant String := "Lovelace";
   Full  : constant String := First & " " & Last;

   Score : constant Integer := 87;
begin
   Put_Line ("Concatenated: " & Full);
   Put_Line ("Length: " & Integer'Image (Full'Length));

   if Score in 0 .. 100 then
      Put_Line (Integer'Image (Score) & " is a valid percentage");
   end if;

   if Score in 90 .. 100 | 80 .. 89 then
      Put_Line ("Grade: A or B");
   end if;

   if Score not in 0 .. 59 then
      Put_Line ("Passing grade");
   end if;
end Strings_Membership;

The in operator paired with a range (0 .. 100) or a set of alternatives (90 .. 100 | 80 .. 89) is a far cleaner way to express bounds checks than chains of >= and <=. Ada’s type system uses the same syntax to define range-constrained types like type Percentage is range 0 .. 100;.

Operator Overloading

Every operator in Ada is a function in disguise — A + B is sugar for "+"(A, B). This means you can define your own operators for your own types, which is essential for unit-safe arithmetic.

Create a file named overload.adb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
with Ada.Text_IO; use Ada.Text_IO;

procedure Overload is
   type Meters is new Float;

   function "+" (L, R : Meters) return Meters is
   begin
      return Meters (Float (L) + Float (R));
   end "+";

   function Image (M : Meters) return String is
   begin
      return Float'Image (Float (M)) & " m";
   end Image;

   Distance_A : constant Meters := 12.5;
   Distance_B : constant Meters := 7.5;
   Total      : constant Meters := Distance_A + Distance_B;
begin
   Put_Line ("A      = " & Image (Distance_A));
   Put_Line ("B      = " & Image (Distance_B));
   Put_Line ("A + B  = " & Image (Total));
end Overload;

Because Meters is a distinct type derived from Float, the compiler treats Meters + Meters as a different operator from Float + Float. Attempting Distance_A + 7.5 (a raw Float) would not compile — exactly the kind of error that contributed to the loss of the Mars Climate Orbiter.

Running with Docker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Pull the Ada image
docker pull codearchaeology/ada:latest

# Compile and run each example
docker run --rm -v $(pwd):/app -w /app codearchaeology/ada:latest \
  sh -c 'gnatmake arithmetic.adb && ./arithmetic'

docker run --rm -v $(pwd):/app -w /app codearchaeology/ada:latest \
  sh -c 'gnatmake mod_rem.adb && ./mod_rem'

docker run --rm -v $(pwd):/app -w /app codearchaeology/ada:latest \
  sh -c 'gnatmake logical.adb && ./logical'

docker run --rm -v $(pwd):/app -w /app codearchaeology/ada:latest \
  sh -c 'gnatmake strings_membership.adb && ./strings_membership'

docker run --rm -v $(pwd):/app -w /app codearchaeology/ada:latest \
  sh -c 'gnatmake overload.adb && ./overload'

Expected Output

Running arithmetic:

Integer arithmetic:
  17 + 5  = 22
  17 - 5  = 12
  17 * 5  = 85
  17 / 5  = 3
  17 mod 5 = 2
  17 rem 5 = 2
  2 ** 10  = 1024

Float arithmetic:
  17.0 / 5.0 = 3.40

Running mod_rem:

A =  13  B =   5  A mod B =   3  A rem B =   3
A = -13  B =   5  A mod B =   2  A rem B =  -3
A =  13  B =  -5  A mod B =  -2  A rem B =   3
A = -13  B =  -5  A mod B =  -3  A rem B =  -3

Running logical:

Relational results:
  Age = 25, Age >= 18 -> TRUE
  10 /= 10            -> FALSE
  'A' < 'B'           -> TRUE

Boolean combinators:
  Has_ID and Is_Adult        -> TRUE
  Has_ID and then Is_Adult   -> TRUE
  False or else (1 / 0 > 0)  -> short-circuit avoids divide-by-zero
  not Is_Adult               -> FALSE
  True xor False             -> TRUE

Running strings_membership:

Concatenated: Ada Lovelace
Length:  12
 87 is a valid percentage
Grade: A or B
Passing grade

Running overload:

A      =  1.25000E+01 m
B      =  7.50000E+00 m
A + B  =  2.00000E+01 m

Operator Precedence

Ada groups operators into six precedence levels, from highest to lowest:

LevelOperatorsNotes
1 (highest)**, abs, notExponentiation and unary
2*, /, mod, remMultiplying
3unary +, -Sign
4binary +, -, &Adding and concatenation
5=, /=, <, <=, >, >=, in, not inRelational
6 (lowest)and, or, xor, and then, or elseLogical

Because and and or share the lowest level and cannot be mixed without parentheses, expressions like A and B and C are fine but A and B or C requires explicit grouping: (A and B) or C.

Key Concepts

  • Strong typing applies to operators: mixed-type expressions like 1 + 1.0 are compile errors; convert explicitly with Float (1) + 1.0.
  • mod vs. rem: rem follows the dividend’s sign, mod follows the divisor’s — use mod for wraparound-style arithmetic.
  • ** is built-in: integer and float exponentiation needs no library import.
  • Short-circuit forms are explicit: and then / or else short-circuit, and / or always evaluate both sides.
  • xor is a first-class logical operator, and mixing and with or without parentheses is forbidden by the syntax — a built-in defense against precedence bugs.
  • in and not in test ranges and membership cleanly: X in 0 .. 100 beats X >= 0 and X <= 100.
  • & concatenates strings and arrays; it has lower precedence than arithmetic but higher than comparison.
  • Every operator is overloadable: define function "+" (L, R : My_Type) return My_Type to give your own types arithmetic.

Running Today

All examples can be run using Docker:

docker pull codearchaeology/ada:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining