Compile time arithmetic

Fractions are a problem because there are some for which there are not enough significant figures to accurately represent them, resulting in losing accuracy when you use them in further arithmetic. Furthermore, computers are binary and merely converting decimal fractional parts to binary will lose accuracy. The <ratio> library provides classes that allow you to represent fractional numbers as objects that are ratios of integers, and perform fraction calculations as ratios. Only once you have performed all the fractional arithmetic will you convert the number to decimal, and this means that the potential loss of accuracy is minimized. The calculations performed by the classes in the <ratio> library are carried out at compile time so the compiler will catch errors such as divide by zero and overflows.

Using the library is simple; you use the ratio class, and provide the numerator and denominator as template parameters. The numerator and denominator will be stored factorized, and you can access these values through the num and den members of the object:

    ratio<15, 20> ratio; 
cout << ratio.num << "/" << ratio.den << endl;

This will print out 3/4.

Fractional arithmetic is carried out using templates (these are, in fact, specializations of the ratio template). At first sight it may appear a little odd, but you soon get used to it!

    ratio_add<ratio<27, 11>, ratio<5, 17>> ratio; 
cout << ratio.num << "/" << ratio.den << endl;

This will print out 514/187 (you may want to get some paper and do the fractional calculations to confirm this). The data members are actually static members, so it makes little sense to create variables. Furthermore, because arithmetic is carried out using types rather than variables, it is best to access the members through those types:

    typedef ratio_add<ratio<27, 11>, ratio<5, 17>> sum; 
cout << sum::num << "/" << sum::den << endl;

You can now use the sum type as a parameter to any of the other operations that you can perform. The four binary arithmetic operations are carried out with ratio_add, ratio_subtract, ratio_multiply, and ratio_divide. Comparisons are carried out through ratio_equal, ratio_not_equal, ratio_greater, ratio_greater_equal, ratio_less, and ratio_less_equal.

    bool result = ratio_greater<sum, ratio<25, 19> >::value; 
cout << boolalpha << result << endl;

This operation tests to see if the calculation performed before (514/187) is greater than the fraction 25/19 (it is). The compiler will pick up divide-by-zero errors and overflows, so the following will not compile:

    typedef ratio<1, 0> invalid; 
cout << invalid::num << "/" << invalid::den << endl;

However, it is important to point out that the compiler will issue the error on the second line, when the denominator is accessed. There are also typedefs of ratio for the SI prefixes. This means that you can perform your calculations in nanometers, and when you need to present the data in meters you can use the nano type to obtain the ratio:

    double radius_nm = 10.0; 
double volume_nm = pow(radius_nm, 3) * 3.1415 * 4.0 / 3.0;
cout << "for " << radius_nm << "nm "
"the volume is " << volume_nm << "nm3" << endl;
double factor = ((double)nano::num / nano::den);
double vol_factor = pow(factor, 3);
cout << "for " << radius_nm * factor << "m "
"the volume is " << volume_nm * vol_factor << "m3" << endl;

Here, we are doing calculations on a sphere in nanometers (nm). The sphere has a radius of 10 nm, so the first calculation gives the volume as 4188.67 nm3. The second calculation converts nanometers into meters; the factor is determined from the nano ratio (note that for volumes the factor is cubed). You could define a class to do such conversions:

    template<typename units> 
class dist_units
{
double data;
public:
dist_units(double d) : data(d) {}

template <class other>
dist_units(const dist_units<other>& len) : data(len.value() *
ratio_divide<units, other>::type::den /
ratio_divide<units, other>::type::num) {}

double value() const { return data; }
};

The class is defined for a particular type of unit, which will be expressed through an instantiation of the ratio template. The class has a constructor to initialize it for values in those units and a constructor to convert from other units, and that simply divides the current units by the units of the other type. This class can be used like this:

    dist_units<kilo> earth_diameter_km(12742); 
cout << earth_diameter_km.value() << "km" << endl;
dist_units<ratio<1>> in_meters(earth_diameter_km);
cout << in_meters.value()<< "m" << endl;
dist_units<ratio<1609344, 1000>> in_miles(earth_diameter_km);
cout << in_miles.value()<< "miles" << endl;

The first variable is based on kilo and hence the units are kilometers. To convert this to meters, the second variable type is based on ratio<1>, which is the same as ratio<1,1>. The result is that the values in the earth_diameter_km are multiplied by 1000 when placed in in_meters. The conversion to miles is a bit more involved. There are 1609.344 m in a mile. The ratio used for the in_miles variable is 1609344/1000 or 1609.344. We are initializing the variable with the earth_diameter_km, so isn't that value too big by a factor of 1000? No, the reason is that the type of earth_diameter_km is dist_units<kilo>, so the conversion between km and miles will include that factor of 1000.

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

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