下载MetaTrader 5

在 MQL4 中处理双精度浮点数

12 四月 2016, 12:15
MetaQuotes Software Corp.
0
1 338

简介

MQL 编程为自动化交易提供了新的机遇,世界各地的众多人士已对其表示赞赏。

当我们编写用于交易的 Expert Advisor 时,必须确保其运行无误。

很多新手经常在一些数学计算的结果跟预期结果不一致时产生困惑。 程序可以编译和工作,但跟预期并不一致。 他们反复检查代码,试图在语言、实现和函数等方面找出新的“错误”。

多数情况下,经过仔细分析后会发现语言和编译工作良好,但代码有小错误,找出并改正需要花费大量的时间。

在本文中我们将研究典型的编程错误,这种错误在处理 MQL4 程序中的双精度数值时会出现。



1. 验证数值

为了验证计算结果和调试,你可以使用标准库 stdlib.mq4 的 DoubleToStrMorePrecision(double number, int precision) 函数,从而将双精度浮点数控制在指定的精确度。

这在搜索可能的错误时可以节省时间。

示例:

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

结果:

标准输出:0.6667,8 位精确度:0.66666667,15 位精确度:0.666666666666667

在某些情况下,为了显示双精度浮点数的数值(例如,在 Print, Alert, Comment中),最好使用 DoubleToStr DoubleToStrMorePrecision 函数(stdlib.mq4)来显示更加精确的值代替标准的 4 位输出精确度。

例如:

#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. 小数位精确度的准确度

由于是双精度浮点格式,其储存的准确度受到限制。

例如,如果我们假设精确度不受限制,从理论上讲,对于任何的双精度浮点数 A 和 B,以下的表达式始终成立:

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

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

(A/B)*(B/A)=1 等

小数位在计算机内储存的准确度取决于小数部分大小,并限制在 52 位。 为了说明该情况,我们来看看以下的例子:

在第一个循环(i)中,我们计算 23 的阶乘(从 2 到 23 的整数的乘积),结果为: 23!=25852016738884976640000. 结果储存在双精度类型的变量 a 中。

在下一个循环(j)中,我们将得到的结果 a 除以从 23 到 2 的所有整数。 最后我们期待的结果是 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 函数,可以将双精度浮点数圆整为指定的精确度。

双精度类型的常量在内存中储存的方式跟双精度变量类似,因此有必要在定义它们时考虑限制在 15 位有效数字。

但不要将小数位精确度的准确度和双精度浮点数的计算精确度混淆。

双精度浮点数的可能值范围更宽:从 -1.7*e-308 到 1.7*e308。

我们来尝试估算双精度浮点数的最小指数。
int start()
  {
  double R=1;
  int minpwr=0;
  while (R>0) {R=R/10; minpwr--;}
  Alert(minpwr);
  return(0);
  }

程序会输出 -324,但我们必须考虑小数部分的小数位(+15),将会得到最小双精度浮点数的指数的近似值。



3. NormalizeDouble 函数

NormalizeDouble (double value, int digits) 函数将浮点数值圆整到给定的精确度。 返回标准化双精度型数值。

例如:

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

结果为:

a=3.14159000

注意:在交易操作中,是 不可能使用非标准价格的,因为其准确度至少超过了交易服务器所要求的一位。
挂单的止损、获利和价格值应以某精确度标准化,它的值储存在预定义的变量 Digits 中。



4. 检查两个双精度浮点数是否相等

建议使用 stdlib.mq4 库的 CompareDoubles(double number1,double number2) 函数 比较两个 双精度 浮点数, 如下所示:

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

该函数比较双精度浮点数 1 和 2,精确度达到 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");}
  }

输出为:

它们相等

因为它们只在第 9 位才不同。

如果必要,你可以类似的编写自己的比较函数(根据所需的准确度)。



5. 整数相除

需要记住,如果我们将两个整数相除,也会得到一个整数。

这就是为什么下面的代码:

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

会输出 0,因为 70 和 100 都是整数。

跟 C/C++ 语言一样,在 MQL 中两个整数相除的结果将是整数,在本例中结果为 0。

但是,如果分子或分母是双精度(即具有小数部分),结果也将是双精度。 因此,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. 整数和双精度浮点数的类型转换

我们来考虑以下代码:
double xbaseBid=1.2972;
double xBid=1.2973;
double xPoint=0.0001;
int i = 100 + (xBid - xbaseBid)/xPoint;
Alert(i);

我们将得到 100,但似乎它应该等于 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。

但是,如果我们对下面代码的精确度进行一些改进:

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

就会得到 101。

原因在于:整数和双精度浮点数的使用因精确度低而复杂化。

因此,使用这种类型的运算时,建议使用 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。

在使用 OrderModify 函数时,有一个普遍的错误,尤其在跟踪止损编程中。 NormalizeDouble OrderModify 函数返回一个 № 1 错误: 如果订单的新值跟已经定义的值一样,就会出现 ERR_NO_RESULT 错误。 因此,有必要仔细的检查是否相等(使用 NormalizeDouble)和仔细的计算点数。

记住:只有在新值和已定义的值差距至少 1 个点时,客户端才允许更改订单参数。



7. MathMod 函数的功能

在 MQL 中,MathMod (double v1, double v2) 函数跟 MSVC6 的 fmod (double v1, double v2) 函数完全对应, 因为已经 从 C Runtime 库直接调用了 fmod 函数。

在某些情况下, MSVC6 的 fmod 函数 (以及MathMod 函数)会给出错误的结果。

如果你在程序中使用了 MathMod 函数,请替换成以下函数:
double MathModCorrect(double a, double b)
{ int tmpres=a/b;
return(a-tmpres*b);
}

注意:这只适用于 MQL4,MQL5 按照其数学定义计算该函数。



总结

注意:上述列表并不完善,如果你发现了这里没有描述的新东西,请查看注释。

如果你还没有找到解决方法,请在注释中进行描述并附上你的代码,MQL 社区的专业人士将会帮助你。


本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/1561

MetaTrader 4 客户端的程序文件夹 MetaTrader 4 客户端的程序文件夹

本文描述了 MetaTrader 4 客户端程序文件夹的内容。 对于那些已经开始掌握客户端操作细节的用户,本文将会非常有用。

是睡,还是不睡? 是睡,还是不睡?

本文提出了 Sleep() 函数的替代用法,在 EA 的操作之间实现暂停。 所讨论的这种方法可以巧妙利用机器时间。

神经网络诀窍 神经网络诀窍

本文面向"多层"蛋糕烘焙初学者。

具有最小延迟的有效平均算法: 在指标和 Expert Advisor 中使用 具有最小延迟的有效平均算法: 在指标和 Expert Advisor 中使用

本文介绍笔者开发的更高质量的自定义平均函数: JJMASeries()、JurXSeries()、JLiteSeries()、ParMASeries()、LRMASeries()、T3Series() 和 MASeries()。 作者考虑在指标中使用 SmoothXSeries() 函数的调用进行上述函数的热替换。