Bibliotecas: Math Utils - página 5

 
FXAI #:
Essas são três funções úteis para comparação e arredondamento de números de ponto flutuante e formatação de dinheiro:

1. `bool DoubleEquals(double x, double y, double eps)` compara dois valores duplos `x` e `y` com um determinado valor de epsilon `eps` e retorna um valor booleano que indica se eles são iguais dentro da tolerância determinada.

2. `double RoundTo(double value, int digits)` arredonda um valor duplo `value` para o número de `dígitos` decimais fornecido.

3. `string FormatMoney(double amount)` formata um valor duplo `amount` como uma string que representa um valor monetário. Ele formata o valor com duas casas decimais, substitui o ponto decimal por uma vírgula e insere espaços a cada três dígitos para facilitar a leitura. Ele também adiciona o símbolo de moeda obtido de `AccountInfoString(ACCOUNT_CURRENCY)` no final.

Muito obrigado por isso. Entretanto, essas funções já estão implementadas na biblioteca (inclusive com resultados mais robustos do que os seus), mas com nomes diferentes.

// Verificar se dois números são iguais até "n" dígitos de precisão.
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)

// arredondamento decimal preciso para evitar resultados inesperados.
double Round(const double v);                       
double Round(const double value, const int digits); 
double Round(const double value, const double step);

// Formata double com separador de milhares e decimais especificados.
string FormatDouble(const double number, const int digits, const string separator=",");

 

Olá @amrali, obrigado por sua contribuição.

Talvez isso seja um bug?

Eu esperava que a segunda impressão fosse "0,0001".

Se for um bug, como corrigi-lo? Caso contrário, o que há de errado em meu código?

Muito obrigado.

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

Print(spread);// Saídas: 0.00009999999999998899
Print(StripError(spread));// Saídas: 0.000099999999999989


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

Olá @amrali, obrigado por sua contribuição.

Talvez isso seja um bug?

Eu esperava que a segunda impressão fosse "0,0001".

Se for um bug, como corrigi-lo? Caso contrário, o que há de errado em meu código?

Muito obrigado.


Imprima as representações hexadecimais e você entenderá que o spread está longe do valor real verdadeiro de 0,0001 (isso se deve a erros de arredondamento durante a subtração).

Portanto, você precisa usar procedimentos de arredondamento.

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

   Print(spread);                                  // Saídas: 0.00009999999999998899
   Print(StripError(spread));                      // Saídas: 0.000099999999999989

   Print(DoubleToHexadecimal(spread));             // Saídas: 0x3F1A36E2EB1C4000
   Print(DoubleToHexadecimal(StripError(spread))); // Saídas: 0x3F1A36E2EB1C4001
   Print(DoubleToHexadecimal(0.0001));             // Saídas: 0x3F1A36E2EB1C432D

   Print(EQ(spread, 0.0001));                      // Saídas: true
   Print(Round(spread, 16));                       // Saídas: 0.001

Há diferenças sutis que você deve observar:

StripError() arredonda para o 16º dígito significativo 0,00009999999999998899 (0's não são contados).

Round(x, 16) arredonda para o 16º dígito após o ponto decimal 0,00009999999999998899

 
jonlinper #: Eu esperava que a segunda impressão fosse "0,0001".

O ponto flutuante tem um número infinito de casas decimais. O problema é que você não entende o que é ponto flutuante e que alguns números não podem ser representados exatamente. (como 1/10.)
Formato de ponto flutuante de precisão dupla - Wikipedia

Veja também o operando ==. - Fórum de programação MQL4 (2013)

Se você quiser ver o número correto de dígitos, converta-o em uma string com a precisão correta/desejada.
question about decima of marketinfo() - MQL4 programming forum (2016)

 
William Roeder #:

O ponto flutuante tem um número infinito de casas decimais. O problema é que você não entende o que é ponto flutuante e que alguns números não podem ser representados exatamente. (como 1/10.)
Formato de ponto flutuante de precisão dupla - Wikipedia

Veja também o operando ==. - Fórum de programação MQL4 (2013)

Se você quiser ver o número correto de dígitos, converta-o em uma string com a precisão correta/desejada.
pergunta sobre decima de marketinfo() - Fórum de programação MQL4 (2016)

Caro William, obrigado pelo esclarecimento, mas não concordo com você no que diz respeito ao "número infinito de casas decimais". Na verdade, os números FP têm um número finito de dígitos decimais. (Por exemplo, 0,1 tem exatamente 52 dígitos após o ponto decimal).

Por favor, use DoubleToStringExact(0.1) da minha biblioteca para imprimir todos eles. Além disso, você pode verificar a cadeia decimal completa usando esta calculadora aqui: https://www.exploringbinary.com/floating-point-converter/
Observe também que a cadeia decimal completa deve sempre terminar com o dígito "5".

0.1000000000000000055511151231257827021181583404541015625
 

Qual é a maneira mais otimizada de imprimir apenas dígitos significativos com duplas.

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

Essa função funciona muito bem para 99,9% dos números, mas tem problemas com números redondos como 1.0000000000

Meu problema é que preciso remover os dígitos não significativos e, por algum motivo, não consigo fazer isso usando apenas @Trunc,

então acabei usando algo como:

string Normalize_Double_ToString(double n, int d)
{
   // Etapa 1 - ajuda a excluir os zeros finais
   n = Round(n, d);

   // Etapa 2 - contar o número de casas decimais significativas
   int sd = GetSignificantDecimals(n);

   // Etapa 3 - não queremos mais do que o especificado em @d
   if (sd > d){ sd = d; }

   // Etapa 4 - remover decimais indesejados sem o arredondamento aleatório negativo 
   double t = Trunc(n, sd);

   // Depurar
   //PrintFormat("%s [%d] [%d] :: %s", DoubleToString(n, DBL_DIG), d, sd, DoubleToString(t, sd));

   // Etapa 5 - definir a precisão
   string s = DoubleToString(t, sd);

   return s;
}

Ele funciona exatamente como necessário, fornecendo a menor cadeia de caracteres possível para todos os números, mas gostaria de saber se ele pode ser otimizado e ainda obter a menor cadeia de caracteres com números redondos como 1.00000000

Obrigado pela atenção

 

Acabei de perceber que estou usando

int GetSignificantDecimals(double value)

Uma versão ligeiramente modificada do seu

int GetSignificantDigits(double value)

e este é o código

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

   // soma de decimais
   int digits = GetDigits(value);

   // excluindo os zeros finais
   while(MathMod(value, 10) == 0)
   {
      digits--;
   }

   return digits;
}
 
Cristian Dan Fechete números redondos como 1.0000000000

Meu problema é que preciso remover os dígitos não significativos e, por algum motivo, não consigo fazer isso usando apenas @Trunc,

então acabei usando algo como:

Ele funciona exatamente como necessário, fornecendo a menor cadeia de caracteres possível para todos os números, mas gostaria de saber se ele pode ser otimizado e ainda obter a menor cadeia de caracteres com números redondos como 1,00000000

Obrigado pela atenção

Desculpe, mas você precisa entender o que são dígitos significativos, pois acho que seu código está confundindo os conceitos básicos.
O que está tentando fazer? Explique em palavras simples, sem código. Dê um exemplo do que você acha que está errado e quais são suas expectativas.
 
amrali #:
Desculpe, mas você precisa entender o que são dígitos significativos, pois acho que seu código está confundindo os conceitos básicos.
O que você está tentando fazer? Explique em palavras simples, sem código. Dê um exemplo do que você acha que está errado e quais são suas expectativas.

Obrigado pelo seu tempo e, sim, não tenho certeza se entendi "dígitos significativos

Basicamente, preciso "imprimir" o menor número possível. Por exemplo:

1.0000000 -> 1

1.0090000 -> 1.009

123.00100 -> 123.001

Para mim, "dígitos significativos" significa: dígito que altera o valor de um número se for removido, portanto, os zeros à direita não são significativos.


A propósito, desde a última atualização do Windows, a função Round(double, int) está causando o bloqueio do MT4. O primeiro código que postei estava funcionando perfeitamente e, desde ontem à noite, ele congela completamente o cliente MT4.

 
Cristian Dan Fechete #:

Obrigado pelo seu tempo e, sim, não tenho certeza se entendi bem o que são "dígitos significativos

Basicamente, preciso "imprimir" o menor número possível. Por exemplo:

1.0000000 -> 1

1.0090000 -> 1.009

123.00100 -> 123.001

Para mim, "dígitos significativos" significa: dígito que altera o valor de um número se for removido, portanto, zeros à direita não são significativos.


A propósito, desde a última atualização do Windows, a função Round(double, int) está causando o bloqueio do MT4. O primeiro código que postei estava funcionando perfeitamente e, desde ontem à noite, ele congela completamente o cliente MT4.

A função Print() ou a conversão de double em string como (string)dbl obterá o menor número possível de dígitos significativos, sem necessidade de manipular o número primeiro. Esse é um recurso incorporado no MQL. (Já propus uma correção para a equipe de desenvolvedores, e ela foi incorporada ao código).

Tudo o que você precisa fazer é:
string num_str = string(number).
ou, Print(number);

É por isso que não é necessária uma função dedicada para imprimir ou formatar números na menor string possível dentro da biblioteca, pois a funcionalidade já é compatível com a linguagem MQL.

Use DoubleToString() somente se precisar controlar o número de dígitos após o ponto decimal. Se o parâmetro digits for maior que os dígitos decimais em seu número, os 0s serão anexados à string retornada, por exemplo
DoubleToString(1.09, 5) retorna a cadeia de caracteres "1.09000".

Se o parâmetro digits for menor que os dígitos decimais do número, o número será aproximado, como DoubleToString(1.12345, 2) que retorna a string "1.12". Sua confusão decorre da incapacidade de diferenciar números de cadeias de caracteres.

Observe que os números que terminam em zeros, como 1,09, 1,090, 1,0900, 1,09000 e 1,090000, são armazenados como o mesmo número fp de precisão dupla em variáveis. Isso só pode ser obtido por meio da entrada direta do usuário como entradas manuais. Dentro do programa, todos esses números são armazenados como o mesmo número, que é 1,09, e os 0s finais não são armazenados.

double a = 1,09;
double b = 1,090000;
Print(a); // "1.09"
Print(b); // "1.09"

Funções de arredondamento como round, ceil e floor alteram (aproximam) o número de entrada para outro número que é o número duplo mais próximo com a contagem especificada de dígitos decimais após o ponto decimal ou o número que contém o número total especificado de dígitos significativos no caso de RoundToSignificantDigits().

Espero que isso tenha esclarecido a confusão sobre a conversão de números fp de precisão dupla em string.