Скачать MetaTrader 5

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

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

Введение

При программировании на языке 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
Переход на новые рельсы: пользовательские индикаторы в MQL5 Переход на новые рельсы: пользовательские индикаторы в MQL5

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

Вот мы и получили долгожданные MetaTrader 5 и MQL5 Вот мы и получили долгожданные MetaTrader 5 и MQL5

Это очень краткий обзор MetaTrader 5. Я не могу описать все новшества системы за столь короткий период времени - тестирование стартовало 09-09-2009. Это символическая дата, и я уверен, что это будет счастливым числом. Всего несколько дней у меня на руках бета-версия терминала MetaTrader 5 и MQL5. Я не успел опробовать все, что в нем есть нового, но то, что есть, уже впечатляет.

Как построить и протестировать стратегию бинарных опционов в Тестере Стратегий MetaTrader 4 Как построить и протестировать стратегию бинарных опционов в Тестере Стратегий MetaTrader 4

Руководство по построению стратегии бинарных опционов и ее тестированию в Тестере Стратегий MetaTrader 4 с использованием утилиты Binary-Options-Strategy-Tester из Маркета на MQL5.com.

Портфельная торговля в MetaTrader 4 Портфельная торговля в MetaTrader 4

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