Write a function that returns the multiplication of the first two arguments if the third argument is an even number or performs an addition if odd.
Solution
As the exercise describes, our function is going to have 3 parameters and it’s going to return a single value so let’s create the function signature first.
func add_if_odd_multiply_if_even(
first_op : felt, second_op : felt, op_selector : felt
) -> (result : felt):
end
To use our function we would have to call it from main passing the required arguments.
func main():
let (result) = add_if_odd_multiply_if_even(
first_op=2, second_op=3, op_selector=5
)
return ()
end
func add_if_odd_multiply_if_even(
first_op : felt, second_op : felt, op_selector : felt
) -> (result : felt):
end
To test that everything is working correctly as we go, let’s hardcode for now a return value from our function and print it to the terminal. Because we are passing an odd number as the op_selector (7), we should expect the function to perform an addition of the first two arguments and return the number 5.
%builtins output
from starkware.cairo.common.serialize import serialize_word
func main{output_ptr : felt*}():
let (result) = add_if_odd_multiply_if_even(
first_op=2, second_op=3, op_selector=7
)
serialize_word(result)
return ()
end
func add_if_odd_multiply_if_even(
first_op : felt, second_op : felt, op_selector : felt
) -> (result : felt):
return (5)
end
To print the result of calling our function to the terminal we will need to use the function serialize_word which has the following signature.
func serialize_word{output_ptr : felt*}(value : felt)
Notice that the function expects one argument called value and an implicit argument called output_ptr, a pointer to a special memory segment within the Prover. To pass the implicit argument, we need to provide the implicit argument to the caller function, in this case main. Because we need the special pointer output_ptr, we have to add the line %builtins output at the very top of our file so the implicit argument is passed to main by the system.
If we compile and execute our code so far we should be able to see the number 5 in the terminal.
$ cairo-compile exercise.cairo --output exercise.json
$ cairo-run --program=exercise.json --print_output --layout=small
>>>
Program output:
5
Now that the general skeleton of our program is completed, we can focus on the implementation of our function. To test if the parameter op_selector is an even or odd number, we would normally do something like:
if op_selector % 2 == 0:
# is even
else:
# is odd
end
The problem is that the remainder operator % is not supported by Cairo. Instead we will have to rely on the math library. Below is a list of some of the functions exposed by the math library.
# Verifies that value != 0
func assert_not_zero(value)
# Verifies that a != b
func assert_not_equal(a, b)
# Verifies that a >= 0
func assert_nn{range_check_ptr}(a)
# Verifies that a <= b
func assert_le{range_check_ptr}(a, b)
# Verifies that a <= b - 1
func assert_lt{range_check_ptr}(a, b)
# Verifies that 0 <= a <= b.
func assert_nn_le{range_check_ptr}(a, b)
# Asserts that value is in the range [lower, upper).
func assert_in_range{range_check_ptr}(value, lower, upper)
# Returns the absolute value of value.
func abs_value{range_check_ptr}(value) -> (abs_value : felt)
# Returns q and r such that:
# 0 <= q < rc_bound, 0 <= r < div and value = q * div + r.
func unsigned_div_rem{range_check_ptr}(value, div) -> (q : felt, r : felt)
# Returns q and r such that:
# -bound <= q < bound, 0 <= r < div and value = q * div + r.
func signed_div_rem{range_check_ptr}(value, div, bound) -> (q : felt, r : felt)
# Returns the floor value of the square root of the given value.
func sqrt{range_check_ptr}(value) -> (res : felt)
In our case we are interested in the function signed_div_rem as it will give us back the remainder of a division. Notice that the function requires the implicit argument range_check_ptr. This special pointer can be made available to our program by enabling the range_check built in.
%builtins output range_check
from starkware.cairo.common.serialize import serialize_word
from starkware.cairo.common.math import signed_div_rem
func main{output_ptr : felt*, range_check_ptr}():
let (result) = add_if_odd_multiply_if_even(first_op=2, second_op=3, op_selector=7)
serialize_word(result)
return ()
end
func add_if_odd_multiply_if_even{range_check_ptr}(
first_op : felt, second_op : felt, op_selector : felt
) -> (result : felt):
let (_, r) = signed_div_rem(value=op_selector, div=2, bound=1000)
if r == 0:
return (first_op * second_op)
else:
return (first_op + second_op)
end
end
Pay attention to how the implicit argument range_check_ptr is passed. By adding the builtin range_check at the top of our code, the pointer is added to main as the second implicit argument of the function (implicit arguments are passed to main in the same order their respective builtins are defined at the top of our code).
Because the range_check_ptr is needed by the signed_div_rem function, we have to define this implicit argument for our function add_if_odd_multiply_if_even so that the system knows that it has to implicitly pass the pointer from main to our function and then to the library function.
The third argument for signed_div_rem, bound, is required by the library to limit the size of the division. We are basically telling the function that we will never create a division whose quotient is bigger than 1000. If we do, the function will fail. The number 1000 was chosen at random, we could have chosen an even bigger number if we wanted to.
Compiling and running the final version of our code we can see that it is working as expected and that the exercise has been solved.
$ cairo-compile exercise.cairo --output exercise.json
$ cairo-run --program=exercise.json --print_output --layout=small
>>>
Program output:
5
Sigo tus publicaciones con interés.