More about variables, expressions and assignment
Comments and questions to John Rowe.
Arithmetic expressions use the symbols "+
- * /" for the arithmetic operations plus, minus,
multiply and divide. They have the standard arithmetic
precedence (*/ bind more closely than +-)
and left-to-right evaluation. Parenthesis ()
have the usual grouping effect. The
following examples expressions on the same line have the
same value to within rounding errors:
x * y + z |
(x*y) + z |
|
1.5 - x - y |
(1.5 - x ) - y |
|
x/y/z |
(x/y)/z |
x/(y*z) |
Having calculated an expression we can use it as the argument
to a function such as: sqrt(x*x+y*y)
No prizes for guessing what the sqrt() function
does! (We shall see below there is also a useful function hypot()).
The most common use is probably the one we one we showed
above, putting it on the right-hand side of "variable = ".
Example: height of a plane
If we are writing a program dealing with a plane of gradient
1 in both the x and y directions (i.e. an absolute gradient of
sqrt(2)), and we want to know the distance of a point on the
plane from the origin, then our code might look like this
(where we have added a couple of "interesting" lines at the
end):
#include <stdio.h>
#include <math.h>
int main() {
double x, y, z, distance;
x = 2.1;
y = 1.3;
z = x + y;
distance = sqrt(x*x + y*y + z*z);
printf("Distance; %g\n", distance);
x = 10.0;
y = y + x;
printf("Distance; %g\n", distance);
return 0;
}
Step through this code
Remember: z will not be exactly 3.4 because the
computer works in binary and neither 1.3 , 2.1 , or 3.4 are
expressible in a finite number of "binary places".
An operator is
something that takes one or more arguments and produces a
result.
Mathematical operators have the same precedence
(bind as closely) as normal arithmetic.
- Step through the above "Key example".
- To demonstrate assignment
- Step through the above "Key example" in a new window.
- Press "Start program" and see the variables
spring into existence with random value.
- Step through the code one step at a time stopping just
before the second assignment of x (x = 10.0)
- Notice the extremely simplistic way the computer performs
calculations, and stores and retrieves the values.
It is essential to realise that the
statements above with equals signs such as:
z = x + y;
are one-off arithmetic
assignments, not lasting mathematical relationships.
(Remember, we are dealing with algorithms
which are sequences of instructions or actions to achieve the
desired result.)
When it gets to the statement "x = 10.0;"
the compiler will not think to itself "z equals x
+ y, so z is now equal to 11.3 and I must
recalculate the distance". The assignment of z was a
one-off action so z will
remain (approximately) equal to 3.4 until we explicitly change
it, and the same applies to distance.
Of course the compiler doesn't "think" at all
and it certainly doesn't realise the significance of the word
"distance". For all it cared we could have called the four
variables (x, y, z and distance)
"variable1", "variable2" , "variable3"
and "i_feel_like_a_banana". But the program would
have been much less clear to us.
- Continue stepping through the above example
- To demonstrate that assignment is an action not a
relationship and has no side-effects
- Continue stepping through the above code
changing the values of x and y.
- Notice how the value of z does not change.
Statements such as: z = x + y; are one-off arithmetic assignments, not
lasting mathematical relationships.
If we change the value of a variable there is
no way to retrieve it later. For example, in the following
code the original values of the variables materials
and labour are lost when we assign them new values:
#include <stdio.h>
int main() {
double materials, labour, total_cost;
materials = 9.4;
labour = 11.3;
total_cost = materials + labour;
printf("The first cost is %g\n", total_cost);
materials = 14.3;
labour = 21.2;
total_cost = materials + labour;
printf("The second cost is %g\n", total_cost);
return 0;
}
Step through this code
When the value of a variable changes
its old value is gone forever
- Change the value of mass or velocity in your momentum calculation
- To illustrate that changing the value of a variable does not
automatically change the values of variables previously calculated from it
- Find your "momentum" exercise from the last lesson.
- Copy-and-Paste the printf() statement that prints out
the mass, velocity
and momentum so that you now have two copies, on two separate lines.
Build & run and check that it does print them out twice.
- In between these two lines insert a line that changes the value of
either the mass or velocity (or insert two lines and change hem both).
- What does your program produce now?
- Does the second value of the momentum
reflect the new value of mass and/or velocity?
- Do you understand why/why not?
If not, step through the example above again.
- Finally, copy the line with the momentum calculation
and paste it into the correct
place for it to update the momentum to the correct new value.
- What is the corrrect place to put this second calculation?
Think through the steps the program will take and do the calculation when the
mas and/or velocity will have the new values.
As mentioned in the previous lecture, putting the the following
compiler directive at the top of our file:
#include <math.h>
(note the Americanism it's math
not maths) gives us access to all of the usual mathematical
functions.
We shall see below that the include file complex.h
gives us access to complex numbers and functions so we show the complex
versions here for convenience.
Real function(s) math.h |
Complex version complex.h |
Notes |
sin(x) cos(x) tan(x) |
csin(x) ccos(x) ctan(x) |
in radians |
asin(x) acos(x) atan(x) |
casin(x) cacos(x) catan(x) |
inverse sin (arcsin), etc. |
hypot(x, y) |
|
hypotenuse: √ x*x
+ y*y ) |
atan2(x, y) |
|
"whole range" atan(), (see note)
|
sinh(x) cosh(x) tanh(x) |
csinh(x) ccosh(x) ctanh(x) |
hyperbolic sine, etc. |
asinh(x) acosh(x) atanh(x) |
casinh(x) cacosh(x) catanh(x) |
inverse hyperbolic sine (arcsinh), etc. |
sqrt(x) |
csqrt(x) |
square root |
log() log10() |
clog() clog10() |
natural log, log to base 10 |
pow(x,y) |
cpow(x,y) |
xy
Note: do not write x^y |
fabs(x) |
cabs(x) |
floating-point absolute value and complex absolute value |
All arguments and returned values are doubles
for the real functions
and
double complex for the complex functions
except that cabs() returns a double.
Be sure to use the right one.Note: atan2()
The function atan2(x,y) returns the number a such that sin(a) = x / r and cos(a)
= y / r where r = √ x*x + y*y
. This is usually better than using atan(x/y). Think of it as
the angle from the vertical y axis, twelve o'clock.
#include <math.h> lets us
use sin(), etc.
- Mathamatical functions
- To practice using built-in mathematical functions.
- Create a new on-line program in a new window with a short comment at the top
saying it demonstrates using mathematical functions.
- Immediately after your
program includes <stdio.h> include <math.h>.
You will need to do this on a separate lines:
#include <stdio.h>
#include <math.h>
- As always, type this in, don't copy and paste. Typing it in
will help fix it in your mind.
- Declare a double
variable called x. Give it a positive value less than one.
- Print out the value of its sine, square root, and hyperbolic sine of
x along with a few other mathematica functions.
- Try printing the value of sin(asin(x)). Does it do what you expect?
- Try calling the asin() function with suitable values to answer the question:
- Does asin() return values in the range 0 to π or -π/2 to π/2?
Warning: don't confuse fabs(), cabs() and abs()
C also provides an integer absolute
value function abs() defined inside stdlib.h
as well as fabs() and cabs() .
Don't confuse fabs(), cabs() and abs()<
- abs() and fabs()
- To see
the difference between abs() and fabs().
- Create a new program with a comment at the top saying
it shows the difference between the abs() and fabs()
functions.
- Immediately after your
program includes <stdio.h> include the two files <math.h> and
<stdlib.h>. As in the previous
mini-exercise you will need to do this on separate lines:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
- As always, type this in, don't copy and paste. Typing it in
will help fix it in your mind.
- Declare a double variable, say x with a positive,
non-integer value.
- Print out the values of fabs(x) and abs(x) and notice the
difference.
- Now make x negative and again see what happens.
- Remove the inclusion of <math.h>. One easy way
to do this is just to "comment it out":
// #include <math.h>
This just turns the line into a comment, which is ignored.
- Build and run.
- Now a weird one: do the same for <stdlib.h>.
Giving functions the wrong type of arguments can have unexpected effects.
We shall see in a later lecture how C allows us to avoid this.
Warning: use pow(x,y) not x^y
Some languages use ^ for exponentiation, C uses it for something
else
Write x*x not x^2 and
pow(x,y) not x^y
It is extremely important to give variables fairly short names that tell us what
they represent. For our earlier "momentum"
example
this doesn't mean huge names such as
mass_of_the_object_measured_in_kilogrammes,
velocity_of_the_object_measured_in_metres_per-second
etc
but it does mean mass, velocity and
momentum (still actually a bit long)
or mass, vel and p or
even just m, v and p.
Choosing a sensible variable name is a compromise between
clarity and compactness. For example, if variables are called number_of_atoms
and maximum_number_of_atoms, that's not just a lot to
type but any arithmetic expressions involving them and other
similarly-named variables become very hard to understand. This
is particularly a problem for scientific programs with large
numbers of complicated arithmetic expressions.
Try to develop a consistent
short-hand. One popular scheme is to either capitalise the second and subsequent "word" in a variable name,
(sometimes referred to as "Camel-case" as the variable name ends up with "humps")
or to join them together with the underscore character. Or just run them together...
Examples for molecules, atoms and electrons
Suppose we want
variables for the numbers of electrons, atoms and molecules and for
the maximum number of electrons, atoms and molecules the system can
handle. We might choose:
Underscore
int n_atoms, max_atoms, n_mols, max_mols, n_elecs, max_elecs;
Camel-Case
int numAtoms, maxAtoms, numMols, maxMols, numElecs, maxElecs;
Notice how the initial letter is still in lower case: it's very easy to mistype numAtoms as NumAtoms and surprisingly hard to spot.
Run together
int natoms, maxatoms, nmols, maxmols, nelecs, maxelecs;
Avoid variable names that need comments
On the other hand, avoid short but unhelpful variable names
such as in the following example.
Example
Suppose we are unfortunate enough to have to deal with masses in
both kilograms (kg) and imperial pounds (lb). (Naturally we should
avoid this situation if at all possible!)A poor choice would be something like:
double mass1; /* Kilograms */
double mass2; /* Pounds */
Try to write your program so that it explains
itself without needing any comments, and only use comments
inside of functions for situations where that isn't possible.
When we come to use these variables we will constantly be
wondering which is which and referring back to the comment where
the variables were declared. Much better would be:
double mass_kg, mass_lb;
which doesn't even need a comment.
Use short variable names that mimic the words you
would use when describing the task to somebody else, such as
mass, price, n_students etc.
Expressions have both a value and a type.
It's useful to think of type names such
as double and int as primarily being
adjectives rather than nouns. When used as
a noun it's implicit from the context whether double
means a double variable, a double expression
or even a double constant
("2 is an int,
2.0 is a double.")
As we mentioned last week, some
Some
things, such as people, are
conventionally considered to considered as coming only in units of
integers, not fractions. Almost every C program will have such
integer variables and expressions, which have generic type int.
Apart from a little discussion of what
happens when we try to give them non-integer values, including
dividing one integer by another, they are fairly simple and
obvious.
Floating-point variables are used for things we measure and integer
variables are used for things we count.
Make a variable an integer only if is it logically
impossible for it to have a fractional value.
Types of integer variables
It's good to know that 210 is
approximately equal to 1000. It's also good to know that it's
not exactly equal to 1000. Use the prefixes Kibi,
Mibi, Gibi,
etc. if it's import to be precise. (Which is not very often!)
The number of bytes used for integers is much less standardised
than for floating-point numbers. Four bytes for an int
is common and we shall occasionally use it in our examples, but
if it matters to you be sure to check A four-byte int
can store integer values roughly in the range plus or minus
2 billion (231).
Since that is not always enough C also provides eight byte integers,
giving a range of roughly plus or minus 8 billion billion or 263.
One bit of the integer is used to indicate
whether it's positive or negative. We may add the qualifier "unsigned"
before any integer type to regain the extra bit in which case
the value are always positive.
C also defines a specialised integer type called
char which is used for storing printable characters
such as 'a', 'b', 'c', etc. We
shall meet this type in a later lecture.
Integer variables are declared in the obvious manner:
int j, k;
j = -2;
k = 4 * j - 11;
Notice that the specific types are called int
and long, the word "integer" is a generic
term, there is no variable type of this name.
Initialisation
Variables of all types (not just integers) can be initialised
at the same time as being declared so we could have written the
above code as:
int j = -2, k;
k = 4 * j - 11;
Notice that k was left uninitialised
(which is a fancy word for random!) when it was declared. It
would have been Very Bad News to have tried to use the value of
k without explicitly setting it first.
Uninitialised variables of all types have random
values and are not automatically set to zero.
- Unused and uninitialised variables
- To help us handle two common errors.
- We look at how the compiler deals with two common
situations where variables are declared but not used. As an example
we shall consider people travelling in cars.
- Start a new project. Put a brief comment at the top
saying this is calculating how many people to put into cars.
- Declare two variables suitable for storing the number of people
travelling and the number of cars they have. As always ask yourself
the questions we asked ourselves before:
- Is it logically possible for people and cars to have non-integer values?
If it is make them a double, if it is not make them an int.
- Now ask: "can I think of two short, snappy variable names so that when I look at them having
read the comment at the top of the program I will immediately know which is the
number of people and which is the
number of cars?". Use those names for your variables.
- Now you have two legally-declared variables that you
have not yet used, press "Build and Run". What happens?
- Print out the values of these variables
(using printf() and %d) even though you have not yet given
them a value. See what happens, both in terms of the compiler messages and the output.
When we do something that is legal C but probably indicates we have made
a mistake the compiler will warn us but but will carry on.
- Change the default: Check the "Warnings as errors" box and see what happens
when you Build with this option enabled..
- This option can be quite useful as our programs get larger and we enable
it by default on our PCs.
Integer constants
As shown above, these work as we would expect but any
Any
constant with
a decimal point or an exponent is treated as a double
even if its value is an integer. This means that 1000.0
and 1e3 are both doubles with value "one
thousand point zero". This only really matters when we are
dealing with division, which we discuss in the next section.
for historical reasons, integer constants
starting with zero are treated as octal (base eight), not
decimal. So 017 is fifteen, not seventeen.
Something that is occasionally useful is that
integer constants starting with "0x" are treated as hexadecimal
("hex") or base sixteen, using the letters "a-f" for ten to
fifteen. This is useful as sixteen is 2 to the power 4, i.e.
four bits so any byte can be represented by two hexadecimal
"digits". Hex is used to specify colours on the Web, for
example.
We saw in the last lecture that %g is used to print
the value of a floating-point expression.
For printing the value
of an integer expression we may use either %i
or %d (where the d stands for decimal).
int j = 6, k = 7;
printf("j+k is %i, j-k is %i\n", j+k, j-k);
Use %g in printf() to
print a floating-point number. Use %i or %d (decimal) for ints.
Using the wrong format
Using the wrong format, for example %d for a double
%g for an int will have bizarre and unexpected effects.
Tecnically "the behavior is undefined" which is
one of the most ominous phrases in computing.
- Right and wrong formats
- To show the dangers of using the wrong format
- Create a new on-line program in a new window, with a suitable title and opening comment.
- Declare two variables, one an int and the other
a double and give them each a suitable value.
- "Suitable" means a non-zero value for the integer and a non-integer
value for the double with the two values being "fairly different".
- Now print them out correctly using %d (or %i) and %g respectively.
- Build and run and check the output is correct.
- Now see what happens when you use the correct formats
but the wrong way round and when you use "%d %d"
and "%g %g".
- The only understandable part of what happens should be the warning message.
- Don't try looking for any logical reason for what does: there won't be one!
- Check the "Warnings as errors" box. and try again.
- This is why we enable this option by default on our cluster PCs.
Warm-up questions
- You wish to divide 13 gallons of petrol equally between
4 cars. How many gallons of fuel go into each car?
- You wish to divide 13 people equally between
4 cars. How many people go into each car?
- Answer the above questions.
- Should become obvious!
- Answer the above questions.
- Do you get the same answer each time?
- If not, why not?
- If you do, what will be the result?
Why are we interested?
For the questions above you should have given a fractional answer
for the first question but the second should have had the form
"x people per car with y left over". You may even have gone one
step further and used those numbers to say
"there will be m cars with x people and n with x+1".
Whenever we dealing with the division of integers
we need to decide whether our algorithm requires the
division to be treated as an integer (plus a remainder)
or as floating-point number.
C gives us the chance to do either integer division, which
always results in an integer (and the ability to find the remainder), or to explicitly convert the
integers to their floating-point values.
The key is to make sure we make the right choice: it is not good to have 3.25
people per car!
As humans we instinctively realise whether to treat a division
as integer (plus a remainder) or as fractional, when programming we must explicitly
say which we want. Failure to do this is a common cause of mistakes.
Integer expressions result in integers
Any arithmetic expression involving only
integers is treated as an integer expression, for example:
int j = 5, k = 3;
double x;
x = (j + 7 ) / (k*6 -j*4 + 12);
Step through this code
here x will have the value 1,
not 1.2 .
For simplicity we will use the
example of the division of two integer variables below, but
exactly the same rules apply to any integer expressions.
For the rest of this lesson we shall
assume that variables j and k have integer
type and that x is a double.
Integer division
Integer division of positive expressions is straight-forward
and is by far the most common case of integer division. (We
don't often want to divide minus-thirteen people between four
cars!) In this section we shall consider only
positive values of j and k.
If both j and k are positive ints
the result of
the division j/k is the smallest integer less than or
equal to the true, floating-point value of j/k. This can be
thought of as "the number of whole ks in j".
7/2 equals 3 (with remainder 1)
13/5 equals 2 (with remainder 3)
9/3 equals 3 (with remainder 0)
Note that this is not necessarily the nearest
integer, for example 13.0/5.0 equals 2.6 which is
nearer to 3 than to 2 but 13/5 equals 2, not 3.
This can equally be thought of as "discarding the fraction" or
rounding towards zero.
Division of two integer expressions gives an
integer result and throws away the fraction, eg 5/3 is 1. It
does not produce the nearest integer.
Division of negative integers
Division of negative integers is extremely rare
and is defined by the rule:
-j / k == j / -k == - ( j / k )
(see the appendix).
% is the integer remainder operator
When we want to find the remainder we can use the expression j % k "j remainder k" (as
above, k must not be zero), defined by:
j % k equals j - (j / k) * k
e.g, using the same examples as above:
7 % 2 equals 1
13 % 5 equals 3
9 % 3 equals 0
This means that j%k is always in the range 0 to k-1.
Between them integer division and the remainder operator enable
programs such as:
Example: convert seconds to minutes and seconds
#include <stdio.h>
int main() {
int totaltime, seconds, minutes;
totaltime = 429;
seconds = totaltime;
minutes = seconds / 60;
seconds = seconds % 60;
printf(" %i seconds in total, is %i minutes and %i seconds.\n",
totaltime, minutes, seconds);
return 0;
}
Step through this code
The output is:
429 seconds in total, is 7 minutes and 9 seconds.
You will observe the use of "%i" to print an
integer. Strangely, "%d" (for decimal) is a
synonym for %i (the "d" does not
stand for "double"!). Use "%ld" or "%li" to
print a long.
- Step through the above example.
- To observe integer division and remainder.
- Step through the above example.
- Click on "Start program".
- Before the assignment of minutes use the law of integer division to predict the value of minutes.
- Step forward and check you were correct if not work out why.
- Before the assignment of seconds use the law of integer remainder to predict the value of seconds.
- Step forward and check you were correct if not work out why.
% is the integer remainder operator, eg 5
% 3 is 2.
- Dividing people between cars
- To familiarise ourselves with integer division and remainder.
- Take your previous mini-exercise and this time give sensible positive values to the number of people and the number of cars.
- Use the / and % operators to solve the "dividing people between cars" problem to
print out something like:
11 people between 3 cars is 3 people per car with 2 people left over.
There is no need to worry about minor grammatical
problems such as:
"1 people left over".
- Try various positives values for the number of people and of cars to
check that / and % are behaving as you expect.
Integer division is simple but we don't want it to occur by
accident, when we were expecting floating-point division.
As scientists our biggest danger from integer division is that
we do it by accident when we meant to do floating-point division.
1. Always use decimal points for double constants
with integer values.
If we do this all the time, even in expressions not involving
integer division, we will avoid mistakes like:
x = 2/3; /* Oops! */
x = 2.0/3.0; /* Good! */
Always use decimal points for double
constants with integer values.
2. The choice of integer or floating-point division depends on
the expression, not the context.
If as humans we look at the statement:
x = j/k;
we might be tempted to look at the left-hand side, see it's a double
and treat the right-hand side j/k as a floating-point
division. (This is a particular danger because we see the x
first.) Computers don't work that way - they follow fixed rules:
- Evaluate j/k (as an integer)
- Assign the result to x
The right-hand side is evaluated as an integer and only after
that does the compiler look at the left-hand side. The computer
doesn't then say "x is a floating-point variable, John probably
wanted to evaluate j/k as a floating-point expression
so I'll go back and do it for him".
The choice of integer or floating-point division
depends on the expression, not the context.
3. Don't put integer division inside floating-point
expressions.
x = v * (j/k);
If v is integer then j/k will be evaluated
as integers, if v is floating point j/k
will be evaluated as floating-point! It's
just giving ourselves another chance to go wrong.
Don't put integer division inside floating-point
expressions.
A steppable example
We gather a few cases of integer division in the following example.
The final expression shows what happens when we replace the integer "3"
with the floating-point number "3.0".
Keep a close
eye on the type of each expression, particularly
during division.
#include <stdio.h>
int main() {
int j, k;
double x;
x = 7/3;
printf("x: %g\n", x);
j = 7;
k = 3;
x = j/k;
printf("x: %g\n", x);
x = 1.14159265358979 + j/k;
printf("x: %g\n", x);
k = x;
printf("k: %d\n", k);
x = 7/3.0;
printf("x: %g\n", x);
return 0;
}
Step through this code
A previous example revisited
We can revisit the earlier example of integer division:
x = (j + 7 ) / (k*6 -j*4 + 12);
and see what happens
when we make some of the integer constants into floating-point constants of the same value.
Again, keep an eye on the type of each expression.
#include<stdio.h>
int main() {
int j = 5, k = 3;
double x, y;
x = (j + 7 ) / (k*6 -j*4 + 12);
y = (j + 7.0 ) / (k*6 -j*4 + 12);
y = (j + 7.0 ) / (k*6.0 -j*4.0 + 12.0);
printf("x: %g y: %g\n", x, y);
return 0;
}
Step through this code
Automatic (implicit) conversion
As seen above, when evaluating expressions with two different
arithmetic types
C just "does the right thing" and converts things for us. For
example, in the expression:
j * x;
Since j is an integer and x a double the
value of j is automatically converted to a double
before the multiplication.(The variable j itself is unchanged of course.)
Integer to floating-point conversions and assignments are also
common and again they "just work":
j = 12;
x = j;
just sets x to 12.
Implicit floating-point to integer conversion
Setting an integer variable to the value of a floating-point
expression rounds to zero, just like integer division:
x = 1.82;
j = x; // j has the value 1
Explicit conversion between types
We have already seen how we can force division involving constant integer
values to be treated as floating point by just adding ".0". But what if we wish
to divide two integer variables to obtain a floating-point result? We can't
add ".0" to a variable name!
Given the previous discussion on integer division we can see
that the following code which tries to calculate an average is
incorrect:
int number, total;
double average;
...
average = total/number; /* Wrong! */
as total and number are both integers and
that therefore the expression total/number is also an
integer.
The way to divide two integers in a floating-point way is to
explicitly convert one or both of the integers to a double.
Any integer expression can be converted to a double expression
of the same value by the simple, if slightly unintuitive, method
of writing "(double) expression" :
We can put any valid type inside the parentheses
to convert an expression to a different type.
int number, total;
double average;
...
average = total/((double) number);
Thus the expression (double) number means
"the value of number treated as a double, not
an integer". If number had the value 7 then (double) number
is treated it as if it were 7.0 . Since we are now dividing an
integer by a double, the integer expression on the top is also
converted to a double and all is well. This is referred to as casting the original expression to the new
type.
The extra parentheses on the bottom are
optional, we could have simply written:
average = total/(double) number;
Expressions of the form (typename)
sub-expression
explicitly convert the sub-expression to that type.
Casts bind extremely tightly so
(double) total/number
is equivalent to ((double) total)/number
which is nearly always what we want.
Complex numbers weren't available in early
versions of C as they are highly specialised and only used by
mathematicians, scientists and engineers.
The include file complex.h allows us to
conveniently use complex numbers using the double complex
type with I (note it's capital!) being the
square root of minus one.
#include <complex.h>
lets us use complex numbers and functions.
Complex versions of most of the math.h functions are
also inside complex.h, all with a c
in front of the name such as csqrt(), csin(),
casin(), cexp(), cpow(), etc.
The functions creal(), cimag()
give the real and imaginary parts respectively with conj(),
cabs() and carg() also available.
For obvious mathematical reasons these last three functos return a double
(ie a real number)..
cabs() is the complex,
absolute value, fabs() is the real
floating-point absolute value and abs() is the
integer absolute value.
Once we've done that complex arithmetic goes just as we would
expect, although we have to split expressions into their real
and imaginary parts for printing:
Internaly C implements complex numbers as a pair
of float-point numbers stored one after the other in memory,
although we don't need to know that in order to use them.
#include <stdio.h>
#include <complex.h>
int main() {
double complex x, y, sqrtx;
x = 1.2 + 3.4 * I;
y = -13.7 * x + 5.6 * x * x - 12.7 * I;
sqrtx = csqrt(x);
printf("x is %g %+g * I\n", creal(x), cimag(x));
printf("y is %g %+g * I\n", creal(y), cimag(y));
printf("sqrt(x) is %g %+g * I\n", creal(sqrtx), cimag(sqrtx));
return 0;
}
Step through this code
The output is:
The format %+g in printf()means
"always print a plus if the number is positive".
x is 1.2 +3.4 * I
y is -73.112 -13.584 * I
sqrt(x) is 1.55009 +1.09671 * I
Observe that we could have written the last printf()
function as
printf("sqrt(x) is %g %+g * I\n", creal(csqrt(x)), cimag(csqrt(x)));
as the arguments to functions are always
the value of expressions, we never "pass a variable"
to a function in C.
- Complex arithmetic
- To practice complex arithmetic and functions
- Create a new on-line program in a new window, with a suitable title and opening comment.
- #include the file complex.h
- Declare three double complex variables and give two of them complex
values. (See the above example for how to do this.)
- Make the third equal to the product of the first two.
- Print out the complex square-root of all three
numbers using %g (three times of course).
- Do the numbers look reasonable?
We have seen how to write the values of mathematical expressions to the screen:
int ivar = 1;
float fvar = 2.2;
double dvar = 3.3;
printf("%d %g %g\n", ivar, fvar, dvar);
The above example writes an int, a float (single-precision floating-point number)
and a double (double-precision floating-point number) to the screen. Conveniently we may
use "%g" for both the single and precision floating-point numbers.
It's so useful to be able to read in numbers from the keyboard
that we show how the very basics here, although the full details
will have to wait until a later lecture.
We will be covering scanf()
more thoroughly in a
later lecture, this is just a very quick recipe.
Here we read in the values of the three variables from the keyboard
and you will immediately see a few differences:
For more details read the lecture on Memory,
input and ouput
scanf("%d %g %lg", &ivar, &fvar, &dvar);
Notes:
We use scanf() instead of printf()
We have to put an ampersand &
in front of the variable names in calls to scanf().
The format to read a
double is %lg (The "l" stands for
long.) but to write a double is %g.
(We shall explain why in a later lecture but for the time being
we will just have to remember.)
There is no \n at the end of the format string for
scanf(), or anything else inside the format string
except spaces
For example, no commas between the formats. Commas and
new-lines are allowed but they do not mean what you think they
mean.
#include <stdio.h>
int main() {
int ivar = 1;
float fvar = 2.2;
double dvar = 3.3;
printf("%d %g %g\n", ivar, fvar, dvar);
printf("Please enter an integer and two floating-point numbers\n");
scanf("%d %g %lg", &ivar, &fvar, &dvar);
printf("%d %g %g\n", ivar, fvar, dvar);
return 0;
}
Step through this code
Although we shall sometimes refer to scanf() in our
notes and non-marked exercises we will always tell you how to use scanf() in our assessed work until it has
been covered in more depth in a later lecture.
- Reading in variables
- To practice using scanf()
- If you have trouble with this exercise leave it until the lab class.
- Take your "people and cars" mini-exercise and modify it to read in the numbers of people
and cars.
- Do the same with your momentum code. Reminder: use %lg
to read in a double.
The text of each key point is a link to the place in the web page.