Скачать MetaTrader 5

Особенности работы с числами типа double в MQL4

2 ноября 2009, 12:38
MetaQuotes Software Corp.
15
4 643

Введение

При программировании на языке MQL4 у новичков порой возникают ситуации, когда результаты некоторых математических вычислений отличаются от ожидаемых. При этом программа компилируется и работает, но не так, как нужно. Они начинают разбираться в программе, находят новые "ошибки" в языке, реализации функций и т.п. В большинстве случаев последующий анализ показывает, что с языком и компилятором все нормально, а вот в тексте кода закралась досадная ошибка, поиск которой может занять много времени.

В данной заметке собраны советы по решению наиболее часто возникающих ошибок при работе с числами типа double в программах на MQL4.


1. Контроль над численными значениями

Для проверки результатов расчетов и отладки программ можно использовать функцию string DoubleToStrMorePrecision(double number, int precision); стандартной библиотеки stdlib.mq4, которая позволяет проконтролировать численные значения чисел типа double до указанного знака.

Это позволит сэкономить время при поиске возможных ошибок.

Пример использования:
#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);
  }  

Результат:

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


Во многих случаях при выводе численных значений чисел c плавающей точкой (например, при использовании Print, Alert, Comment) вместо стандартного вывода (только первых 4 знаков после запятой) лучше использовать функцию DoubleToStrMorePrecision для более точного контроля численных значений.

Например код:

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

в результате выведет: "Standard output=0, More precise output=0.000020000000000".


2. Погрешности при работе с числами типа double

Специфика формата хранения чисел double в компьютере приводит к ограничению точности их хранения и возникновению погрешностей при работе с ними.

Например, при использовании бесконечной точности вычислений, для любых действительных чисел A и B всегда будут справедливы тождества:

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

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

(A/B)*(B/A)=1 и т.п.

В компьютере точность хранения количества десятичных знаков чисел типа double определяется размерами мантиссы и ограничена 52 битами.

Рассмотрим следующий пример, иллюстрирующий указанную потерю точности. Приведенная ниже программа вычисляет в цикле по i произведение целых чисел до 23 (23!=25852016738884976640000), результат расчетов хранится в переменной a типа double. Затем в цикле по j производится деление числа a на каждое из целых чисел до 23. В результате логично было бы ожидать 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);
  }

Однако в результате имеем:

a=1.0000000000000002

Таким образом при работе с целыми числами мы получили погрешность в 16-м знаке.

Если увеличить расчет до 35!, то получим a=0.9999999999999998.

В языке MQL существует функция NormalizeDouble, позволяющая округлить число типа double до указанной точности.

Поскольку константы типа double в памяти хранятся в том же виде, что и числа типа double, поэтому при определении констант необходимо учитывать ограничение на 15 значимых цифр после запятой.

Однако не следует путать рассмотренную выше точность представления чисел double с пределами их изменения - они гораздо шире: от -1.7*e-308 до 1.7*e308.

Приближенно можно оценить минимальную степень числа double, которое будет неотличимо от 0 при помощи следующего кода:

int start()
  {
  double R=1;
  int minpwr=0;
  while (R>0) {R=R/10; minpwr--;}
  Alert(minpwr);
  return(0);
  }



3. Функция NormalizeDouble

Функция double NormalizeDouble (double value, int digits), осуществляет округление числа value до точности в digits знаков.

В примере:

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

результат будет

a=3.14159000

Функцию NormalizeDouble() необходимо применять при использовании чисел типа double в качестве аргументов функций для проведения торговых операций. В торговых операциях нельзя использовать ненормализованные цены, чья точность превышает требуемую торговым сервером хотя бы на один знак.

Рассчитываемые значения StopLoss, TakeProfit, а также значения цены открытия отложенных ордеров должны быть нормализованы с точностью, значение которой хранится в предопределенной переменной Digits.


4. Особенности сравнения чисел типа double

Операцию сравнения двух чисел double на равенство рекомендуется производить при помощи функции bool CompareDoubles(double number1,double number2) стандартной библиотеки stdlib.mq4, которая имеет вид:

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

Данная функция производит сравнение чисел number1 и number2 типа double с точностью до 8 знака после запятой.

Пример:

#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");}
  }

выведет

They are equal

поскольку числа a и b различаются лишь в 9-м знаке.

В случае необходимости использования более высокой точности аналогичным образом можно написать свою функцию сравнения чисел до нужной точности.


5. Деление целых чисел

Следует помнить, что при делении двух целых чисел результатом будет целое число.

Поэтому код:

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

выведет 0, т.к. 70 и 100 - целочисленные значения. Как и в языке C, в MQL4 результатом деления целого числа на целое будет целое число, в данном случае 0.

Однако если одно из значений является числом типа double (т.е. имеет дробную часть), то результат деления будет числом типа double. Поэтому Alert(70/100.0); даст число 0.7. Также следует помнить про правила приведения типов и аккуратнее оформлять выражения.

Например код:

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

выведет a=0, b=0.3333


6. Приведение типов - integer и double

Рассмотрим участок кода:
double xbaseBid=1.2972;
double xBid=1.2973;
double xPoint=0.0001;
int i = 100 + (xBid - xbaseBid)/xPoint;
Alert(i);

В результате работы будет выведено число 100, хотя казалось бы, i должно быть равным 101, поскольку 0.0001/0.0001=1.

Аналогичный пример на C/C++:

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

тоже выдает 100.

Для исследования причины данного обстоятельства рассмотрим код:
double a=0.99999999999999;
int i = 100 + a;
Alert(i);

Результатом работы также будет вывод числа i=100.

Однако если несколько улучшить точность числа a:

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

то получим 101. Причина состоит в смешанном использовании целочисленных чисел и чисел с плавающей точкой, усугубленная пониженной точностью значений.

Поэтому при проведении операций такого типа рекомендуется проведение округления подобных выражений при помощи функции double MathRound(double value) которая возвращает значение, округленное до ближайшего целого числа, указанного числового значения:

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

В этом случае мы получим правильное значение 101.

Часто встречается ошибка (особенно в участках кода, отвечающего за Trailing Stop) при неправильном проведении сравнения чисел типа double и последующем их использовании при вызове функции OrderModify(), которая, при попытке изменения уже установленных таких же значений, выдает ошибку с номером 1: ERR_NO_RESULT.

Особенно внимательным следует быть при проведении операций сравнения (помните про нормализацию) и подобных выражений для подсчета количества пунктов. Следует помнить, что терминал допускает изменение ордеров функцией OrderModify только в том случае, если новые численные значения отличаются от старых хотя бы на 1 пункт.


7. Особенность функции MathMod

Результат работы функции MathMod(double v1, double v2) в языке MQL4 полностью соответствует результату работы функции fmod(double v1, double v2) математической библиотеки MSVC6, поскольку при ее выполнении используется прямой вызов данной функции в C Runtime Library. В некоторых случаях функция fmod в MSVC6 (и соответственно MathMod), выдает неверный результат.

Если в Ваших программах используется данная функция, замените вызов MathMod вызовом следующей функции, которая всегда возвращает верный результат:

double MathModCorrect(double a, double b)
{ int tmpres=a/b;
return(a-tmpres*b);
}

Следует отметить, что данная особенность имеет место только для MQL4, в MQL5 расчет данной функции производится согласно математическому определению функции.


Заключение

Отметим, что приведенный перечень не является исчерпывающим, и если Вы обнаружили какую-то новую ситуацию, которая здесь не описана, посмотрите в комментариях ниже. Если решение не нашлось, сами опишите ее в комментарии, приведите участок кода и специалисты сообщества MQL4 постараются Вам помочь.

Последние комментарии | Перейти к обсуждению на форуме трейдеров (15)
MQL4 Comments
MQL4 Comments | 1 сен 2010 в 12:20

Цитата:

"Для проверки результатов расчетов и отладки программ можно использовать функцию string DoubleToStrMorePrecision(double number, int precision); стандартной библиотеки stdlib.mq4, которая позволяет проконтролировать численные значения чисел типа double до указанного знака. "
Конец цитаты.

Не нашел в справке ничего по функциям:

----------------------------------------------------------------------------

#import "user32.dll"
int MessageBoxA(int hWnd, string lpText, string lpCaption, int uType);

#import "stdlib.ex4"
string ErrorDescription(int error_code);
int RGB(int red_value, int green_value, int blue_value);
bool CompareDoubles(double number1, double number2);
string DoubleToStrMorePrecision(double number, int precision);
string IntegerToHexString(int integer_number);

---------------------------------------------------------------------------

Случайно наткнулся на эту статью. Что это за функции и с чем их едят?

У меня есть полное описание "TurboC 2.0" в пяти томах. Там всё есть, от структуры меню и команд редактора, до описания каждой функции и возможных ключей к этим функциям. Для MetaTrader4, есть полное описание одним-двумя файлами "в электронном" виде?

vlad-755
vlad-755 | 1 мар 2013 в 06:29

О некорректном исполнении кода.

При тестировании Эксперта выявились ошибки исполнения кода:
- исполнение оператора цикла for(i=0;i<OrdersTotal();i++) прерывается следующим тиком,
- ордера buy выбираются и закрываются в произвольном порядке.

Подскажите, пожалуйста, как установить и устранить их причину?
Код программы и отчёт из журнала тестера прилагаю.

С уважением.  Vladislav.

Код программы.

//+------------------------------------------------------------------+
//|                                                     Poligon1.mq4 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|Тест оператора цикла.                                             |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      ""
//+------------------------------------------------------------------+
//|Объявленные переменные                                            |
//+------------------------------------------------------------------+
int Mn=357;//Magic number
int i;//Показатель счётчика итераций
double PriTP;//TP серии
int Tick;//Счётчик тиков
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
//----Установка ордеров
OrderSend(Symbol(),OP_BUY,0.5,Ask,0,Bid-350*Point,Ask+350*Point,NULL,Mn,0,CLR_NONE);//
OrderSend(Symbol(),OP_BUY,1,Ask,0,Bid-250*Point,Ask+250*Point,NULL,Mn,0,CLR_NONE);//
OrderSend(Symbol(),OP_BUY,1.5,Ask,0,Bid-150*Point,Ask+150*Point,NULL,Mn,0,CLR_NONE);//
OrderSend(Symbol(),OP_BUY,2.5,Ask,0,Bid-150*Point,Ask+150*Point,NULL,Mn,0,CLR_NONE);//
OrderSend(Symbol(),OP_BUY,3.5,Ask,0,Bid-150*Point,Ask+150*Point,NULL,Mn,0,CLR_NONE);//
OrderSend(Symbol(),OP_BUYLIMIT,1,Ask-150*Point,0,Bid-400*Point,
Ask+250*Point,NULL,Mn,0,CLR_NONE);//
OrderSend(Symbol(),OP_SELLSTOP,3,Ask-300*Point,0,Bid+150*Point,
Ask-750*Point,NULL,Mn,0,CLR_NONE);//
//----
   PriTP=Bid;//PriTP
   Print("Кс0:"," OrdersTotal=",OrdersTotal());//Сообщение
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
//----
  
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
//----Счётчик Tick
   Tick++;//Tick
//----Закрываем ордера BUY
   for(i=0;i<OrdersTotal();i++)//Инициализация цикла
   {
   Print("Кс1:"," Tick=",Tick," i=",i);//Сообщение
   if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==true)//Выбор ордера i
    {
   if(OrderSymbol()==Symbol())//Символ
     {
   if(OrderMagicNumber()==Mn)//Идентификационное число
      {
   if(OrderType()==OP_BUY && OrderTakeProfit()>PriTP)//Тип операции текущего выбранного ордера
       {
   OrderClose(OrderTicket(),OrderLots(),Bid,0,CLR_NONE);//Закрытие позиции
       }//Закрыто if(OrderType()==OP_BUY && OrderTakeProfit()>PriTP)
      }//Закрыто if(OrderMagicNumber()==Mn)
     }//Закрыто if(OrderSymbol()==Symbol()
    }//Закрыто if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==true)
   }//Закрыто for(i=0;i<OrdersTotal();i++)
//----
   return(0);
  }

Записи из журнала тестера.

2013.03.01 12:12:57 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=5 i=1
2013.03.01 12:12:57 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=5 i=0
2013.03.01 12:12:33 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=4 i=1
2013.03.01 12:12:33 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=4 i=0
2013.03.01 12:12:09 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=3 i=1
2013.03.01 12:12:09 2012.09.03 00:00  Poligon1 EURUSD,M15: close #4 buy 2.50 EURUSD at 1.25773 sl: 1.25616 tp: 1.25923 at price 1.25770
2013.03.01 12:12:09 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=3 i=0
2013.03.01 12:11:45 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=2 i=2
2013.03.01 12:11:45 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=2 i=1
2013.03.01 12:11:45 2012.09.03 00:00  Poligon1 EURUSD,M15: close #2 buy 1.00 EURUSD at 1.25773 sl: 1.25516 tp: 1.26023 at price 1.25768
2013.03.01 12:11:45 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=2 i=0
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=1 i=3
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: close #5 buy 3.50 EURUSD at 1.25773 sl: 1.25616 tp: 1.25923 at price 1.25766
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=1 i=2
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: close #3 buy 1.50 EURUSD at 1.25773 sl: 1.25616 tp: 1.25923 at price 1.25766
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=1 i=1
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: close #1 buy 0.50 EURUSD at 1.25773 sl: 1.25416 tp: 1.26123 at price 1.25766
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс1: Tick=1 i=0
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: Кс0: OrdersTotal=7
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: open #7 sell stop 3.00 EURUSD at 1.25473 sl: 1.25916 tp: 1.25023 ok
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: open #6 buy limit 1.00 EURUSD at 1.25623 sl: 1.25366 tp: 1.26023 ok
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: open #5 buy 3.50 EURUSD at 1.25773 sl: 1.25616 tp: 1.25923 ok
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: open #4 buy 2.50 EURUSD at 1.25773 sl: 1.25616 tp: 1.25923 ok
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: open #3 buy 1.50 EURUSD at 1.25773 sl: 1.25616 tp: 1.25923 ok
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: open #2 buy 1.00 EURUSD at 1.25773 sl: 1.25516 tp: 1.26023 ok
2013.03.01 12:11:21 2012.09.03 00:00  Poligon1 EURUSD,M15: open #1 buy 0.50 EURUSD at 1.25773 sl: 1.25416 tp: 1.26123 ok
2013.03.01 12:11:21 Poligon1 test started
2013.03.01 12:11:21 Poligon1 EURUSD,M15: loaded successfully

//+------------------------------------------------------------------+

MQL4 Comments
MQL4 Comments | 29 авг 2013 в 06:44

Внимание!

Билд 509. Функция MathModCorrect(), представленная в данной статье как замена функции MathMod(), при значениях делимого и делителя равных 0.6 и 0.2 соответственно, возвращает значение 0.2!

Происходит это из-за того, что int tmpres загадочным образом приобретает значение 2.

С другими аргументами подобного казуса не обнаружено.

Будьте бдительны!

Всем профитов!

MQL4 Comments
MQL4 Comments | 29 авг 2013 в 07:28

Добавлю к предыдущему посту.

Сам не математик. Пообщался со знакомым, разбирающимся в данной области. Подобные казусы могут возникать и с другими числами double.

На что хочется обратить внимание авторов статей: коллеги, не пишите "всегда" (в последнем предложении), если не уверены на 100% в истинности этого утверждения.

Надеюсь, информация окажется полезной.

Vasyl Nosal
Vasyl Nosal | 8 апр 2015 в 10:52
int start()
  {
   Alert(70/100);
   return(0);
  }
Для того чтобы вышло правильное деление достаточно написать 70.0/100 или 70/100.0 или 70.0/100.0
Проект Meta COT - новые горизонты анализа отчетов CFTC в терминале MetaTrader 4 Проект Meta COT - новые горизонты анализа отчетов CFTC в терминале MetaTrader 4

Статья посвящена вопросам использования при торговле в MetaTrader индикатора открытого интереса (Open Interest), публикуемого CFTC. В ней подробно описан предлагаемый проект, показано как загружать необходимую информацию. С помощью торгового робота, входящего в проект, исследуется эффективность концепции изложенной в статье, делаются итоговые выводы, высказываются конструктивные предложения.

Защищайтесь, господа разработчики! Защищайтесь, господа разработчики!

Вопросы защиты своей интеллектуальной собственности все еще остаются большой проблемой. В статье описаны основные принципы защиты разработок на MQL4, используя которые можно если не совсем побороть воровство результатов многодневного труда разработчика злоумышленником, то, по крайней мере, настолько усложнить вору его "труд", чтобы ему просто не захотелось заниматься этим.

Библиотека матричной алгебры LibMatrix (часть первая) Библиотека матричной алгебры LibMatrix (часть первая)

Автор знакомит читателей с простой библиотекой матричной алгебры. Рассматриваются основные функции и их особенности.

Alert и Comment для внешних индикаторов. Мультивалютный анализ посредством внешнего сканирования Alert и Comment для внешних индикаторов. Мультивалютный анализ посредством внешнего сканирования

Алерт для мультивалютного и мультитаймфреймного анализа внешних индикаторов. В статье рассматривается способ получения информации о событиях происходящих во внешних индикаторах без присоединения их на график и без открытия самих графиков. Назовем это внешним сканированием.