程序库: CDouble & CDoubleVector

 

CDouble & CDoubleVector:

用于 MQL 开发的常用舍入方法函数库,用于类型 (double) 的元初包装类和 CDouble 对象的向量。 MQL5 和 MQL4 兼容!

用于 MQL 开发的常用舍入方法函数库,用于类型 (double) 的元初包装类和 CDouble 对象的向量。 MQL5 和 MQL4 兼容!

版本 1.02: (2018.02.18)

  • 修正了舍入结果偏离预期结果的错误。 鸣谢 AMRALI!

版本 1.01:

  • 修复了算术运算符未返回舍入值的错误。
  • 添加了品种 setter (赋值) 方法以在调用构造函数后设置品种。


作者: nicholi shen

 

尝试删除 约束

Note: Only one arithmetic operator can be used per statement.

并尝试使比较运算符 "静态"。

 
fxsaber:

尝试删除约束

并尝试使比较运算符成为 "静态 "运算符。


操作符不能声明为 "静态"。

每条语句可以有一个以上的(重载)算术运算,条件是每组两个操作数都按正确的顺序用括号包装起来。我仍然不建议这样做。

CDouble foo = 3, bar = 4, spam = 3;

CDouble error   = foo+bar+spam; //ERROR

CDouble error_too = (pi2 + pi5)+pi2; //ERROR

CDouble correct = foo+(bar+spam);// 确定!

 

为了消除任何可能的混淆,对于包含多个运算符 的语句,正确的处理方法是 使用重载运算符,而是使用其中一个适用的值获取方法。

CDouble sum, foo=3, bar=2, spam=1;
sum = foo.AsRawDouble() + bar.AsRounded() + spam.AsRoundedTick();
Print(sum.ToString());
//6
 
nicholishen:

运算符不能声明为 "静态"。

每条语句可以有一个以上的(重载)算术运算,但每组两个操作数必须按正确的顺序用括号包装起来。我仍然不建议这样做。

class CDouble2 : public CDouble
{
private:
  static CDouble2 TmpDouble;
  
public:
  const CDouble2* const operator +( const double Value ) const
  {
    CDouble2::TmpDouble = this.m_value + Value;
    
    return(&CDouble2::TmpDouble);
  }
  
  const CDouble2* const operator +( const CDouble2 &Other ) const 
  {
    return(this + Other.m_value);
  }
  
  static CDouble2* const Compare2( const double Value )
  {
    CDouble2::TmpDouble = Value;
    
    return(&CDouble2::TmpDouble);
  }
  
  static CDouble2* const Compare2( const CDouble2 &Other )
  {
    CDouble2::TmpDouble = Other;
    
    return(&CDouble2::TmpDouble);
  }  
};

static CDouble2 CDouble2::TmpDouble;

#define _CP(A) CDouble2::Compare2(A)

#define  PRINT(A) Print(#A + " = " + (string)(A));

void OnStart()
{
  CDouble2 foo = 3, bar = 4, spam = 3;
  CDouble2 error   = foo+bar+spam + foo+bar+spam; //OK!
  
  PRINT(error.ToString()); // 10
  PRINT(_CP(foo + error + 5) > 2);
  PRINT(_CP(25) > foo + bar + 7 +spam);
  PRINT((foo + bar + spam + 9).ToString());
  PRINT((_CP(9) + foo).ToString());
  PRINT(foo + 7 > 11)
}

运算结果

error.ToString() = 20
_CP(foo+error+5)>2 = true
_CP(25)>foo+bar+7+spam = false
(foo+bar+spam+9).ToString() = 19
(_CP(9)+foo).ToString() = 12
foo+7>11 = false
 
fxsaber:

结果


这非常聪明,我非常喜欢,但对大多数用户来说太聪明了......(我们都在论坛上被指责过;)我会将您的更改提交到我的个人库中,其他人也可以这样做,但为了更多用户的利益,我会保持简单,并坚持使用官方推荐的调用其中一个获取方法。(例如,num1.AsRounded() * num2.AsRounded() + num3.AsRounded() )


我个人喜欢 (num1*num2+num3).AsRounded()


CDouble2 的挑战:

void Func(double param)
{
}

void OnStart()
{
   CDouble2 foo = 2, bar = 3;
   double number = foo+bar; //ERROR
   Func(foo+bar); //ERROR
}
 

* 版本 1.01:

  • 修正了算术运算符不能返回四舍五入值的错误。
  • 添加了符号设置方法,以便在调用构造函数后设置符号
 

你好,nicholishen。我测试您的库有一段时间了。这是一个很棒的库,它使价格和批量的四舍五入变得很容易。

但是,我对四舍五入方法的准确性有些担心。我在函数 RoundToStep()、RoundToStepUp()、RoundToStepDown() 和 RoundToTick() 中发现了很多四舍五入错误。这些错误总是发生在以 5 结尾的边缘数字上(如 1.12345)。

例如,CDouble::RoundToStep(1.700605, 0.00001) 返回 1.70060,而不是正确的结果 1.70061。

等式 round(number / point) * point 应更正为 round(number * power) / power,其中 point 和 power 均来自您希望舍入的小数位数。

因为 1 点的值应该 = 0.00001,但实际上编码为 0.0000100000000000000008180305391403130954586231382563710(64 位双精度浮点数)。这就导致四舍五入法(round(number / point) * point)的最终结果经常与正确结果相差 1 个点(0.00001)。

此外,为了进行正确的 "算术 "四舍五入(远离零的中点四舍五入),一个好的方法是添加或减去一个半埃普西隆作为校正。(根据 IEEE-754 规范的要求,这将抵消处理器应用的半匀舍入,尤其是在中点边缘情况下)。

mql 的NormalizeDouble() 函数能 正确处理所有这些问题,你应该使用它来进行正确的 "算术 "四舍五入。

这里还有我写的一个算术四舍五入函数的源代码,你可以自己测试。该函数的结果与 NormalizeDouble() 完全相同。我的函数运行速度更快,并且支持更高水平的四舍五入精度。(MQL 的 NormalizeDouble() 仅限于小数点后 8 位)。

/**
 * MidpointRounding 远离零("算术 "四舍五入)
 * 使用半epsilon 进行修正。(这抵消了在边缘情况下应用的 IEEE-754
 * 半匀舍入)。
 */
double RoundCorrect(double num, int precision) {
        double c = 0.5 * DBL_EPSILON * num;
// double p = MathPow(10, precision); //slow
        double p = 1; while (precision--> 0) p *= 10;
        if (num < 0)
                p *= -1;
        return MathRound((num + c) * p) / p;
}
 

此外,这里还有一个脚本,您可以用来调试 CDouble 库中的四舍五入精度。希望对你有用。

#property strict

#define  PRINT(A) Print(#A + " = ", (A))
#define  forEach(element, array)  for (int __i = 0, __max = ArraySize((array)); __i < __max && ((element) = array[__i]) == (element); __i++)

#include "CDouble.mqh"

//+------------------------------------------------------------------+
//||
//+------------------------------------------------------------------+
string DoubleToFixed(double number, int decimals = 55) {

        return StringFormat(StringFormat("%%#.%if", decimals), number);
}

//+------------------------------------------------------------------+
//||
//+------------------------------------------------------------------+
void OnStart() {

        // 测试某些边缘情况的四舍五入
        double numbers_3[] = {1.005, 2.175, 5.015, 16.025};
        double numbers_6[] = {1.011885, 1.113325, 1.143355, 1.700605};

        double num;

        forEach (num, numbers_3) {

                Print("----------------------------------------------");
                PRINT( num );
                // 比较 3 个函数(四舍五入到两位数)
                PRINT( CDouble::RoundToStep(num, 0.01) );
                PRINT( NormalizeDouble(num, 2) );
                PRINT( RoundCorrect(num, 2) );
        }

        forEach (num, numbers_6) {

                Print("----------------------------------------------");
                PRINT( num );
                // 比较 3 个函数(四舍五入到 5 位数)
                PRINT( CDouble::RoundToStep(num, 0.00001) );
                PRINT( NormalizeDouble(num, 5) );
                PRINT( RoundCorrect(num, 5) );
        }

        // CDouble 库中出现四舍五入问题的原因
        Print("----------------------------------------------");

        PRINT( DoubleToFixed(0.01, 55) );      // 0.0000100000000000000008180305391403130954586231382563710
        PRINT( DoubleToFixed(0.00001, 55) );   // 0.0100000000000000002081668171172168513294309377670288086

        // 通过精确相等来比较 NormalizeDouble 和 RoundCorrect
        Print("----------------------------------------------");

        PRINT( NormalizeDouble(numbers_6[0], 5) == RoundCorrect(numbers_6[0], 5) );  // true
        PRINT( NormalizeDouble(numbers_6[0], 4) == RoundCorrect(numbers_6[0], 4) );  // true
        PRINT( NormalizeDouble(numbers_6[0], 3) == RoundCorrect(numbers_6[0], 3) );  // true
        PRINT( NormalizeDouble(numbers_6[0], 2) == RoundCorrect(numbers_6[0], 2) );  // true
        PRINT( NormalizeDouble(numbers_6[0], 1) == RoundCorrect(numbers_6[0], 1) );  // true

}

 
amrali:

你好,nicholishen。我测试您的库有一段时间了。这是一个很棒的库,它使价格和批量的四舍五入变得很容易。

但是,我对四舍五入方法的准确性有些担心。我在函数 RoundToStep()、RoundToStepUp()、RoundToStepDown() 和 RoundToTick() 中发现了很多四舍五入错误。这些错误总是发生在以 5 结尾的边缘数字上(如 1.12345)。

例如,CDouble::RoundToStep(1.700605, 0.00001) 返回 1.70060,而不是正确的结果 1.70061。

等式 round(number / point) * point 应更正为 round(number * power) / power,其中 point 和 power 均来自您希望舍入的小数位数。

因为 1 点的值应该 = 0.00001,但实际上编码为 0.0000100000000000000008180305391403130954586231382563710(64 位双精度浮点数)。这导致四舍五入方法 round(number / point) * point 的最终结果经常与正确结果偏差 1 个点(0.00001)。

此外,为了进行正确的 "算术 "四舍五入(远离零的中点四舍五入),一个好的方法是添加或减去一个半埃普西隆作为校正。(根据 IEEE-754 规范的规定,这将抵消处理器应用的半匀舍入,尤其是在中点边缘情况下)。

mql 的NormalizeDouble() 函数可以 正确处理所有这些问题,您应该使用它来进行正确的 "算术 "四舍五入。

这里还有我写的一个算术四舍五入函数的源代码,你可以自己测试。该函数的结果与 NormalizeDouble() 完全相同。我的函数运行速度更快,支持更高水平的四舍五入精度。(MQL 的 NormalizeDouble() 仅限于小数点后 8 位)。

感谢您指出这一点。我将更新代码,使用 NormalizeDouble 代替四舍五入。

step * NormalizeDouble(number_to_round / step, 0)
 
amrali:

你好,nicholishen。我测试您的库有一段时间了。这是一个很棒的库,它使价格和批量的四舍五入变得很容易。

但是,我对四舍五入方法的准确性有些担心。我在函数 RoundToStep()、RoundToStepUp()、RoundToStepDown() 和 RoundToTick() 中发现了很多四舍五入错误。这些错误总是发生在以 5 结尾的边缘数字上(如 1.12345)。

例如,CDouble::RoundToStep(1.700605, 0.00001) 返回 1.70060,而不是正确的结果 1.70061

等式 round(number / point) * point 应更正为 round(number * power) / power,其中 point 和 power 均来自您希望舍入的小数位数。

因为 1 点的值应该 = 0.00001,但实际上编码为 0.0000100000000000000008180305391403130954586231382563710(64 位双精度浮点数)。这就导致四舍五入法(round(number / point) * point)的最终结果经常与正确结果相差 1 个点(0.00001)。

不过,从交易的角度来看,您需要的是 1.70060 或 1.70061,它们都是正确的。因此,您可能希望根据自己的交易操作 选择最佳的方案,而不是依赖于数学四舍五入方案。