Lecture 7: Number Types and Functions to Store Data¶
Every object in Sage has a type. The type of an object determines the operations that can be performed on the object. The main data types in Python are lists, dictionaries, and tuples.
The most basic number types in Sage have short abbreviations,
CC, for the integers, rationals,
reals, and complex numbers. We see how to explicitly fix the type of
a number with so-called type coercing.
The ability to choose random numbers is often very useful.
We see how to select left and right hand sides of equations.
The lecture ends with a specific application of default parameters
of functions that enables us to store data in functions.
Coercing to the Basic Number Types¶
The main number types are integers, rationals, reals, and complex numbers,
respectively denoted by
To convert from one type to the other is to coerce.
We can convert a string in hexadecimal format or octal format into decimal notation.
a = ZZ('0x10'); print(a) b = ZZ('010'); print(b)
Given the list of coefficients, we can evaluate a number in any base. The following instruction
returns the list
[2, 4, 1] as these are the digits of 142
in the decimal number system , written backwards, starting at
the least significant digit.
Writing 142 in full is \(1 \times 10^2 + 4 \times 10^1 + 2 \times 10^0\).
Passing 3 as the argument of
we decompose 142 in a number system of base 3, as shown below:
c = ZZ(142).digits(3) print(c) print(ZZ(c, base=3))
The last instruction above takes the coefficients
in the list
[1, 2, 0, 2, 1] and evaluates the coefficients
as \(1 \times 3^0 + 2 \times 3^1 + 0 \times 3^2 + 2 \times 3^3
+ 1 \times 3^4 = 142\).
A quick way to make a rational representation of a floating-point number
is via type coercing to
print(QQ) x = numerical_approx(pi, digits=20) y = QQ(x); print(y)
Note the we cannot coerce
pi directly to a rational number.
z = RR(y); print(z) print(QQ(z))
Observe the difference between the value
y and the value
This is because the precision of
RR is the same 53 bit
as the hardware double floats.
print(RR) print(RDF) print(RR == RDF)
RR has a precision of 53 bits, it is not the same
RealDoubleField which is abbreviated as
Generating random numbers makes the distinction between the
RDF a bit more explicit.
The machine precision is the smallest positive number we can add to 1.0 and still make a difference. We can compute the machine precision as follows:
eps = 2.0^(-RR.precision()+1) print(eps)
Note that the
2.0 in the formula for
eps is necessary,
2 instead of
2.0 would have resulted in an exact
rational number, not an element of
RR. We see the value
eps again in the following calculation:
a = 1.0 + eps a - 1.0
For any real floating-point number
x smaller than
the result of
1.0 + x would have remained
x belonging to some real number field, we can compute
a nearby rational approximation with a given bound on the denominator.
a rational approximation for
x where the denominator is smaller
than the given bound of
To compute a sequence of consecutively larger rational approximations,
each time allowing a denominator that is ten times larger than the
previous one in the sequence, we can run the following command.
[x.nearby_rational(max_denominator=10^k) for k in range(1, 11)]
nearby_rational method gives the third type of rational
approximations for real numbers. The first two we covered were
Rational approximations with prescribed accuracy.
Convergents of the continued fraction representation of the real number.
The application of the method
hex() on a real number shows
the hexadecimal expansion of the number.
onetenth = 0.1 onetenth.hex()
The output is
0x1.999999999999ap-4 which does not suggest
a finite expansion. Casting
onetenth in a
a higher precision (e.g.: one hundred bits) will confirm that in binary,
the representation of
0.1 cannot be exact.
Of course, in the decimal notation
agrees exactly with \(1/10\).
In simulations, we work with random numbers.
With the method
random_element(), we can generate a random
integer number, for example of 3 decimal places, between 100 and 999:
Random real numbers are generated as follows:
x = RR.random_element(); print(x, type(x)) y = RDF.random_element(); print(y, type(y))
The type of
while the type of
The same distinction can be made between the Complex Field
and the Complex Double Field
x = CC.random_element(); print(x, type(x)) y = CDF.random_element(); print(y, type(y))
To visualize the distribution of numbers, we can plot a bar chart:
L = [RR.random_element() for _ in range(100)] bar_chart(L)
Then the output of
bar_chart(L) is shown in Fig. 8.
If we sort the numbers, then we can see that the distribution tends to be uniform.
The sorted numbers are visualized in Fig. 9.
Components of Expressions¶
We often work with equations.
eqn = x**2 + 3*x + 2 == 0 print(type(eqn))
eqn is of type
a type we encountered already many times.
Our expression eqn has an operator.
The operator is
<built-in function eq> and
we can select its left and right hand side
and instead of
rhs() we may also use
Storing Data with Functions¶
The example in this section is taken from the book Sage for Power Users by William Stein.
With default arguments in functions, we can store references to objects implicitly. Consider the following function.
def our_append(item, L=): L.append(item) print(L)
Let us now execute the function a couple times.
our_append(1/3) our_append('1/3') our_append(1.0/3)
We see the following lists printed to screen.
[1/3] [1/3, '1/3'] [1/3, '1/3', 0.333333333333333]
To explain what happened, let us print the address of
L each time.
Because we will need to use the address later, we return
def our_append2(item, L=): L.append(item) print(L, id(L)) return(id(L))
We run this function
our_append2 also three times.
idL = our_append2(1/3) idL = our_append2('1/3') idL = our_append2(1.0/3)
and we see the following output
[1/3] 4650781440 [1/3, '1/3'] 4650781440 [1/3, '1/3', 0.333333333333333] 4650781440
The first time we called
our_append2 without giving an argument
for the list
L, the arguments were evaluated.
The effect of
L =  is that an empty list is created
and placed somewhere in memory.
Each time the function is called with the default
L, the same memory location is used.
Note that the name
L does not exist outside the function.
Just to check,
print(L) will result in a
With ctypes we can retrieve the object an address refers to.
idL = our_append2(0) import ctypes print(ctypes.cast(idL, ctypes.py_object).value)
Executing the cell shows
[1/3, '1/3', 0.333333333333333, 0] 4650781440 [1/3, '1/3', 0.333333333333333, 0]
QQ.random_element(). What do you observe? How would you make a random rational number with type coercions?
QQ(pi). Describe what happens. Is this what you would expect? Write a mathematical explanation.
Illustrate how you would generate a random complex number of type
CC. The number should have absolute value equal to one. Hint: think about the polar representation of complex numbers.
x = R100(sqrt(2))where
RealFieldwith a precision corresponding to about 100 decimal places.
Compute a list of 10 rational approximations for
x, starting with a 10 as the first bound on the denominator. The bound of the denominator of the rational approximations equals
kruns from 1 to 10.
For each approximation in your list, compute the accuracy, that is the relative error for each rational approximation. Write the relative errors in scientific notation.
eqn = x^3 + 8.0*x - 3 == 0and solve this equation. Verify the solutions in the polynomial defined at the left hand side of the equation
eqnwithout retyping the expression at the left hand side of the equation.