Related sites: University of Antwerp | Department of Mathematics and Computer Science

# CANT Research Group

Navigation: Precompiler

# Examples

This section contains various examples. On the Usage page all command line options are given.

^ TOP

## Example 1 : Using the -nofor and -noprintf options

Consider the C++ code source code 'in.cpp':

    int main(){
      float b;
      for(float a=0;a<10;a=a+0.5){
        b=a*2;
        printf("a=%f",a);
      }
      return 0;
    }

We can use the precompiler to convert this code with the conversion file for the MpIeee datatype:

    ./precompile -x mpieee.xml in.cpp out.cpp

The resulting output file out.cpp now contains the following code:

    int main(){
      MpIeee b;
      for(MpIeee a= MpIeee( "0" );a<MpIeee( "10" );a=a+MpIeee( "0.5" )){
        b=a*MpIeee( "2" );
        {cout<<"a="<<setiosflags((ios::fixed & ios::floatfield))<<a;
    cout.precision(6);cout.fill(' ');cout.width(0);cout.setf(ios::dec,ios::basefield);
    cout<<resetiosflags((ios::fixed & ios::floatfield))<<"";}
      }
      return 0;
    }

To optimize the loop and thus skip conversion of the loop variable 'a' we use the -nofor option:

    ./precompile -x mpieee.xml in.cpp out.cpp -nofor

The resulting output:

    int main(){
      MpIeee b;
      for(float a=0;a<10;a=a+0.5){
        b=a*MpIeee( "2" );
        {cout<<"a="<<setiosflags((ios::fixed & ios::floatfield))<<a;
    cout.precision(6);cout.fill(' ');cout.width(0);cout.setf(ios::dec,ios::basefield);
    cout<<resetiosflags((ios::fixed & ios::floatfield))<<"";}
      }
      return 0;
    }

Now we do have an unchecked conversion in the assignment to the MpIeee variable b. Here the precompiler will give a warning:

    Warning : Assignment or comparison to type MpIeee may contain wrong conversion for 'a' at line: 8

To avoid conversion of the printf statements, use the -noprintf option:

    ./precompile -x mpieee.xml in.cpp out.cpp -nofor -noprintf

The resulting output code is:

    int main(){
      MpIeee b;
      for(float a=0;a<10;a=a+0.5){
        b=a*MpIeee( "2" );
        printf("a=%f",a);
      }
      return 0;
    }

^ TOP

## Example 2 : The preparse option

Specific functions or variables can be skipped using a skip configuration file. To get a list of variables and functions which are candidates for skipping we use the -preparse option:

    ./precompile -x mpieee.xml in.cpp variables.out -preparse

The variables.out file for the C++ program in Example 1 :

    /               globD           double
    main            b               float
    main            a               float

Every variable is represented in one row. The first column gives its function name or / if it's a global variable. The second column gives the variable name and the third column gives the type.

^ TOP

## Example 3 : The constants option

To identify the used constants in a source file we can use the -constants option:

    ./precompile -x mpieee.xml in.cpp constants.out -constants

The constants.out file contains the following information:

    CONSTANTS IN FILE : 'in.cpp'
    constant: '2.2' at line 3, col 14
    constant: '0' at line 7, col 15
    constant: '10' at line 7, col 19
    constant: '0.5' at line 7, col 26
    constant: '2' at line 8, col 9
    constant: '0' at line 11, col 10

This option is used to identify any interesting constants such as mathematical constants. Using the information in the constants output file it is easier to replace such mathematical constants by their multiprecision implementations.

^ TOP

## Example 4 : The skip configuration file

Sometimes we want to skip conversion of specific variables or even entire functions. To do this we use the -c <skip configfile> option. The skipconfig file is an XML file. Let's say we want to skip variable b in the main function of 'in.cpp'. We create the following skipconfig file:

    <document>
      <skip name="example">
        <function name="main"/>
        <variable name="b"/>
      </skip>
    </document>

When running the precompiler with this skipconfig file

    ./precompile -x mpieee.xml in.cpp out.cpp -c skip1.conf

the variable b will not be converted and the resulting output is

    ...
    int main(){
      float  b;
      for(MpIeee a= MpIeee( "0" );a<MpIeee( "10" );a=a+MpIeee( "0.5" )){
        b=a*2;
        {cout<<"a="<<setiosflags((ios::fixed & ios::floatfield))<<a;
    cout.precision(6);cout.fill(' ');cout.width(0);cout.setf(ios::dec,ios::basefield);
    cout<<resetiosflags((ios::fixed & ios::floatfield))<<"";}
      }
      return 0;
    }

To skip every variable in a function, just omit the variable name.

    <document>
      <skip name="example">
        <function name="main"/>
      </skip>
    </document>

When we run the following command:

    ./precompile -c skip2.conf -x mpieee.xml in.cpp out.cpp -noprintf

This will result in the output where everything is copied literally except for the globD variable.

    #include "MpIeee.hh"
    #include "ArithmosIO.hh"

    #include <stdio.h>

    MpIeee globD= MpIeee( "2.2" );

    int main(){
      float  b;
      for(float  a= 0;a<10;a=a+0.5){
        b=a*2;
        printf("a=%f",a);
      }
      return 0;
    }

To skip the global variables like globD we specify an empty function name:

    <document>
      <skip>
        <function name=""/>
        <variable name="globD"/>
      </skip>
    </document>

There is one more variation. To skip all variables of a certain name in all functions. You leave out the function tag. If we want to skip all variables with the name 'tmp' we would create the following line in our skipconfig file:

     <skip><variable name="tmp"/></skip>

^ TOP

## Example 5 : The MpIeee specific command line options (expbits, radix, precision, round)

We start out with a little example program:

    #include <stdio.h>

    int main(){
      double c=1.0/3.0;
      printf( "c=%f\n",c);
    }

We want to test this with arithmos and set some settings. Let's say we want 4 bits exponent, radix=10, precision=5 and rounding down to zero. We will run the precompiler like this:

    ./precompile test.cpp test_out.cpp -x mpieee.xml -expbits 4 -radix 10 -precision 5 -round z -default

The resulting converted file in test_out.cpp will look like this:

    #include <iostream>
    #include <iomanip>
    using namespace std;

    #include "MpIeee.hh"
    #include "ArithmosIO.hh"

    #include <stdio.h>

    int main(){

      MpIeee::fpEnv.setRadix(10);
      MpIeee::fpEnv.setPrecision(5);
      MpIeee::fpEnv.setExponentRange(-6,7);
      MpIeee::fpEnv.setRound(FP_RZ);
      ArithmosIO::setIoMode(ARITHMOS_IO_MPIEEE_DECIMAL);

      MpIeee c= MpIeee( "1.0" )/MpIeee( "3.0" );
      {cout<<"c="<<setiosflags((ios::fixed & ios::floatfield))<<c;
    cout.precision(6);cout.fill(' ');cout.width(0);cout.setf(ios::dec,ios::basefield);
    cout<<resetiosflags((ios::fixed & ios::floatfield))<<"\n";}
    }

We see the correct settings for MpIeee are set in the main routine and the printf is converted to cout to use with classes. We can now quickly experiment with different settings by changing the command line options to experiment with different settings using the precompiler to generate different versions of our program until we are satisfied with the result.

^ TOP

## Example 6 : Arithmos specific command line options (exp, outputverbose, outputmpieee)

In this example we will use the -exp option to set the exponent range. This is the general version of expbits where you can specify any L and U for the exponent (L=minimum, U=maximum). The only requirement is U=-L-1 and the values are limited (have to fit in a long int). We also set some flags using -outputverbose and -outputmpieee options. Remember to use the -default option so that this is inserted into the main() function. We run the precompiler on the in.cpp with the following options:

    ./precompile in.cpp out.cpp -x mpieee.xml -outputmpieee pyh -outputverbose -exp -100 99 -default

We can see in the resulting out.cpp that the exponent range is set and the 3 flags for mpieee and the general verbose flag is set for the arithmos library:

    #include <iostream>
    #include <iomanip>
    using namespace std;

    #include "MpIeee.hh"
    #include "ArithmosIO.hh"

    #include <stdio.h>

    MpIeee globD= MpIeee( "2.2" );

    int main(){

      MpIeee::fpEnv.setRadix(2);
      MpIeee::fpEnv.setPrecision(24);
      MpIeee::fpEnv.setExponentRange(-100,99);
      MpIeee::fpEnv.setRound(FP_RN);
      ArithmosIO::setIoMode(ARITHMOS_IO_VERBOSE|
    			ARITHMOS_IO_MPIEEE_PARAM|
    			ARITHMOS_IO_MPIEEE_BINREP|ARITHMOS_IO_MPIEEE_HEXREP );

      MpIeee b;
      for(MpIeee a= MpIeee( "0" );a<MpIeee( "10" );a=a+MpIeee( "0.5" )){
        b=a*MpIeee( "2" );
        {cout<<"a="<<setiosflags((ios::fixed & ios::floatfield))<<a;
    cout.precision(6);cout.fill(' ');cout.width(0);cout.setf(ios::dec,ios::basefield);
    cout<<resetiosflags((ios::fixed & ios::floatfield))<<"";}
      }
      return 0;
    }

^ TOP

## Example 7 : The init tag and -init option

In the previous 2 examples we showed how the -default option was used to insert arithmos specific settings. In this example we show how to use the more generic -init option. The data contained in the given file init.txt will be inserted literally in the main function.

    ./precompile -x mpieee.xml in.cpp out.cpp -init init.txt

As seen in the resulting output file the code in init.txt was inserted into the main function.

    #include <iostream>
    #include <iomanip>
    using namespace std;

    #include "MpIeee.hh"
    #include "ArithmosIO.hh"

    #include <stdio.h>

    MpIeee globD= MpIeee( "2.2" );

    int main(){
    /*
    Some extra initialization code.
    */

    MpIeee::setPrecision(20);

      MpIeee b;
      for(MpIeee a= MpIeee( "0" );a<MpIeee( "10" );a=a+MpIeee( "0.5" )){
        b=a*MpIeee( "2" );
        {cout<<"a="<<setiosflags((ios::fixed & ios::floatfield))<<a;
    cout.precision(6);cout.fill(' ');cout.width(0);cout.setf(ios::dec,ios::basefield);
    cout<<resetiosflags((ios::fixed & ios::floatfield))<<"";}
      }
      return 0;
    }

Apart from using a seperate file, one can also insert extra initialization code using an <init> tag in the conversion configuration file. In our example we would add this tag to the configuration file:

    <init>
        /*
        Some extra initialization code.
        */
        MpIeee::setPrecision(20);
    </init>

^ TOP

## Example 8 : Custom operation functions

The existing operation functions can be found in the execute member of the ConvertConfig class. When adding a new operation to the ConvertConfig class be sure to update the ConvertConfig::execute member (add another case to the if/else if construction).

          ...
          if( cElem.operation == "toStringConstructor" ) {
            retStr=toStringConstructor( cElem, value );
          }
          else if( cElem.operation == "toConstructor" ) {
            retStr=toConstructor( cElem, value );
          }

          --> write the 'else if' for your operation function here <---
          ...

Short overview of existing operation functions:

Operation | Description
--- | ---
"toStringConstructor" | Calls a string constructor of target with value inside quotes
"toConstructor" | Calls normal constructor of target
"toRational" | Calls a string constructor of target with modified value which is in for "numerator/denomenator"
"toString" | Constructs a std::string from value
"toMatrix" | Specific/custom rule for Mpfr
"typeDef" | Specific/custom rule for MpIeee
"noChange" | Used when you wan't to identify types which don't need a type change
"castIt" | Calls MpIeee specific function to convert to 32bit integer

Assume we want to add our own type of conversion rule called 'callMember'. This may be usefull if the target class has some conversion function as a member of its class. First we add the following lines to the ConvertConfig::execute member of the precompiler:

    string ConvertConfig::execute(const ConvertElem& cElem, const string& value){
     ...
        else if( cElem.operation == "callMember" ){
          retStr = callMember( cElem, value );
        }
     ...

We add the function callMember to the class (by adding some lines to the ConvertConfig.h and ConvertConfig.cpp). Here is the function implementation:

    string ConvertConfig::callMember( const ConvertElem& cElem, const string& value ){
      return value + "." + cElem.target.keyword + "()";
    }

We add the new rule to our configuration file:

     <convert name="to index type">
        <rhs name="rmpieee"/>
        <source name="INDECES"/>
        <operation>callMember</operation>
      </convert>

The operation function callMember is now executed whenever we have an MpIeee on the right hand side and an INDECES type on the left side. We test it on the following input file:

    int main(){
      int a;
      float value=123.4;
      a = value;

      return 0;
    }

In the assignment a = value, the rule 'callMember' will be executed. And the output looks like this:

    int main(){
      int a;
      MpIeee value= MpIeee( "123.4" );
      a = value.int();

      return 0;
    }

This is nice, we added a new type of operation rule and used it. But for this type of rule there is something missing. Let's say the member function to do the conversion is not called 'int()' but some other name. Without the ability to give an extra argument to the conversion rules we would have to add a different operation function to the ConvertConfig class. This will have to be done every time the conversion member name of the target data type changes. We improved the precompiler by making some extra modifications which extend the capabilities of our XML conversion configuration files. This way only the XML file needs to be updated when the target data type changes in some way.

We added a new public local variable to the ConvertElem class called operationArg. And an extra line of code in the setOperation member of ConvertElem:

    void ConvertElem::setOperation(XMLNode::iterator i){
     ...
    	operationArg = e->attribute("argument",""); //additional, optional argument for operations
     ...
    }

And a line in the assignment operator which is also used by the copy constructor

    ConvertElem& ConvertElem::operator=(const ConvertElem& m){
      ...
      operationArg=m.operationArg;
      ...
    }

With this extension we can use the following conversion rule:

      <convert name="to index type">
        <rhs name="rmpieee"/>
        <source name="INDECES"/>
        <operation argument="toInt()">callMember</operation>
      </convert>

In other words, it is now possible to give operation rules an extra argument which can be used in any way desired. Here we use it in our callMember conversion operation to specify the name of the function that we want to call when converting from INDECES data type to the MpIeee data type. The callMember implementation in the ConvertConfig class :

    string ConvertConfig::callMember( const ConvertElem& cElem, const string& value ){
      return value + "." + cElem.operationArg;
    }

This callMember operation can now be used to call arbitrary member functions of our target class. The operation argument extension is included in the algorithm submission. Another example of this extended operation tag is given in the next example.

The final output, using the new callMember operation function with the argument 'toInt()' :

    int main(){
      int a;
      MpIeee value= MpIeee( "123.4" );
      a = value.toInt();

      return 0;
    }

Here is the configuration file used for this example : precompile.xml.
^ TOP

## Example 9 : Writing a custom configuration file.

Up to now we've been using the same configuration file namely the mpieee.xml. Here we will explain how to write your own configuration file with a little example that demonstrates the precompiler can be used for arbitrary type conversions.

Let's say we have two types we want to use: BigNumber and BigFloat. The former is for big integers and the latter for floating point. Let's say we want to convert int, long to BigNumber and we only want double to be converted to BigFloat. We define some source tags, in our case for int,long and double. Here is what our new conversion file looks like up to now:

    <?xml version="1.0"?><document>
      <source name="integer">
        <keyword>long</keyword>
        <keyword>int</keyword>
      </source>

      <source name="floating">
        <keyword> double</keyword>
      </source></document>

So we want the integer to be converted into BigNumber and the floating into BigFloat. We add some target types for BigNumber and BigFloat and 2 conversion rules that do this.

    <?xml version="1.0"?><document>
      <source name="integer">
        <keyword>long</keyword>
        <keyword>int</keyword>
      </source>

      <source name="floating">
        <keyword> double</keyword>
      </source>

      <target name="bigfloat">
        <keyword>BigFloat</keyword>
      </target>

      <target name="bigint">
        <keyword>BigNumber</keyword>
      </target>

      <convert>
            <source name="integer"/>
            <target name="bigint"/>
      </convert>

      <convert>
            <source name="floating"/>
            <target name="bigfloat"/>
      </convert></document>

Now let's run our custom file on the following example:

    int main(){
      int a;
      float b;
      long c;
      double d;

      return 0;
    }

When we use the precompiler with our custom configuration file we get the following output:

    wschrep@pascal:~/precompiler> ./precompile custom.cpp custom_pre.cpp -x custom.xml -noprintf
    wschrep@pascal:~/precompiler> cat custom_pre.cpp
    int main(){
      BigNumber a;
      float b;
      BigNumber c;
      BigFloat d;

      return 0;
    }

We extend our example input source and add an assignment between 2 different types by adding the line:

      d=a;

This is where the operation functions come in to play. Let's assume there is a member in BigFloat called 'ceil' which returns a BigNumber type. We will use the 'callMember' operation function to automate the conversion for such assignments.

Here is the conversion rule we add to our configuration file:

      <convert>
            <target name="bigint"/>
            <target name="bigfloat"/>
            <operation argument="ceil()">callMember</operation>
      </convert>

The output using the new configuration file :

    int main(){
      BigNumber a;
      float b;
      BigNumber c;
      BigFloat d;

      d=a.ceil();
      return 0;
    }

The assignment d=a; was changed into d=a.ceil();. Without the last convert rule the precompiler would have given a warning and just copied the assignment literally. You can run this example yourself with different command line options and/or configuration files and/or input files. Here are the files to replicate this example:

File | Description
--- | ---
custom.xml | Custom configuration file
custom.cpp | An input file
custom_pre.cpp | Generated output file

The user can also add a rhs tag as exercise so that a constant assignment to this type is modified. Look at a complete example which contains these rhs tags.