コンテンツにスキップ

【検証#01】double型の比較ミス、知らないと致命傷

DoubleComparisonTestの実行結果

DoubleComparisonTestの実行結果

この記事の3行まとめ

  • 🔢 double型同士の == 比較は信用できない
  • NormalizeDouble または差分の絶対値で比較すべし
  • 💡 FX価格計算で必須の基礎知識

はじめに

この記事は、「とあるMetaTraderの備忘秘録」様のブログ記事を検証・紹介するシリーズの第1弾です。

オリジナル記事の知見に敬意を表しつつ、MQL5での動作確認と追加解説を行います。

時代背景に関する注意

この記事の元となるブログ記事は2009年頃に公開されたものです。
現在のMQL5/MT5では仕様が変更・改善されている可能性があります。

元ネタ

MQL4のdouble型の比較に注意する話
https://fai-fx.hatenadiary.org/entry/20090718/1247924725
(2009年7月18日 公開)


何が問題なのか?

プログラミングを学んだ方なら、こんなコードを書いたことがあるかもしれません。

MQL
double price = 1.2690 + 0.0003;  // 計算で1.2693を得る
double target = 1.2693;           // リテラルで指定

if(price == target)
{
    Print("一致!");  // ← これが実行されない!?
}

数学的には 1.2690 + 0.0003 = 1.2693 ですよね?

でもコンピュータの世界では、これが一致しないことがあります。


なぜ一致しないのか

用語解説:IEEE 754(浮動小数点数)

コンピュータが小数を表現する国際標準規格。
10進数を2進数に変換するため、多くの小数は完全には表現できず、微小な誤差が生じる。
例:0.1 は2進数では無限小数になる。

double型は64ビットの浮動小数点数です。内部的には2進数で表現されるため、10進数の計算結果と微妙にずれることがあります。

実験:内部表現を確認してみよう

MQL
void OnStart()
{
    double price = 1.2690 + 0.0003;
    double target = 1.2693;

    // 表示上は同じ
    Print("price  = ", price);    // 1.2693
    Print("target = ", target);   // 1.2693

    // 内部的な差を確認
    Print("差 = ", DoubleToString(price - target, 20));
    // → 0.00000000000000022204... などの微小な値が出る可能性

    // 比較結果
    if(price == target)
        Print("一致");
    else
        Print("不一致!");  // こちらが実行される可能性あり
}

正しい比較方法

方法1:NormalizeDouble を使う

用語解説:NormalizeDouble

浮動小数点数を指定した桁数に丸める MQL 関数。
価格の比較では、通貨ペアの小数点桁数(Digits())に合わせて丸める。

MQL
// 推奨パターン
if(NormalizeDouble(price - target, _Digits) == 0.0)
{
    Print("一致!");
}

方法2:差の絶対値で判定

MQL
// より汎用的なパターン
double epsilon = _Point;  // または任意の許容誤差

if(MathAbs(price - target) < epsilon)
{
    Print("一致!");
}

どちらを使うべき?

シーン 推奨方法
価格の比較(指値、逆指値) NormalizeDouble(a - b, _Digits) == 0
インジケータ値の比較 MathAbs(a - b) < epsilon
厳密なゼロ判定 NormalizeDouble(a, 8) == 0

MQL5での完全動作コード

以下はスクリプトとして動作確認できるコードです。

MQL
//+------------------------------------------------------------------+
//|                                    DoubleComparisonTest.mq5      |
//| 「とあるMetaTraderの備忘秘録」様の記事を検証                      |
//| https://fai-fx.hatenadiary.org/entry/20090718/1247924725         |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property link      "https://fx-omoshiro-lab.com/"
#property version   "1.00"
#property script_show_inputs

//+------------------------------------------------------------------+
//| Script program start function                                     |
//+------------------------------------------------------------------+
void OnStart()
{
    Print("=== double型比較テスト ===");

    // テストケース1: 計算結果 vs リテラル
    double calculated = 1.2690 + 0.0003;
    double literal = 1.2693;

    Print("--- ケース1: 計算結果 vs リテラル ---");
    Print("calculated = ", DoubleToString(calculated, 10));
    Print("literal    = ", DoubleToString(literal, 10));
    Print("差         = ", DoubleToString(calculated - literal, 20));

    // 危険な比較
    if(calculated == literal)
        Print("== 比較: 一致");
    else
        Print("== 比較: 不一致 ← 危険なパターン!");

    // 正しい比較1: NormalizeDouble
    if(NormalizeDouble(calculated - literal, 5) == 0.0)
        Print("NormalizeDouble: 一致 ✓");
    else
        Print("NormalizeDouble: 不一致");

    // 正しい比較2: MathAbs
    double epsilon = 0.00001;  // 5桁精度
    if(MathAbs(calculated - literal) < epsilon)
        Print("MathAbs: 一致 ✓");
    else
        Print("MathAbs: 不一致");

    Print("");

    // テストケース2: 実際の価格計算(より現実的)
    Print("--- ケース2: 実際の価格計算 ---");
    double entryPrice = 145.500;
    double slDistance = 0.500;
    double slPrice = entryPrice - slDistance;
    double expectedSL = 145.000;

    Print("SL価格     = ", DoubleToString(slPrice, 5));
    Print("期待値     = ", DoubleToString(expectedSL, 5));

    if(NormalizeDouble(slPrice - expectedSL, 3) == 0.0)
        Print("SL判定: 正しく一致 ✓");
    else
        Print("SL判定: 不一致");

    Print("=== テスト完了 ===");
}

実務での応用例

指値注文のヒット判定

MQL
// 悪い例 ❌
if(Bid == targetPrice)  // 完全一致を求めている
{
    // 執行処理
}

// 良い例 ✓
if(Bid >= targetPrice)  // 不等号で判定
{
    // 執行処理
}

// より厳密な例 ✓
if(NormalizeDouble(Bid - targetPrice, _Digits) >= 0)
{
    // 執行処理
}

トレーリングストップ

MQL
// 新しいSLが現在のSLより良いか判定
double newSL = Bid - trailDistance * _Point;
double currentSL = PositionGetDouble(POSITION_SL);

// 悪い例 ❌
if(newSL > currentSL)  // double同士の不等号は基本的にOKだが...

// より安全な例 ✓
if(NormalizeDouble(newSL - currentSL, _Digits) > 0)
{
    // SL更新処理
}

まとめ

ポイント 内容
問題 double型== 比較は誤差で失敗する
原因 IEEE 754による2進数表現の限界
解決策 NormalizeDouble または MathAbs で比較
重要度 極めて高い(指値・逆指値のバグに直結)

オリジナル記事への謝辞

この記事は「とあるMetaTraderの備忘秘録」様の貴重な知見をもとに、
MQL5での検証と解説を加えたものです。
オリジナル記事に心より感謝いたします。


ソースコードのダウンロード

この記事で紹介したコードをダウンロードできます。

ファイルの種類:スクリプト

保存先: MQL5/Scripts/ フォルダ
使い方: MT5のナビゲーターから「スクリプト」を展開し、チャートにドラッグ&ドロップで実行

01_DoubleComparison.mq5 をダウンロード


関連用語

この記事で登場した用語は用語集でも解説しています。