© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
C. MilanesiBeginning Rusthttps://doi.org/10.1007/978-1-4842-7208-4_2

2. Doing Arithmetic and Writing More Code

Carlo Milanesi1  
(1)
Bergamo, Italy
 
In this chapter, you will learn:
  • How to compute an arithmetic operation between integer numbers or between floating-point numbers

  • How to write a program containing several statements

  • How to print strings in several lines

Adding Integer Numbers

Let’s see how to compute the sum of two integer numbers, for example, 80 and 34.

Put the following line as the only contents within the braces of the main function:
print!("The sum is {}", 80 + 34);

The execution will print: The sum is 114.

The second argument of the print macro is the expression 80 + 34.

The compiler does not store into the executable file such numbers as they appear in source code, using the decimal format. The compiler may convert the two numbers into binary format, and then it may store into the executable file such binary numbers and the addition machine language instruction. At runtime, the two binary numbers are added, obtaining the number 114 in binary format.

But, as this expression contains only constant values, the compiler probably optimizes the addition operation by directly evaluating that expression, obtaining the integer number 114; then it stores that number, in binary format, into the executable file.

In both cases, at runtime, the resulting binary number is converted to the three-character string “114” in decimal format, and then the placeholder {} of the literal string is replaced by the generated string. Finally, of course, the resulting string is printed to the console.

Notice that the string The sum is 114 has been generated by the program; it is not present in source code, so it is still a string, but not a literal string.

Similarly, the two-character sequence 80 represents an integer number directly in the source code, and so it is called a literal integer. The same holds for the two characters 34. Instead, the integer number 114 is not a literal integer, as it does not appear in source code.

You can also write:
print!("{} + {} = {}", 34, 80, 80 + 34);

whose execution will print 34 + 80 = 114.

In such a case, the second argument of the macro will be put where there is the first placeholder, the third argument where there is the second placeholder, and the fourth argument where there is the third placeholder.

You can specify hundreds of arguments for the print macro, as long as the arguments after the first one are as many as the placeholders {} inside the first argument.

Other Operations Between Integer Numbers

All the integer arithmetic operators of C language can be used. For example, in this statement:
print!("{}", (23 - 6) % 5 + 20 * 30 / (3 + 4));

This will print 87.

Let’s see why.

Such a formula is evaluated by the Rust compiler exactly as the C compiler would.

First, the operations in parentheses, 23 - 6 and 3 + 4, are evaluated, obtaining, respectively, 17 and 7.

At this point, our expression has become 17 % 5 + 20 * 30 / 7.

Then, the multiplication and division operations are evaluated, as they have precedence over addition and subtraction. Operations of the same precedence are evaluated in order from left to right.

17 % 5 is the remainder of the integer division operation. The remainder of the operation 17 / 5 is 2. The expression 20 * 30 is evaluated before the following division, as it is at its left. Its result is 600.

At this point, our expression has become 2 + 600 / 7.

Then, the integer division with truncation 600 / 7 is performed, and so our expression becomes 2 + 85.

Finally, the sum is evaluated, the result is formatted in decimal notation, the placeholder is replaced by the formatted number, and the resulting string is printed.

These arithmetic operations are always performed on integer binary numbers, obtaining integer binary numbers, and the result is converted to decimal format only when it is used to replace the placeholder of the print macro.

The Rust compiler is highly optimizing, so it tries to evaluate directly at compile time the expressions that it is possible to evaluate using the information available in source code. Our expression is made only of literal integers, and so the whole expression will be evaluated already at compile time, storing into the executable program only the result to print. Though, conceptually, usually you can think that all the computations are performed at runtime.

Floating-Point Arithmetic

Let’s see how to compute the sum between two numbers with fractional parts. For example, let’s add 80.3 and 34.9.

Replace the statement with this:
print!("The sum is {}", 80.3 + 34.8);

It will print The sum is 115.1.

Now, in the second number only, replace the character 8 with a 9, obtaining:
print!("The sum is {}", 80.3 + 34.9);

It will print The sum is 115.19999999999999.

This will surprise those who would expect 115.2 as the result.

This phenomenon also happens in many other programming languages; it is due to the fact that Rust, like most programming languages, performs computations involving noninteger numbers using the floating-point format. But here we won’t treat this format any more.

All the arithmetic operators available for integer numbers are also available for floating-point numbers. For example, in this statement:
print!("{}", (23. - 6.) % 5. + 20. * 30. / (3. + 4.));

It will print 87.71428571428571.

Let’s see why.

By putting a dot after a literal number, it is transformed into a literal floating-point number. Some programming languages require a digit after the dot, but not Rust. The precedence rules are the same as those for integer arithmetic. Division has a different result, though.

Let’s see how the evaluation of the expression is performed.

The evaluation of 23. - 6. and of 3. + 4. is similar to that of integer numbers.

By evaluating 17. % 5., 2. is obtained, similarly to integer numbers. Such an operator does not exist in C language for floating-point numbers. It corresponds to the expression fmod(17., 5.) of the C standard library.

By evaluating 20. * 30., 600. is obtained, similarly to integer numbers.

The expression 600. / 7. performs a floating-point number division. The theoretical result of such division is a number that cannot be exactly represented either in binary notation or in decimal notation. Internally, a binary-format approximate representation is generated; if you ask Rust to convert such a binary number into a decimal format, you would get the approximate representation 85.71428571428571.

Finally, the value 2. is added to such a binary number, obtaining another value that cannot be exactly represented, which is printed in the way shown earlier.

Notice that, differing from C language, in Rust you cannot simply mix integer numbers and floating-point numbers. The following statement generates a compilation error:
print!("{}", 2.7 + 1);
A way to make it valid is to add a dot after the integer literal, making it a floating-point literal:
print!("{}", 2.7 + 1.);

However, this one is a syntax-only limitation, not an operative one; anyway, machine code cannot sum an integer number and a floating-point number without first converting one of the two operands to the type of the other operand. When a C compiler encounters the expression 2.7 + 1, it implicitly emits the machine language instruction to convert the integer number 1 to a floating-point number; or better, being that 1 is a constant, it is converted to a floating-point number at compile time. In Rust, such conversions must be explicit.

At last, a note about the “%” operator. This is often improperly named modulo operator. Well, it should be better named remainder operator , because the mathematical modulo operator has a different behavior for negative numbers.

The remainder operator, when applied to integer numbers, behaves in Rust exactly like the “%” operator in C language, and when applied to floating-point numbers it behaves exactly like the “fmodfunction of the C standard library. Here is an example with negative numbers, both integers and floating-point:
print!("{} {}", -12 % 10, -1.2 % 1.);

This will print -2 -0.19999999999999996.

Sequences of Statements

As the body of the main function , write these three lines:
print!("{} + ", 80);
print!("{} =", 34);
print!(" {}", 80 + 34);

This will print 80 + 34 = 114.

The program now contains three statements, each of them terminated by the “;” character. Such statements are executed in order of appearance.

If the body of the main function would become
    print!("{} + ",80);print!("{} = ",34);
            print  ! ( "{}"  ,
        80     + 34 )  ;

its result wouldn’t change. Actually, additional white spaces (blanks, tabs, and line breaks) are ignored.

However, Rust programmers have the following habits that are recommended:
  • Indent lines by four spaces inside functions.

  • Avoid adding several consecutive spaces inside statements.

  • Avoid exceeding 80 columns, possibly splitting long statements into several lines.

Breaking Literal Strings

As said before, to avoid code lines that are too long, you can break them at any point between syntax symbols, like in C language, though the syntax to break a literal string is different. This code is illegal:
println!("{}", "This"
    "is not allowed");
Actually, in Rust you cannot simply juxtapose literal strings, like in C. However, you can start a literal string in one line, and end it a few lines below. For example, this is a valid program (notice that this program and the others in this section explicitly specify the main function):
fn main() {
    println!("{}", "These
        are
        three lines");
}
This is what is printed:
These
        are
        three lines

As you can see, the literal string contains all the characters that in the source file are between the start and the end of the string, including newline characters and line leading spaces.

Maybe this is what you want, but you may need instead a single resulting line. In such case, you can write this:
fn main() {
    println!("{}", "This
        is
        just one line");
}
It will print:
This is just one line

By adding a backslash character (“”) inside a literal string, just before the end of a line, the resulting string will contain neither that end-of-line character nor the following spaces; therefore, the leading spaces of the next line are omitted. Given that we wanted at least one space before each break, we inserted such spaces just before the backslashes.

Finally, if we want to generate a single literal string containing several resulting lines, but with no leading white space, we can write this:
fn main() {
    println!("{}", "These
are
three lines");
}
or this:
fn main() {
println!("{}", "These
    are
    three lines");
}
Both will print:
These
are
three lines

The first solution has the drawback of being disrespectful of indentation conventions, and therefore usually the second solution is preferable. In such a solution, at the end of the lines there is the sequence , which is codified as a newline sequence, and then another backslash to exclude from the string the source code newline and the following spaces.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.218.1