воскресенье, 13 мая 2012 г.

Мини-исследование: NaN и INF в MQL4

Документация MQL4 говорит, что некоторые функции возвращают неопределенные значения или бесконечность. Например, функция MathLog(0) вернет бесконечность (INF), а MathLog(-1) - неопределенность (NaN).

Если мы всегда проверяем аргументы функций перед их вычислением, то такие числа нам никогда не встретятся. Но что если нам понадобится сравнить результат операции с этими значениями? Вот здесь и появляется проблема.

Проблема в том, что с точки зрения MQL4 все эти переменные равны. То есть неопределенность с любым знаком, а она может быть с любым знаком в MQL4, равна бесконечности, также с любым знаком.

NaN == -NaN == INF == -INF

Но есть способ определить конкретное значение. Нужно преобразовать значение в строку (с точностью 5 знаков). Все четыре значения имеют различное строковое представление.

Числовое значение Строковое значение Получено с помощью кода
+NaN 1.#QNAN
DoubleToStr(-MathLog(-1), 5)
-NaN -1.#IND0
DoubleToStr(MathLog(-1), 5)
+INF 1.#INF0
DoubleToStr(-MathLog(0), 5)
-INF -1.#INF0
DoubleToStr(MathLog(0), 5)

Лишний ноль в конце появляется за счет округления до пяти знаков. Если округлять всё до четырех знаков, +NaN будет представлено строкой "1.#QNB", что является некоторой внутренней особенностью округления в MQL4, которая может поменяться в любой момент. Надежнее всё округлить до пяти знаков. Еще надежнее при работе с подобными неопределенностями использовать TDD, но это совсем другая тема.

Теперь можно сравнивать полученный результат с неопределенными значениями. Преобразование в строки и их сравнение - медленные операции, это нужно учитывать при проектировании алгоритмов, использующих такие проверки.

Неопределенность не имеет смысла разделять на знаки, поэтому можно объединить проверку на неё в одну функцию или выражение.

// Проверить, является ли x неопределенным (NaN или -NaN)
bool IsNaN(double x)
{
  string sx = DoubleToStr(x, 5);
  return(sx == "1.#QNAN" || sx == "-1.#IND0");
}

Можно реализовать и более быстрый вариант за счет относительной дешевизны математических операций и проверки только одного уникального для всех строковых представлений неопределенности и бесконечности символа.

// Проверить, является ли x неопределенным (NaN или -NaN), быстрый вариант
bool IsNaN(double x)
{
  return(StringGetChar(DoubleToStr(MathAbs(x), 5), 5) == 'A'); // "1.#QNAN"?
}

С проверкой на бесконечность получается не так однозначно, так как могут понадобиться отдельно проверки на плюс бесконечность, минус бесконечность и бесконечность по модулю.

Есть пара вариантов:
  • реализовать общую функцию для проверки на бесконечность по модулю и по отдельной функции для каждого знака
  • реализовать функцию для проверки только на плюс бесконечность и передавать ей значение с отрицанием для проверки на минус бесконечность или значение по модулю для проверки на бесконечность по модулю (функция MathAbs() работает с бесконечностью)
Выбор зависит от личных предпочтений, стандартов.

// Проверить, является ли x +бесконечностью (+INF)
bool IsINF(double x)
{
  return(DoubleToStr(x, 4) == "1.#INF");
}

При работе со стандартными функциями, возвращающими неопределенность или бесконечность, нужно перепроверять то, что написано в справке. Вопреки документации некоторые функции возвращают вместо NaN ноль, но пишут ошибку в журнал, например MathSqrt(-1) и MathArcsin(2).

Исследование выросло из простого желания возвращать такие значения из своих функций, что реализуется теперь довольно просто.

Пример такой функции:

// Гиперболический арктангенс
double Artanh(double x)
{
    if (MathAbs(x) < 1.0)
        return(0.5 * MathLog((1.0 + x) / (1.0 - x)));
    else
        return(-MathLog(-1)); // NaN
}

Подобную функцию можно было бы реализовать без проверки входного параметра, так как в хорошем коде это почти всегда делается заранее. Если не делать проверку, можно получить вместо неопределенного результата какой-то определенный, что исказит результаты вычислений и затруднит отладку. Например, если в функцию определения гиперболического арккосинуса MathLog(x + MathSqrt(x * x - 1.0)) передать значение 0.5, для которого он не определен, функция вернет определенное значение ln(0.5), так как MathSqrt не останавливает программу в случае отрицательного входного параметра, просто возвращая ноль. Хорошая функция должна учесть использование ее в любом сценарии, поэтому проверку входного параметра лучше сделать.

Всё описанное относится к MetaTrader 4.00.409 и документации к нему на момент написания этого текста. Возможно, что-то работало раньше по-другому, а что-то исправят в следующих версиях.

MQL5

Для MQL5 справка описывает все намного более подробно, смотрите раздел Справочник MQL5 / Основы языка / Типы данных / Вещественные типы (double, float). Исправлено поведение некоторых функций, например MathSqrt(-1) действительно вернет NaN, но теперь функция DoubleToString() работает по другому алгоритму, возвращая вместо текстового представления NaN (см. раздел справки по ссылке выше) строковое представление его числового значения при том, что для бесконечностей функция работает так же, как и в MQL4. Нужно проверять каждый аргумент и справку по каждой функции.

Дополнение про MQL5 относится к MetaTrader 5.00.642 и документации к нему на 17.05.2012 г.

Комментариев нет:

Отправить комментарий