
在 MQL4 中处理双精度浮点数
简介
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
例如:
#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 Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/1561
