ライブラリ: CDouble & CDoubleVector

 

CDouble & CDoubleVector:

MQL開発、型 (double) のプリミティブラッパークラス、および CDouble オブジェクトのベクターで使用される一般的なラウンディングメソッドライブラリ。 MQL5 と MQL4 の互換性!

作者: nicholi shen

 

制約を取り除く

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

そして、比較演算子を "静的 "にしてみる。

 
fxsaber:

制約を取り除く

そして、比較演算子を "static "にしてみてください。


演算子を「静的」に宣言することはできません。

2つのオペランドの各セットが正しい順序で括弧でくくられるのであれば、ステートメントごとに1つ以上の(オーバーロード)算術演算を行うことができます。私はまだこれを推奨しない。

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);// OK!

 

混乱を避けるため、複数の演算子を 持つステートメントの算術演算を処理する適切な方法は、オーバーロードされた演算子を 使用せず 、該当する値取得メソッドのいずれかを使用することです。

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

演算子は'static'と宣言することはできない。

つのオペランドが正しい順序で括弧でくくられていれば、1つのステートメントに1つ以上の(オーバーロード)算術演算を行うことができます。しかし、これはお勧めできない。

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; //エラー
   Func(foo+bar); //エラー
}
 

* バージョン 1.01:

  • 算術演算子が丸めた値を返さないバグを修正。
  • コンストラクタが呼ばれた後にシンボルを設定するシンボル・セッター・メソッドを追加。
 

こんにちは、nicholishen。あなたのライブラリは以前からテストしていました。素晴らしいライブラリで、価格やロットの丸めを簡単に行うことができます。

しかし、四捨五入メソッドの精度について懸念があります。RoundToStep()、RoundToStepUp()、RoundToStepDown()、RoundToTick()関数で多くの丸めエラーを見つけました。これらのエラーは常に 5 で終わる端の数(1.12345 など)で発生します。

例えば、CDouble::RoundToStep(1.700605, 0.00001) は 正しい結果 1.70061 ではなく 1.70060 を返します。

round(number / point) * point という式は、round(number * power) / power に修正する必要があります。

なぜなら、0.00001であるはずの1ポイントの値は、実際には64ビットの倍精度浮動小数点として0.000010000000000008180305391403130954586231382563710としてエンコードされているからです。このため、四捨五入の最終結果であるround(number / point) * pointは、正しい結果から1ポイント(0.00001)だけずれてしまうことがよくあります。

さらに、適切な'算術'丸め(ゼロから離れる中間点丸め)を行うには、補正として1/2イプシロンを加算または減算するのが良い方法です。(これは、IEEE-754仕様で義務付けられているように、特に中間点のエッジケースで、プロセッサによって適用された半平均丸めを相殺します)。

mqlのNormalizeDouble()関数は これらの問題をすべて正しく処理するので、適切な「算術」丸めを行うにはこれを使うべきである。

以下は私が書いた算術丸めを行う関数のソースコードです。この関数はNormalizeDouble()と全く同じ結果をもたらします。私の関数はさらに高速に実行され、より高いレベルの丸め精度をサポートしています。(MQLのNormalizeDouble()は小数点以下8桁に制限されています)。

/**
 * ゼロから離れる中間点丸め('算術'丸め)
 * 補正に半イプシロンを使用。(これは、エッジケースで適用されていたIEEE-754
 * 半平均丸めを相殺します)。
 */
double RoundCorrect(double num, int precision) {
        double c = 0.5 * DBL_EPSILON * num;
// double p = MathPow(10, precision); //遅い
        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つの関数を比較(2桁に丸める)
                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) );  // 真
        PRINT( NormalizeDouble(numbers_6[0], 4) == RoundCorrect(numbers_6[0], 4) );  // 真
        PRINT( NormalizeDouble(numbers_6[0], 3) == RoundCorrect(numbers_6[0], 3) );  // 真
        PRINT( NormalizeDouble(numbers_6[0], 2) == RoundCorrect(numbers_6[0], 2) );  // 真
        PRINT( NormalizeDouble(numbers_6[0], 1) == RoundCorrect(numbers_6[0], 1) );  // 真

}

 
amrali:

こんにちは、nicholishen。あなたのライブラリは以前からテストしていました。素晴らしいライブラリで、価格やロットの丸めを簡単に行うことができます。

しかし、四捨五入メソッドの精度について懸念があります。RoundToStep()、RoundToStepUp()、RoundToStepDown()、RoundToTick()関数で多くの丸めエラーを見つけました。これらのエラーは、常に 5 で終わる端の数値(1.12345 など)で発生します。

例えば、CDouble::RoundToStep(1.700605, 0.00001) は 正しい結果 1.70061 ではなく 1.70060 を返します。

round(number / point) * point という式は、round(number * power) / power に修正する必要があります。

なぜなら、0.00001であるはずの1ポイントの値は、実際には64ビットの倍精度浮動小数点として0.000010000000000008180305391403130954586231382563710としてエンコードされているからです。このため、四捨五入の最終結果であるround(number / point) * pointは、正しい結果から1ポイント(0.00001)だけずれてしまうことがよくあります。

さらに、適切な'算術'丸め(ゼロから離れる中間点丸め)を行うには、補正として1/2イプシロンを加算または減算するのが良い方法です。(これは、特に中間点のエッジケースで、IEEE-754仕様で義務付けられているように、プロセッサによって適用された半平均丸めを相殺します)。

mqlのNormalizeDouble()関数は これらの問題をすべて正しく処理するので、適切な'算術'丸めを行うにはこれを使うべきである。

以下は私が書いた算術丸めを行う関数のソースコードです。この関数はNormalizeDouble()と全く同じ結果をもたらします。私の関数はさらに高速に実行され、より高いレベルの丸め精度をサポートしています。(MQLのNormalizeDouble()は小数点以下8桁に制限されています)。

ご指摘ありがとうございます。roundの代わりにNormalizeDoubleを使うようにコードを更新します。

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

こんにちは、nicholishen。あなたのライブラリは以前からテストしていました。素晴らしいライブラリで、価格やロットの丸めを簡単に行うことができます。

しかし、四捨五入メソッドの精度について懸念があります。RoundToStep()、RoundToStepUp()、RoundToStepDown()、RoundToTick()関数で多くの丸めエラーを見つけました。これらのエラーは、常に 5 で終わる端の数値(1.12345 など)で発生します。

例えば、CDouble::RoundToStep(1.700605, 0.00001) は 正しい結果 1.70061 ではなく 1.70060 を返します。

round(number / point) * point という式は、round(number * power) / power に修正する必要があります。

なぜなら、0.00001であるはずの1ポイントの値は、実際には64ビットの倍精度浮動小数点として0.000010000000000008180305391403130954586231382563710としてエンコードされているからです。このため、四捨五入の最終結果であるround(number / point) * pointは、正しい結果から1ポイント(0.00001)ずれてしまうことがよくあります。

しかし、取引の観点からは、1.70060か1.70061のどちらかが必要であり、どちらも正しいのです。そのため、数学的な丸め方式に頼るのではなく、取引操作に応じて 最適な方を選択することをお勧めします。