CLN, a Class Library for Numbers - 3 Ordinary number types

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 (including integers),
- complex numbers whose real and imaginary parts are both rational numbers.

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:

- Short floats, type `cl_SF`. They have 1 sign bit, 8 exponent bits (including the exponent's sign), and 17 mantissa bits (including the "hidden" bit). They don't consume heap allocation.
- Single floats, type `cl_FF`. They have 1 sign bit, 8 exponent bits (including the exponent's sign), and 24 mantissa bits (including the "hidden" bit). In CLN, they are represented as IEEE single-precision floating point numbers. This corresponds closely to the C/C++ type ``float'`.
- Double floats, type `cl_DF`. They have 1 sign bit, 11 exponent bits (including the exponent's sign), and 53 mantissa bits (including the "hidden" bit). In CLN, they are represented as IEEE double-precision floating point numbers. This corresponds closely to the C/C++ type ``double'`.
- Long floats, type `cl_LF`. They have 1 sign bit, 32 exponent bits (including the exponent's sign), and n mantissa bits (including the "hidden" bit), where n >= 64. The precision of a long float is unlimited, but once created, a long float has a fixed precision. (No "lazy recomputation".)

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:

- The conversion from ``int'` works only if the argument is < 2^29 and > -2^29.
- The conversion from ``unsigned int'` works only if the argument is < 2^29.

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.