Библиотеки: Math Utils - страница 5

 
FXAI #:
Это три удобные функции для сравнения и округления чисел с плавающей точкой и форматирования денег:

1. `bool DoubleEquals(double x, double y, double eps)` сравнивает два двойных значения `x` и `y` с заданным значением эпсилон `eps` и возвращает булево значение, указывающее, равны ли они в пределах заданного допуска.

2. `double RoundTo(double value, int digits)` округляет двойное значение `value` до заданного количества десятичных `цифр`.

3. `string FormatMoney(double amount)` форматирует двойное значение `сумма` как строку, представляющую сумму в валюте. Она форматирует сумму с двумя десятичными знаками, заменяет десятичную точку запятой и вставляет пробелы через каждые три цифры для удобочитаемости. Также в конце добавляется символ валюты, полученный из `AccountInfoString(ACCOUNT_CURRENCY)`.

Большое спасибо за это. Однако эти функции уже реализованы в библиотеке (даже с более надежными результатами, чем у вас), но с другими именами.

// Проверьте, равны ли два числа с точностью до "n" цифр.
int    Compare(const double a, const double b, const int digits);
bool   EqualDoubles(double a, double b, int significantDigits = 15);
bool   IsClose(const double a, const double b, const int maxDifferentSignificantDigits = 2)

// точное округление десятичных дробей во избежание неожиданных результатов.
double Round(const double v);                       
double Round(const double value, const int digits); 
double Round(const double value, const double step);

// Формирует двойное число с разделителем тысяч и указанными десятичными знаками.
string FormatDouble(const double number, const int digits, const string separator=",");

 

Здравствуйте @amrali, спасибо за ваш вклад.

Может быть, это ошибка?

Я ожидал, что второй отпечаток будет "0.0001".

Если это ошибка, как ее исправить? Если нет, то что не так в моем коде?

Спасибо.

double ask = 1.2973;
double bid = 1.2972;
double spread = ask - bid;

Print(spread);// Выходные данные: 0.00009999999999998899
Print(StripError(spread));// Выходные данные: 0.000099999999999989


amrali
amrali
  • 2024.04.05
  • www.mql5.com
Trader's profile
 
jonlinper #:

Здравствуйте, @amrali, спасибо за ваш вклад.

Может быть, это ошибка?

Я ожидал, что второй отпечаток будет "0.0001".

Если это ошибка, как ее исправить? Если нет, то что не так в моем коде?

Спасибо.


Выведите шестнадцатеричные представления и вы поймете, что разброс далек от истинного реального значения 0.0001 (это из-за ошибок округления при вычитании).

Поэтому необходимо использовать процедуры округления.

   double ask = 1.2973;
   double bid = 1.2972;
   double spread = ask - bid;

   Print(spread);                                  // Выходные данные: 0.00009999999999998899
   Print(StripError(spread));                      // Выходные данные: 0.000099999999999989

   Print(DoubleToHexadecimal(spread));             // Выходы: 0x3F1A36E2EB1C4000
   Print(DoubleToHexadecimal(StripError(spread))); // Выходы: 0x3F1A36E2EB1C4001
   Print(DoubleToHexadecimal(0.0001));             // Выходы: 0x3F1A36E2EB1C432D

   Print(EQ(spread, 0.0001));                      // Выходные данные: true
   Print(Round(spread, 16));                       // Выходы: 0.001

Здесь есть небольшие различия, которые вы должны заметить:

StripError() округляет до 16-й значащей цифры 0.00009999999999998899 (0 не учитываются).

Round(x, 16) округляет до 16-й цифры после десятичной точки 0.00009999999999998899.

 
jonlinper #: Я ожидал, что второй вывод будет "0.0001".

Плавающая точка имеет бесконечное число десятичных дробей. Это вы не понимаете, что такое плавающая точка и что некоторые числа не могут быть представлены точно. (например, 1/10.)
Формат с плавающей точкой двойной точности - Википедия.

См. также Операнд ==. - Форум по программированию на MQL4 (2013)

Если вы хотите видеть правильное количество цифр, преобразуйте его в строку с нужной/желаемой точностью.
Вопрос про дециму в marketinfo() - Форум по программированию на MQL4 (2016)

 
William Roeder #:

Плавающая точка имеет бесконечное число десятичных дробей. Это вы не понимаете, что такое плавающая точка и что некоторые числа не могут быть представлены точно. (например, 1/10.)
Формат двойной точности с плавающей точкой - Википедия

См. также Операнд ==. - Форум по программированию на MQL4 (2013)

Если вы хотите видеть правильное количество цифр, преобразуйте его в строку с нужной/желаемой точностью.
Вопрос про дециму в marketinfo() - Форум по программированию на MQL4 (2016)

Уважаемый Уильям, спасибо за разъяснение, однако я не согласен с вами по поводу "бесконечного количества десятичных знаков". Числа FP на самом деле имеют конечное число десятичных цифр. (Например, 0.1 имеет ровно 52 цифры после запятой).

Используйте DoubleToStringExact(0.1) из моей библиотеки, чтобы вывести их все. Также вы можете проверить полную десятичную строку с помощью этого калькулятора здесь: https://www.exploringbinary.com/floating-point-converter/.
Также обратите внимание, что полная десятичная строка всегда должна заканчиваться цифрой "5".

0.1000000000000000055511151231257827021181583404541015625
 

Каков наиболее оптимизированный способ печати только значащих цифр в удвоенных числах.

double Trunc(const double value, const int digits);

Эта функция отлично работает для 99,9% чисел, но у нее есть проблемы с круглыми числами, такими как 1.0000000000.

Моя проблема заключается в том, что мне нужно удалить незначащие цифры, и по какой-то причине я не могу сделать это, используя только @Trunc,

поэтому я решил использовать что-то вроде этого:

string Normalize_Double_ToString(double n, int d)
{
   // Шаг 1 - это поможет исключить нули в конце страницы
   n = Round(n, d);

   // Шаг 2 - подсчет количества значащих десятичных дробей
   int sd = GetSignificantDecimals(n);

   // Шаг 3 - нам не нужно больше, чем указано в @d
   if (sd > d){ sd = d; }

   // Шаг 4 - удаление ненужных десятичных дробей без отрицательного случайного округления 
   double t = Trunc(n, sd);

   // Отладка
   //PrintFormat("%s [%d] [%d] :: %s", DoubleToString(n, DBL_DIG), d, sd, DoubleToString(t, sd));

   // Шаг 5 - установка точности
   string s = DoubleToString(t, sd);

   return s;
}

Это работает точно так, как нужно, давая мне наименьшую возможную строку для всех чисел, но мне интересно, можно ли это оптимизировать и по-прежнему получать наименьшую строку с круглыми числами, такими как 1.00000000.

Спасибо

 

Только что понял, что я использую

int GetSignificantDecimals(double value)

Слегка модифицированная версия вашего

int GetSignificantDigits(double value)

и вот код

int GetSignificantDecimals(double value)
{
   if(!value || !MathIsValidNumber(value))
   {
      return 0;
   }

   // сумма десятичных дробей
   int digits = GetDigits(value);

   // без учета нулей в конце
   while(MathMod(value, 10) == 0)
   {
      digits--;
   }

   return digits;
}
 
Cristian Dan Fechete круглыми числами, например 1.0000000000.

Моя проблема заключается в том, что мне нужно удалить незначащие цифры, и по какой-то причине я не могу сделать это, используя только @Trunc,

поэтому в итоге я использую что-то вроде:

Это работает точно так, как нужно, давая мне наименьшую возможную строку для всех чисел, но мне интересно, можно ли это оптимизировать и по-прежнему получать наименьшую строку с круглыми числами, такими как 1.00000000

Спасибо

Извините, вам нужно понять, что такое значащие цифры, так как я нахожу, что ваш код путает основные понятия.
Что вы пытаетесь сделать... пожалуйста, объясните простыми словами, без кода. Приведите пример того, что вы считаете неправильным и каковы ваши ожидания.
 
amrali #:
Извините, вам нужно понять, что такое значащие цифры, так как я нахожу, что ваш код путает основные понятия.
Что вы пытаетесь сделать... Пожалуйста, объясните простыми словами, без кода. Приведите пример того, что вы считаете неправильным, и каковы ваши ожидания.

Спасибо, что уделили время, и да, я не совсем уверен, что понимаю "значащие цифры".

В основном мне нужно "напечатать" самое короткое возможное число. Например:

1.0000000 -> 1

1.0090000 -> 1.009

123.00100 -> 123.001

Для меня "значимые цифры" означают: цифра, которая изменяет значение числа, если ее удалить, поэтому нули в конце числа не являются значимыми.


Кстати, после последнего обновления Windows функция Round(double, int) приводит к блокировке MT4. Первый код, который я выложил, работал идеально, а со вчерашнего вечера он полностью замораживает клиент MT4.

 
Cristian Dan Fechete #:

Спасибо, что уделили время, и да, я не совсем уверен, что понимаю "значащие цифры".

В основном мне нужно "напечатать" самое короткое возможное число. Например:

1.0000000 -> 1

1.0090000 -> 1.009

123.00100 -> 123.001

Для меня "значимые цифры" означают: цифра, которая изменяет значение числа, если ее убрать, поэтому нули в конце числа не являются значимыми.


Кстати, после последнего обновления Windows функция Round(double, int) приводит к блокировке MT4. Первый код, который я выложил, работал идеально, а со вчерашнего вечера он полностью замораживает клиент MT4.

Функция Print() или приведение double к строке, например (string)dbl, позволит получить кратчайшее возможное количество значащих цифр, без необходимости сначала манипулировать числом. Это встроенная функция в MQL. (Я уже предлагал исправление команде разработчиков, и оно было включено в код).

Все, что вам нужно сделать, это:
string num_str = string(number).
или Print(number);

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

Используйте DoubleToString() только в том случае, если вам нужно контролировать количество цифр после запятой. Если параметр digits больше, чем десятичных цифр в вашем числе, к возвращаемой строке будут добавлены 0, например
DoubleToString(1.09, 5) возвращает строку "1.09000".

Если параметр digits меньше десятичных цифр числа, то число будет приближено, например DoubleToString(1.12345, 2) вернет строку "1.12". Путаница возникает из-за невозможности отличить числа от строк.

Обратите внимание, что числа, оканчивающиеся нулями, например 1.09, 1.090, 1.0900, 1.09000 и 1.090000, хранятся в переменных как одно и то же число двойной точности fp. Это возможно только при прямом вводе пользователем в качестве ручного ввода. Внутри программы все эти числа хранятся как одно и то же число, равное 1.09, при этом 0 в конце строки не сохраняется.

double a = 1.09;
double b = 1.090000;
Print(a); // "1.09"
Print(b); // "1.09"

Функции округления, такие как round, ceil и floor, изменяют (аппроксимируют) входное число в другое число, которое является ближайшим двойным числом с указанным количеством десятичных цифр после запятой, или числом, содержащим указанное общее количество значащих цифр в случае RoundToSignificantDigits().

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