Особенности языка mql5, тонкости и приёмы работы - страница 301

 
amrali #:

BTW, вот более точный способ сравнить двойные числа a и b (т.е. вычисленные значения, а не константы) на равенство:

Так как это сравнение может изящно обрабатывать очень большие или очень маленькие числа.


Да, этот способ наиболее корректен. 
 
d1 или d2 нормализовано?
void OnStart()
{
  double d1 = (string)"1.0054400000000001";
  double d2 = (string)"1.00544";
  
  Print(d1 == NormalizeDouble(d1, 8)); // true
  Print(d2 == NormalizeDouble(d2, 8)); // false
  Print(d1 == d2 + DBL_EPSILON);       // true
}
 
fxsaber #:
Нормализуется ли d1 или d2?

В функции NormalizeDouble() была обнаружена небольшая ошибка, о которой я сообщал ранее, когда на выходе функции часто появляются небольшие ошибки округления (= 1 ULP; единица на последнем месте).

Если вам нужно "очень" точное округление до наименьшего значащего бита (ULP), пожалуйста, используйте мою библиотечную функцию Round(num, digits) из"math_utils.mqh".

#include "math_utils.mqh"  // https://www.mql5.com/ru/code/20822

void OnStart()
{
  double d1 = (string)"1.0054400000000001";
  double d2 = (string)"1.00544";

  Print(d1);                       // 1.0054400000000001
  Print(d2);                       // 1.00544

  // ошибки округления при использовании функции NormalizeDouble()
  Print(NormalizeDouble(d1, 8));   // 1.0054400000000001
  Print(NormalizeDouble(d2, 8));   // 1.0054400000000001

  // точное округление (math_utils.mqh)
  Print(Round(d1, 8));             // 1.00544
  Print(Round(d2, 8));             // 1.00544
}

Редактировать:

"Точная" функция Round может быть реализована как:

//+------------------------------------------------------------------+
//| Округляем до заданного количества десятичных цифр (без округления)|.
//+------------------------------------------------------------------+
double Round(const double value, const int digits)
  {
   double pwr10 = MathPow(10, digits);
   return MathRound((value * pwr10) * (1 + DBL_EPSILON)) / pwr10;
  }

math_utils.mqh содержит довольно оптимизированную версию Round() для скорости.

BTW, в старых версиях MT5 функция Print() имела ошибки при печати двойных значений, поэтому было очень сложно определить, где именно происходят ошибки округления fp.

Теперь предполагается, что функция Print() корректна в MT5 (я не тестировал Print() на MT4).

Edit2:

Еще одна проблема в вашем коде - это точное сравнение дублей (вычисленных значений).

  Print(d2 == NormalizeDouble(d2, 8)); // false

Вы должны использовать свободное (более толерантное) сравнение с использованием абсолютной разницы , как и раньше

  double a = (string)"1.00544";
  double b = NormalizeDouble(a, 8);

  // проверьте, равны ли a, b.
  Print( MathAbs(a - b) < DBL_EPSILON * MathMax(MathAbs(a), MathAbs(b)) );  // true
MT5/mql5 reported and confirmed bugs. - How to fix a problem with the format of StringToDouble
MT5/mql5 reported and confirmed bugs. - How to fix a problem with the format of StringToDouble
  • 2022.02.13
  • Alain Verleyen
  • www.mql5.com
Why are you thinking the problem is normalizedouble (it could be but we have no idea about it) when stringtodouble give 1. To check if conversion string -> double is correct, we can examine it here  https://baseconvert
 
amrali #:

Если вам нужно "очень" точное округление до наименьшего значащего бита (ULP), пожалуйста, используйте мою библиотечную функцию Round(num, digits) из"math_utils.mqh".

Мне нужна быстрая распаковка сжатого MqlTick. Этот вариант, к сожалению, медленный.

 
fxsaber #:

Мне нужна быстрая распаковка сжатого MqlTick. Этот вариант, к сожалению, медленный.

Во-первых, вы должны знать, что при переходе dbl -> float (упаковка) и затем обратно из float -> double (распаковка) может быть потеряна точность.

Например, если вы упакуете цену 1.1235, то при распаковке результат может быть 1.1234444.

Смотрите мой пример здесь https://www.mql5.com/en/forum/349798/page2#comment_27766871.

Бенч как NormalizeDouble(), так и оптимизированный Round(). Я думаю, что моя функция достаточно быстрая.

Также деление на / 1000 может быть медленным. Попробуйте более быстрое (3x) умножение * 0.001, если это имеет значение.
 
amrali #:
Такая задача.
void OnStart()
{
  MqlTick NormTicks[]; // bid/ask - NormalizeDouble: CopyTicks.
  
  T CompressTicks[];
  Compress(NormTicks, CompressTicks) // Any performance
    
  MqlTick Ticks[];
  Decompress(CompressTicks, Ticks); // need it faster
  
  Print(sizeof(MqlTick) / sizeof(T)) // need more
  
  bool Res = true;
  
  for (uint i = ArraySize(Ticks); Res && (bool)i--;)
    Res = (Ticks[i].bid == NormTicks[i].bid) && (Ticks[i].ask == NormTicks[i].ask);
    
  Print(Res); // true
}
 

fxsaber #:
MqlTick

Print(sizeof(MqlTick) / sizeof(T)) // need more

Очень неразумно для упаковки данных создавать структуру T, когда каждому неупакованному элементу массива MqlTick будет соответствовать один упакованный элемент массива структуры T.
Упакованные данные - это для максимальной плотности и производительности должен быть набор нескольких массивов данных(причем в идеале битовых) и набор нескольких индексных массивов (тоже битовых). Упаковка, как и распаковка - это непрерывный последовательный рекуррентный процесс от нулевого до последнего индекса.
По идее в среднем на одну 60 байтную структуру MqlTick 4 байтов будет достаточно. Т.е. упаковка в 15 раз. Это подтверждается размером файлов  tkc. И похоже это предел. 
Очень странно почему данные  M1 MqlRates (файлы hcc) до сих пор не упакованы!!! Сомневаюсь, чтобы загрузка и распаковка упакованного файла с диска SSD была бы дольше, чем загрузка неупакованного файла, размер которого скажем в 5-6 раз больше упакованного. 

 
Nikolai Semko #:

каждому неупакованному элементу массива MqlTick будет соответствовать один упакованный элемент массива структуры T.

Следующее условие не является обязательным.
ArraySize(CompressTicks) == ArraySize(NormTicks)

Т.е. упаковывать можно разом не один MqlTick-элемент массива, а сразу несколько.


Т.е. в общем виде такое условие.

Print(ArraySize(NormTicks) * sizeof(MqlTick) / (ArraySize(CompressTicks) * sizeof(T))) // need more

Но при такой нелаконичной исходной формулировке сразу отпадают люди из-за долгого понимания.

 
fxsaber #:

Мне нужна быстрая распаковка сжатого MqlTick. Этот вариант, к сожалению, медленный.

Попробуйте округлить до 5 цифр вместо 8, чтобы избежать крошечных ошибок округления:

    static  void decompress( const MqlTickBidAsk &tick, MqlTick &result)
    {
// result.bid = tick.bid; 
// result.ask = tick.ask; 
      result.bid = :: NormalizeDouble (tick.bid, 5 );
      result.ask = :: NormalizeDouble (tick.ask, 5 ); 
      result.last = 0 ;
      result.time_msc = tick.time_msc;
      result.time = ( datetime )( tick.time_msc / 1000 );
      result.flags = tick.flags;
      result.volume = 0 ;
      result.volume_real = 0 ;
    }
 
amrali #:

Попробуйте округлить до 5 цифр вместо 8, чтобы избежать крошечных ошибок округления:

Да, в том контексте цифра имеет значение.