Rational Numbers Tutorial
This tutorial shows how to use Nada Numpy Rational datatypes to work with fixed-point numbers in Nada.
Notions
This tutorial uses fixed-point numbers as it is the only available way to use Fixed-Point numbers in Nada. The representation of a fixed-point number uses integer places to represent decimal digits. Thus, every number is multiplied by a scaling factor, that we refer to as SCALE
, generally chosen to be a power of 2 where the exponent represents the number of bits. In a nutshell, SCALE
is going to reserve a number of bits for the exponent.
If we want to input a variable a = float(3.2)
, we need to first encode it. For that, we will define a new variable a'
which is going to be the scaled version. In this case, the scaling factor (to simplify) is going to be 3 bits so our scale would be SCALE = 2 ** 3 # 8
. By multiplying our variable a
with SCALE
we obtain the encoded value. To decode, we just need to make the division by the scale.
BITS = 3
SCALE = 2 ** BITS #8
a = float(3.2)
# Encoding
a_encoded = round(a * SCALE) # round(3.2 * 8) = 26
# Decoding
a_decoded == a_encoded / SCALE # 26 / 8 = 3.25
Thus, to introduce a value with 3 bits of precision, we would be inputting 26 instead of 3.2. Note that, the larger the BITS
precision, the better result we would obtain when decoding.
Nada Numpy uses a default value of 16 bits for decimal scale. If you want to change it, you can do so with:
na.set_log_scale(BITS)
Example
from nada_dsl import *
import nada_numpy as na
def nada_main():
# We define the number of parties
parties = na.parties(3)
# We use na.SecretRational to create a secret rational number for party 0
a = na.secret_rational("my_input_0", parties[0])
# We use na.SecretRational to create a secret rational number for party 1
b = na.secret_rational("my_input_1", parties[1])
# This is a compile time rational number
c = na.rational(1.2)
# The formula below does operations on rational numbers and returns a rational number
# It's easy to see that (a + b - c) is both on numerator and denominator, so the end result is b
out_0 = ((a + b - c) * b) / (a + b - c)
return [
Output(out_0.value, "my_output_0", parties[2]),
]
Let’s break this down step-by-step.
1. Import Section
Start by importing the necessary modules. We need Nada DSL and Nada Numpy, so we import them like this:
from nada_dsl import *
import nada_numpy as na
Next, define the main function for our program:
def nada_main():
2. Party Declaration Section
In this section, we’ll declare the parties involved in our computation. Parties represent the different participants in our Nada program. Nada Numpy makes it easy to create multiple parties with the na.parties
function:
parties = na.parties(3)
This line creates a list of three parties named: Party0
, Party1
, and Party2
.
3. Input Declaration Section
Now, let’s define our inputs. We’ll create two secret rational numbers using na.SecretRational
. This function allows us to define rational numbers with a specific owner and name.
a = na.secret_rational("my_input_0", parties[0])
This line creates a secret rational number a
owned by Party0
, named “my_input_0”
.
Similarly, we define the second secret rational number b
:
b = na.secret_rational("my_input_1", parties[1])
This secret rational number is owned by Party1
, named “my_input_1”
.
We also define a compile-time rational number c
:
c = na.rational(1.2)
This line creates a compile-time rational number c
with a value of 1.2.
4. Computation Section
With our inputs ready, we can perform computations. In this example, we compute a specific rational operation.
out_0 = ((a + b - c) * b) / (a + b - c)
This line performs the operation (a + b - c) * b / (a + b - c)
, which simplifies to b
because the numerator and denominator are the same.
5. Output Section
Finally, we need to produce the output. Since SecretRational
is not a base Nada type, we use the Output
method to generate the output. This method takes the value, output party, and output variable name as arguments:
return [
Output(out_0.value, "my_output_0", parties[2]),
]
In this case, we specify that the output party will be Party2
and the name of the output variable will be "my_output_0"
.
With everything in place, we can build and test our program:
nada build
(Optional) Next, ensure that the program functions correctly by testing it with:
nada test
Finally, we can call our Nada program via the Nillion Python client by running:
python3 main.py
And that’s it! You’ve successfully created, built, and integrated a Nada Numpy Rational numbers program.
For more examples, please visit our Github Repository Examples.