# Chapter 4
Among all the instructions that you can execute in a smart contract, arithmetic instructions are probably the most common ones, but also the simplest ones. After all, computers in general were created to provide an easy way to do arithmetic operations that started to become too complex.
Within your smart contract, you may want to add or subtract tokens to the balance of your users or you may want to multiply or divide different values. As you will understand it when reading what follows, it will be extremely easy in Michelson. You only have two conditions to remember before trying any operation: first, you must make sure that you have two elements on top of the stack. Second, you must verify that these two elements are of numeric types and of types that work together.
Unlike programming languages that you may already be familiar with, Michelson doesn't use arithmetic operators you know like +, -, * or /. Everything is "instruction-based" and you will have to use instructions that modify the stack to make operations.
# Available types for arithmetic operations
As you may have already guessed, Michelson allows us to work with int
(positive and negative values) and nat
(positive values and 0) for arithmetic operations. An operation of two int
will yield an int
and an operation of twonat
will yield a nat
, except for a subtraction that yields an int
. These two types can be used together for addition, subtraction, multiplication and division but as long there is an int
in your operation, the result will also be an int
. For example, int + nat = int
.
In addition to int
and nat
, you can use other types for arithmetic operations, for example mutez
(one millionth of a tez) and timestamp
(Unix time in seconds since 1970). Here is a table that sums up all the possible combinations and the type of the result:
Value type | Available operation | Value type | Result type |
---|---|---|---|
int | ADD/SUB/MUL/EDIV | int | int |
nat | ADD/MUL/EDIV | nat | nat |
nat | SUB | nat | int |
int | ADD/SUB/MUL/EDIV | nat | int |
timestamp | ADD/SUB | int | timestamp |
timestamp | SUB | timestamp | int |
mutez | ADD/SUB | mutez | mutez |
mutez | MUL/EDIV | nat | mutez |
A few considerations to keep in mind regarding the table above:
- Be always very mindful about the return type when you put
int
andnat
together. You may spend some time scratching your head and wondering why you are not getting the type you are expecting because you overlooked the types you are using in your operation. - A few operations are available for types outside of
int
andnat
, but they are somehow limited (for logical reasons). For example, it wouldn't make any sense to multiply timestamps with other values or multiplymutez
together. - Other limitations are set in place for safety purposes: limiting the possible operations on
mutez
prevents negative balances.
# The ADD
instruction
The first instruction we are going to play with is the ADD
instruction. As its name suggests, it takes two values and add them together. If you refer to the table above, you can observe that it is one of the most widely available instruction throughout the different types. ADD
also allows you to use different types of values, keeping in mind that the return type is always predetermined by the values you add.
Let's check some examples and see how it works:
## We quickly initialize a contract environment to manipulate different values on the stack
storage unit ;
parameter unit ;
BEGIN Unit Unit ;
DROP ;
stdout
storage unit; parameter unit; BEGIN: use %default; drop all; push (Unit, Unit); DROP: pop (Unit, Unit);
In this first example, we push 2 int
onto the stack and add them.
As you can see, the return value is of type int
and is the result of 5 + 5
.
PUSH int 5 ;
PUSH int 5 ;
ADD ;
stdout
PUSH: push 5; PUSH: push 5; ADD: pop 5, 5; push 10;
Now we have a value of type int
on top of the stack. Let's push a value of type nat
and see what happens:
PUSH nat 5 ;
ADD ;
stdout
PUSH: push 5; ADD: pop 5, 10; push 15;
As expected, int 10 + nat 5
equals int 15
. The addition of int
and nat
always yields an int
(for logical reasons, the result of something like int -20 + nat 5
cannot be a nat
value).
In the next snippet, you will see a new instruction, NOW
. We will come back to it in a later chapter, just know for now that it pushes the current timestamp on top of the stack. We can then use the timestamp to demonstrate how ADD
works with values of this type:
NOW ;
ADD ;
stdout
NOW: push 1592111017; ADD: pop 1592111017, 15; push 1592111032;
According to the day and time you are running this code block, the timestamp will be different. However, you should clearly see that the initial value has been incremented with 15
, which was the value we had in our stack when we pushed the timestamp. You can also push a value and add it to the existing timestamp:
PUSH int 500 ;
ADD ;
stdout
PUSH: push 500; ADD: pop 500, 1592111032; push 1592111532;
Let's clean our stack and see how adding mutez
together works. As in the previous examples, we just push two values on top of the stack and add them. This yields a result in mutez
:
DROP ;
PUSH mutez 50 ;
PUSH mutez 20 ;
ADD ;
stdout
DROP: pop 1592111532; PUSH: push 50; PUSH: push 20; ADD: pop 20, 50; push 70;
Keep in mind that when using mutez
, you are not moving funds but operating with amounts. When writing PUSH mutez 50
, you are not manipulating tez, only their representation. There are specific instructions we will study later that tell the contract to send actual tez.
# The SUB
instruction
After adding different amounts comes a time when you have to subtract 😅 The SUB
instruction works very similarly to the ADD
instruction outside of a few exceptions we are going to study here.
First, let's start with a simple example:
DROP ; ## let's start with a clean stack
PUSH int 3 ;
PUSH int 5 ;
SUB ;
stdout
DROP: pop 70; PUSH: push 3; PUSH: push 5; SUB: pop 5, 3; push 2;
In this example, you can see that we push 3
to the stack, then 5
before subtracting 3
from 5
.
In a subtraction, the order of the elements is essential, this is why you have to remember the right order of the elements in the stack: 5 - 3
is not going to yield the same result as 3 - 5
!
Let's see what would happen if we push the values in the reverse order:
DROP ;
PUSH int 5 ;
PUSH int 3 ;
SUB ;
stdout
DROP: pop 2; PUSH: push 5; PUSH: push 3; SUB: pop 3, 5; push -2;
As you can see, you get now -2
. This may be what you want, as integers can have negative values.
SUB
is going to work a little differently for nat
values:
DROP ;
PUSH nat 5 ;
PUSH nat 3 ;
SUB @first_value ;
PUSH nat 3 ;
PUSH nat 5 ;
SUB @second_value ;
DUMP ;
stdout
DROP: pop -2; PUSH: push 5; PUSH: push 3; SUB: pop 3, 5; push -2; PUSH: push 3; PUSH: push 5; SUB: pop 5, 3; push 2;
Something very interesting happens here, can you spot it? We started with nat
values and we end up with int
values! The result of the subtraction of 2 nat
values is always an int
as there is a possibility for a negative number. Now, an int
value may not be what you want. Maybe you are updating a nat
value in your storage and you need a nat
value to put it back in the storage. In this case, you can use the ABS
instruction. ABS
turns an int
value into a nat
value. As usual, make sure that the top element of the stack is of type int
before using it:
ABS @second_value ;
SWAP ;
ABS @first_value ;
DUMP ;
stdout
ABS: pop 2; push 2; SWAP: pop 2, -2; push 2; push -2; ABS: pop -2; push 2;
You can also use SUB
with timestamps. In this case, you can only subtract int
values from timestamps that represent the number of seconds to subtract, for example:
DROP_ALL ; ## DROP_ALL is a non-Michelson instruction used in these notebooks to reset the stack to zero
PUSH int 100 ;
NOW ;
SUB ;
stdout
DROP_ALL: drop all; PUSH: push 100; NOW: push 1592111022; SUB: pop 1592111022, 100; push 1592110922;
In the steps of the execution, you can observe that the current timestamp is pushed onto the stack before 100
is subtracted from it.
If you wish, you can also subtract one timestamp from another to get the difference in seconds between both of them:
DROP ;
PUSH int 100 ;
NOW ;
SUB ;
NOW ;
SUB ;
stdout
DROP: pop 1592110922; PUSH: push 100; NOW: push 1592111024; SUB: pop 1592111024, 100; push 1592110924; NOW: push 1592111024; SUB: pop 1592111024, 1592110924; push 100;
In a first time, we push int 100
onto the stack, then the current timestamp and we subtract 100
from the timestamp. Next, we push another timestamp onto the stack and subtract the first created timestamp from the second one. The result should be 100
because the NOW
instruction returns the same timestamp throughout the execution of the contract.
As you would expect, it is also possible to subtract mutez from one another to get the difference between the two amounts:
DROP ;
PUSH mutez 25 ;
PUSH mutez 50 ;
SUB ;
stdout
DROP: pop 100; PUSH: push 25; PUSH: push 50; SUB: pop 50, 25; push 25;
Once again, be careful of the order you push the elements onto the stack, for safety purposes, negative amounts of mutez
are not allowed and the contract will fail if it happens:
DROP ;
PUSH mutez 50 ;
PUSH mutez 25 ;
SUB ;
stdout
DROP: pop 25; PUSH: push 50; PUSH: push 25; SUB: pop 25, 50;
stderr
MichelsonRuntimeError: expected non-negative val at SUB
# The MUL
instruction
Multiplications are far less common throughout Michelson types than addition or subtraction. For timestamps, they just don't make sense. For mutez
, they are possible only under a certain condition. You can use it for int
and nat
values. The syntax is the same as addition and subtraction and this time, you can relax about the order of the elements 😊
DROP ;
PUSH int 5 ;
PUSH int 4 ;
MUL ;
stdout
DROP: pop 25; PUSH: push 5; PUSH: push 4; MUL: pop 4, 5; push 20;
DROP ;
PUSH nat 3 ;
PUSH nat 10 ;
MUL ;
stdout
DROP: pop 20; PUSH: push 3; PUSH: push 10; MUL: pop 10, 3; push 30;
As it is also the case for addition and subtraction, the multiplication of int
and nat
values together yields a new int
:
DROP ;
PUSH int 5 ;
PUSH nat 3 ;
MUL ;
stdout
DROP: pop 30; PUSH: push 5; PUSH: push 3; MUL: pop 3, 5; push 15;
Finally, you can multiply mutez
values with nat
values:
DROP ;
PUSH mutez 500 ;
PUSH nat 5 ;
MUL ;
stdout
DROP: pop 15; PUSH: push 500; PUSH: push 5; MUL: pop 5, 500; push 2500;
# The EDIV
instruction
Division in Michelson is a little more technical than the other arithmetic operations and requires more explanation.
Michelson performs what is called a Euclidean division, hence the E(uclidean)DIV(ision) name. In a nutshell, a Euclidean division divides two numbers together and returns a result (formally known as the quotient) and a remainder (there are no float numbers in Michelson). If there is a pizza with 9 slices and 4 people who want pizza, a Euclidean division of the pizza will distribute 2 slices to everyone (total 8 slices) with 1 slice left.
Keeping in mind this example, let's observe how the division and its result would look like in Michelson:
DROP ;
PUSH int 4 ;
PUSH int 9 ;
EDIV ;
stdout
DROP: pop 2500; PUSH: push 4; PUSH: push 9; EDIV: pop 9, 4; push ((2, 1),);
Now that's a surprise! The other arithmetic operations above only yield numbers but this one gives us a pretty complex result back! The result of EDIV
is an optional that contains a pair with the quotient (the result of the division) on the left and the remainder on the right. The quotient is of type int
and the remainder is of type nat
(unsurprisingly, the remainder cannot be a negative number).
Outside of int
and nat
, you can use EDIV
with mutez
values, only if the divisor is of type nat
:
DROP ;
PUSH nat 5 ;
PUSH mutez 500 ;
EDIV ;
stdout
DROP: pop ((2, 1),); PUSH: push 5; PUSH: push 500; EDIV: pop 500, 5; push ((100, 0),);
The example above is also great to demonstrate that the pair result can contain a 0
as the remainder of the division.
# Other operations on numeric values
Outside of arithmetic operations, you can use other operations to manipulate numeric values in Michelson. We've already talked about ABS
earlier that turns an int
value into a nat
. Here is a refresher:
DROP ;
PUSH int 5 ;
ABS ;
PUSH int -6 ;
ABS ;
DUMP ;
stdout
DROP: pop ((100, 0),); PUSH: push 5; ABS: pop 5; push 5; PUSH: push -6; ABS: pop -6; push 6;
You can use NEG
to turn a positive value into a negative one. The result will logically be an int
even if the value you give is a nat
:
DROP_ALL ;
PUSH int 5 ;
NEG ;
PUSH nat 5 ;
NEG ;
DUMP ;
stdout
DROP_ALL: drop all; PUSH: push 5; NEG: pop 5; push -5; PUSH: push 5; NEG: pop 5; push -5;
Sometimes, you may want to flip the type of a number from nat
to int
the same way ABS
turns an int
into a nat
. In this case, you can use the INT
instruction:
NOTE
you may have noticed by now that Michelson is case-sensitive, writing int
and INT
is completely different, as is writing pair
, Pair
and PAIR
. Types are always written in lowercase (pair
), values are written capitalized (Pair
) and instructions are all uppercase (PAIR
).
DROP_ALL ;
PUSH nat 5 ;
INT ;
DUMP ;
stdout
DROP_ALL: drop all; PUSH: push 5; INT: pop 5; push 5;
Michelson offers also a practical shortcut to check if an int
is positive and turn it into a nat
: ISNAT
. The instruction takes a value of type int
and returns None
if the int
is less than 0
or (Some nat)
if the int
is greater than 0
:
DROP ;
PUSH int 5 ;
ISNAT ;
PUSH int -4 ;
ISNAT ;
DUMP ;
stdout
DROP: pop 5; PUSH: push 5; ISNAT: pop 5; push (5,); PUSH: push -4; ISNAT: pop -4; push None;