Working with Doubles in MQL4

MetaQuotes | 4 November, 2009

Introduction

The MQL programming opens new opportunities for the automated trading, many people all over the world already have appreciated it.

When we are writing an Expert Advisor for trading, we must be sure that it will work correctly.

Many newbies often have some questions when the results of some mathematical calculations differ from those expected. The program is compiled and can work, but not as it should. They are checking the code again and again, finding the new "mistakes" in language, in implementation, in functions, etc.

In the most of cases, the careful analysis shows that the language and compiler work correctly, but code has a small error, and it can take a long time to find and correct it.

In this note we will consider a typical programming errors, that occurs while working with double numbers in MQL4 programs.


1. Verifying the numerical values

To verify the calculation results and to debug you can use the function DoubleToStrMorePrecision(double number, int precision) of standard library stdlib.mq4, which allows you to control the numerical values of double numbers to the specified precision.

It will allow to save time in searching for possible errors.

An example:

#include <stdlib.mqh>
int start()
  {
   double a=2.0/3;
   Alert("Standard output:",a,", 8 digits precision:",DoubleToStr(a,8),", 15 digits precision:", DoubleToStrMorePrecision(a,15));
   return(0);
  }  

Result:

Standard output:0.6667, 8 digits precision:0.66666667, 15 digits precision:0.666666666666667

In some cases to show the numerical values of double numbers (for example, in Print, Alert, Comment) it is better to use the functions DoubleToStr and DoubleToStrMorePrecision (from stdlib.mq4) to display more precise values, instead of the standard 4 digits output precision.

For example:

#include <stdlib.mqh>
int start()
  {
   double a=2.0/100000;
   Alert("Standard output=",a,", More precise output=",DoubleToStrMorePrecision(a,15));
   return(0);
  }

returns: "Standard output=0, More precise output=0.000020000000000".


2. Accuracy of the decimal digits precision

Because of the double precision floating-point format there is a limited accuracy of their storage.

For example, if we assume that we have an unlimited precision, as in theory, for any double numbers A and B, the following expressions are always valid:

(A/B)*(B)=A,

A-(A/B)*B=0,

(A/B)*(B/A)=1 etc.

The accuracy of the decimal digits storage in computer is dependent on the fraction size and limited by 52 bits. To illustrate this fact, let's consider the following example.

In the first cycle (i) we are calculating the factorial of 23 (product of the integers from 2 to 23), and the result is: 23!=25852016738884976640000. The result is stored in the variable a of double type.

In the next cycle (j), we are dividing the resulted value a by all of the integers from 23 to 2. It seems that finally we can expect that a=1.

#include <stdlib.mqh>
int start()
  {
   int maxfact=23;
   double a=1;
   for (int i=2; i<=maxfact; i++) { a=a*i; }
   for (int j=maxfact; j>=2; j--) { a=a/j; }
   Alert(" a=",DoubleToStrMorePrecision(a,16));
   return(0);
  }

Instead it we have:

a=1.0000000000000002

As we see we have got an inaccurancy in 16th digit.

If we increase the calculation to 35!, we will get a=0.9999999999999998.

The MQL language has a function NormalizeDouble, which allows to round the double number to the specified precision.

The constants of the double type are stored in memory in a similar way as double variables, therefore it is necessary to take into account the limit of 15 significant digits in their definition.

But do not confuse the accuracy of the decimal digits precision with the calculation precision for the double numbers.

The range of possible values for the double numbers is much wider: from -1.7*e-308 to 1.7*e308.

Let's try to estimate the smallest exponent of the double number.
int start()
  {
  double R=1;
  int minpwr=0;
  while (R>0) {R=R/10; minpwr--;}
  Alert(minpwr);
  return(0);
  }

The program will print -324, but we have to take into account the decimal digits in fraction (+15) and we will get an approximate value for the exponent of the smallest double number.


3. Function NormalizeDouble

The function NormalizeDouble (double value, int digits) rounds the floating point value to the given precision. Returns normalized value of the double type.

For example:

int start()
  {
   double a=3.141592653589;
   Alert("a=",DoubleToStr(NormalizeDouble(a,5),8));
   return(0);
  }

the result is:

a=3.14159000

Note that in trading operations it is impossible to use the unnormalized prices, whose accuracy exceeds at least by one digit demanded by a trading server.
The StopLoss, TakeProfit and Price values for the pending orders should be normalized with the accuracy, which value is stored in predetermined variable Digits.


4. Check for equality of two double numbers

It is recommended to compare two double numbers using the function CompareDoubles(double number1,double number2) of the stdlib.mq4 library, which looks as:

//+------------------------------------------------------------------+
//| correct comparison of 2 doubles                                  |
//+------------------------------------------------------------------+
bool CompareDoubles(double number1,double number2)
  {
   if(NormalizeDouble(number1-number2,8)==0) return(true);
   else return(false);
  }

This function compares number1 and number2 of double type with an accuracy of up to 8 decimal digits.

The example:

#include <stdlib.mqh>
int start()
  {double a=0.123456781;
   double b=0.123456782; 
   if (CompareDoubles(a,b)) {Alert("They are equal");}
   else {Alert("They are different");}
  }

will output:

They are equal

because the are differ only in the 9th digit.

If necessary, you can write your own compare function (with the desired accuracy) in a similar way.


5. Dividing integers

It is necessary to remember, that if we are dividing two integers, we will get an integer number as result.

That's why the code:

int start()
  {
   Alert(70/100);
   return(0);
  }

will output 0, because 70 and 100 are integers.

As well as C/C++ language, in MQL the result of division of two integers will be an integer, in this case it is 0.

However, if the numerator or denominator is double (i.e. has a fractional part), the result will be a double. Therefore Alert (70/100.0); will output the right value 0.7.

For example:
int start()
  { double a=1/3;
    double b=1.0/3;
   Alert("a=",a,", b=",b);
   return(0);
  }

will output "a=0, b=0.3333"

6. Typecasting for integer and double numbers

Let's consider the code:
double xbaseBid=1.2972;
double xBid=1.2973;
double xPoint=0.0001;
int i = 100 + (xBid - xbaseBid)/xPoint;
Alert(i);

We will get 100, but it seems that it should be 101, because of the evident equity: 0.0001/0.0001=1

The same example in C/C++:
double baseBid=1.2972,Bid=1.2973,Point=0.0001;
int i = 100 + (Bid - baseBid)/Point;
printf("%d\n",i);

also gives 100.

To determine the reason of this fact, let's consider the code:

double a=0.99999999999999;
int i = 100 + a;
Alert(i);

which gives i=100.

However, if we perform some accuracy improvement for a:

double a=0.999999999999999;
int i = 100 + a;
Alert(i);

then we will get 101.

The reason of this fact is the use of integer and double numbers, complicated by low accuracy values.

Therefore, using the operations of such type it is recommended to perform the rounding of the similar expressions using the function MathRound(double value)
which returns the rounded double value to the nearest integer:

double baseBid=1.2972;
double xBid=1.2973;
double xPoint=0.0001;
int i = 100 + MathRound((xBid - baseBid)/xPoint);
Alert(i);

In such case we will get the right value 101.

There is a common error in using the function OrderModify, especially for the Trailing Stop programming. The function OrderModify returns an error № 1: ERR_NO_RESULT in the case if the new values for the orders are the same as already defined. So it is necessary to perform a careful check for the equality (use NormalizeDouble) and a careful points size calculations.

Remember that the client terminal allows to change the order parameters only if the new values differ from already defined at least in 1 point.


7. Features of function MathMod


In MQL the MathMod (double v1, double v2) function completely corresponds to the function fmod (double v1, double v2) of MSVC6, because of the direct call for the fmod function from C Runtime Library has used.

In some cases the function fmod of MSVC6 (and also MathMod) gives wrong results.

If you are using the function MathMod in your programs, please replace it to the following function:
double MathModCorrect(double a, double b)
{ int tmpres=a/b;
return(a-tmpres*b);
}

Note that this remark is for MQL4 only, MQL5 calculates this function by its mathematical definition.


Conclusion

Note that the above list is not exhaustive, if you have found something new, which hasn't been described here, look at the comments.

If you haven't found a solution, describe it in the comments, add your code and MQL community professionals will help you.