Go to the first, previous, next, last section, table of contents.


3. Ordinary number types

CLN implements the following class hierarchy:

                        Number
                       cl_number
                     <cl_number.h>
                          |
                          |
                 Real or complex number
                        cl_N
                     <cl_complex.h>
                          |
                          |
                     Real number
                        cl_R
                      <cl_real.h>
                          |
      +-------------------+-------------------+
      |                                       |
Rational number                     Floating-point number
    cl_RA                                   cl_F
<cl_rational.h>                          <cl_float.h>
      |                                       |
      |                  +-------------+-------------+-------------+
   Integer               |             |             |             |
    cl_I            Short-Float   Single-Float  Double-Float   Long-Float
 <cl_integer.h>        cl_SF         cl_FF         cl_DF         cl_LF
                   <cl_sfloat.h> <cl_ffloat.h> <cl_dfloat.h> <cl_lfloat.h>

The base class cl_number is an abstract base class. It is not useful to declare a variable of this type except if you want to completely disable compile-time type checking and use run-time type checking instead.

The class cl_N comprises real and complex numbers. There is no special class for complex numbers since complex numbers with imaginary part 0 are automatically converted to real numbers.

The class cl_R comprises real numbers of different kinds. It is an abstract class.

The class cl_RA comprises exact real numbers: rational numbers, including integers. There is no special class for non-integral rational numbers since rational numbers with denominator 1 are automatically converted to integers.

The class cl_F implements floating-point approximations to real numbers. It is an abstract class.

3.1 Exact numbers

Some numbers are represented as exact numbers: there is no loss of information when such a number is converted from its mathematical value to its internal representation. On exact numbers, the elementary operations (+, -, *, /, comparisons, ...) compute the completely correct result.

In CLN, the exact numbers are:

Rational numbers are always normalized to the form numerator/denominator where the numerator and denominator are coprime integers and the denominator is positive. If the resulting denominator is 1, the rational number is converted to an integer.

Small integers (typically in the range -2^30...2^30-1, for 32-bit machines) are especially efficient, because they consume no heap allocation. Otherwise the distinction between these immediate integers (called "fixnums") and heap allocated integers (called "bignums") is completely transparent.

3.2 Floating-point numbers

Not all real numbers can be represented exactly. (There is an easy mathematical proof for this: Only a countable set of numbers can be stored exactly in a computer, even if one assumes that it has unlimited storage. But there are uncountably many real numbers.) So some approximation is needed. CLN implements ordinary floating-point numbers, with mantissa and exponent.

The elementary operations (+, -, *, /, ...) only return approximate results. For example, the value of the expression (cl_F) 0.3 + (cl_F) 0.4 prints as `0.70000005', not as `0.7'. Rounding errors like this one are inevitable when computing with floating-point numbers.

Nevertheless, CLN rounds the floating-point results of the operations +, -, *, /, sqrt according to the "round-to-even" rule: It first computes the exact mathematical result and then returns the floating-point number which is nearest to this. If two floating-point numbers are equally distant from the ideal result, the one with a 0 in its least significant mantissa bit is chosen.

Similarly, testing floating point numbers for equality `x == y' is gambling with random errors. Better check for `abs(x - y) < epsilon' for some well-chosen epsilon.

Floating point numbers come in four flavors:

Of course, computations with long floats are more expensive than those with smaller floating-point formats.

CLN does not implement features like NaNs, denormalized numbers and gradual underflow. If the exponent range of some floating-point type is too limited for your application, choose another floating-point type with larger exponent range.

As a user of CLN, you can forget about the differences between the four floating-point types and just declare all your floating-point variables as being of type cl_F. This has the advantage that when you change the precision of some computation (say, from cl_DF to cl_LF), you don't have to change the code, only the precision of the initial values. Also, many transcendental functions have been declared as returning a cl_F when the argument is a cl_F, but such declarations are missing for the types cl_SF, cl_FF, cl_DF, cl_LF. (Such declarations would be wrong if the floating point contagion rule happened to change in the future.)

3.3 Complex numbers

Complex numbers, as implemented by the class cl_N, have a real part and an imaginary part, both real numbers. A complex number whose imaginary part is the exact number 0 is automatically converted to a real number.

Complex numbers can arise from real numbers alone, for example through application of sqrt or transcendental functions.

3.4 Conversions

Conversions from any class to any its superclasses ("base classes" in C++ terminology) is done automatically.

Conversions from the C built-in types `long' and `unsigned long' are provided for the classes cl_I, cl_RA, cl_R, cl_N and cl_number.

Conversions from the C built-in types `int' and `unsigned int' are provided for the classes cl_I, cl_RA, cl_R, cl_N and cl_number. However, these conversions emphasize efficiency. Their range is therefore limited:

In a declaration like `cl_I x = 10;' the C++ compiler is able to do the conversion of 10 from `int' to `cl_I' at compile time already. On the other hand, code like `cl_I x = 1000000000;' is in error. So, if you want to be sure that an `int' whose magnitude is not guaranteed to be < 2^29 is correctly converted to a `cl_I', first convert it to a `long'. Similarly, if a large `unsigned int' is to be converted to a `cl_I', first convert it to an `unsigned long'.

Conversions from the C built-in type `float' are provided for the classes cl_FF, cl_F, cl_R, cl_N and cl_number.

Conversions from the C built-in type `double' are provided for the classes cl_DF, cl_F, cl_R, cl_N and cl_number.

Conversions from `const char *' are provided for the classes cl_I, cl_RA, cl_SF, cl_FF, cl_DF, cl_LF, cl_F, cl_R, cl_N. The easiest way to specify a value which is outside of the range of the C++ built-in types is therefore to specify it as a string, like this:

   cl_I order_of_rubiks_cube_group = "43252003274489856000";

Note that this conversion is done at runtime, not at compile-time.

Conversions from cl_I to the C built-in types `int', `unsigned int', `long', `unsigned long' are provided through the functions

int cl_I_to_int (const cl_I& x)
unsigned int cl_I_to_uint (const cl_I& x)
long cl_I_to_long (const cl_I& x)
unsigned long cl_I_to_ulong (const cl_I& x)
Returns x as element of the C type ctype. If x is not representable in the range of ctype, a runtime error occurs.

Conversions from the classes cl_I, cl_RA, cl_SF, cl_FF, cl_DF, cl_LF, cl_F and cl_R to the C built-in types `float' and `double' are provided through the functions

float cl_float_approx (const type& x)
double cl_double_approx (const type& x)
Returns an approximation of x of C type ctype. If abs(x) is too close to 0 (underflow), 0 is returned. If abs(x) is too large (overflow), an IEEE infinity is returned.

Conversions from any class to any of its subclasses ("derived classes" in C++ terminology) are not provided. Instead, you can assert and check that a value belongs to a certain subclass, and return it as element of that class, using the `As' and `The' macros. As(type)(value) checks that value belongs to type and returns it as such. The(type)(value) assumes that value belongs to type and returns it as such. It is your responsibility to ensure that this assumption is valid. Example:

   cl_I x = ...;
   if (!(x >= 0)) abort();
   cl_I ten_x = The(cl_I)(expt(10,x)); // If x >= 0, 10^x is an integer.
                // In general, it would be a rational number.


Go to the first, previous, next, last section, table of contents.