Librerías: Math Utils - página 5

 
FXAI #:
Se trata de tres prácticas funciones para comparar y redondear números en coma flotante y dar formato al dinero:

1. `bool DoubleEquals(double x, double y, double eps)` compara dos valores dobles `x` e `y` con un valor épsilon dado `eps` y devuelve un valor booleano que indica si son iguales dentro de la tolerancia dada.

2. `double RoundTo(double value, int digits)` redondea un valor doble `value` al número dado de `digits` decimales.

3. `string FormatMoney(double amount)` formatea un valor doble `amount` como una cadena que representa un importe monetario. Formatea el importe con dos decimales, sustituye el punto decimal por una coma e inserta espacios cada tres dígitos para facilitar la lectura. También añade el símbolo de moneda obtenido de `AccountInfoString(ACCOUNT_CURRENCY)` al final.

Muchas gracias por ello. Sin embargo, estas funciones ya están implementadas en la librería (incluso con resultados más robustos que los tuyos), pero con nombres diferentes.

// Comprueba si dos números son iguales hasta "n" dígitos de precisió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)

// redondeo 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);

// Formatea doble con separador de miles y decimales especificados.
string FormatDouble(const double number, const int digits, const string separator=",");

 

Hola @amrali, gracias por tu aportación.

¿Quizás se trate de un bug?

Esperaba que la segunda impresión fuera "0.0001".

Si es un error, ¿cómo solucionarlo? Si no, ¿qué hay de malo en mi código?

Gracias.

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

Print(spread);// Salidas: 0.00009999999999998899
Print(StripError(spread));// Salidas: 0.000099999999999989


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

Hola @amrali, gracias por tu aportación.

¿Quizás se trate de un bug?

Esperaba que la segunda impresión fuera "0.0001".

Si es un error, ¿cómo solucionarlo? Si no, ¿qué hay de malo en mi código?

Gracias.


Imprime las representaciones hexadecimales y comprenderás que la dispersión dista mucho del verdadero valor real 0,0001 (eso se debe a errores de redondeo durante la resta).

Por lo tanto, tienes que utilizar procedimientos de redondeo.

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

   Print(spread);                                  // Salidas: 0.00009999999999998899
   Print(StripError(spread));                      // Salidas: 0.000099999999999989

   Print(DoubleToHexadecimal(spread));             // Salidas: 0x3F1A36E2EB1C4000
   Print(DoubleToHexadecimal(StripError(spread))); // Salidas: 0x3F1A36E2EB1C4001
   Print(DoubleToHexadecimal(0.0001));             // Salidas: 0x3F1A36E2EB1C432D

   Print(EQ(spread, 0.0001));                      // Salidas: true
   Print(Round(spread, 16));                       // Salidas: 0.001

Hay sublte diferencias que usted debe notar:

StripError() redondea al decimosexto dígito significativo 0.00009999999999998899 (los 0 no se cuentan).

Round(x, 16) redondea a los 16 dígitos después del punto decimal 0.00009999999999998899

 
jonlinper #: Esperaba que la segunda impresión fuera "0,0001".

El punto flotante tiene un número infinito de decimales. Eres , que no entiendes floating-point y que algunos números no se pueden representar exactamente. (como 1/10.)
Formato de punto flotante de doble precisión - Wikipedia

Vea también El operando ==. - Foro de programación MQL4 (2013)


pregunta sobre la decima de marketinfo() - Foro de programación MQL4 (2016)

 
William Roeder #:

El punto flotante tiene un número infinito de decimales. Eres , que no entiendes el punto flotante y que algunos números no se pueden representar exactamente. (como 1/10.)
Formato de punto flotante de doble precisión - Wikipedia

Vea también El operando ==. - Foro de programación MQL4 (2013)


pregunta sobre la decima de marketinfo() - Foro de programación MQL4 (2016)

Estimado William, gracias por la aclaración, sin embargo no estoy de acuerdo contigo en cuanto al "número infinito de decimales". Los números FP en realidad tienen un número finito de cifras decimales. (Por ejemplo 0,1 tiene exactamente 52 dígitos después del punto decimal).

Utiliza DoubleToStringExact(0.1) de mi librería para imprimirlos todos. También, puedes comprobar la cadena decimal completa usando esta calculadora aquí: https://www.exploringbinary.com/floating-point-converter/
Además, fíjate en que la cadena decimal completa debe terminar siempre con el dígito "5".

0.1000000000000000055511151231257827021181583404541015625
 

Cuál es la forma más optimizada de Imprimir sólo dígitos significativos con dobles.

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

Esta función funciona muy bien para el 99,9% de los números, pero tiene problemas con números redondos como 1.0000000000

Mi problema es que tengo que quitar los dígitos no significativos, y por alguna razón no soy capaz de hacerlo utilizando sólo @Trunc,

así que terminé usando algo como:

string Normalize_Double_ToString(double n, int d)
{
   // Paso 1 - ayuda a excluir los ceros finales
   n = Round(n, d);

   // Paso 2 - contar el número de decimales significativos
   int sd = GetSignificantDecimals(n);

   // Paso 3 - no queremos más de lo especificado en @d
   if (sd > d){ sd = d; }

   // Paso 4 - eliminar los decimales no deseados sin el redondeo aleatorio negativo 
   double t = Trunc(n, sd);

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

   // Paso 5 - fijar la precisión
   string s = DoubleToString(t, sd);

   return s;
}

Funciona exactamente como lo necesitaba, me da la cadena más pequeña posible para todos los números, pero me preguntaba si se puede optimizar y aún así obtener la cadena más pequeña con números redondos como 1.00000000

Gracias

 

Acabo de darme cuenta de que estoy usando

int GetSignificantDecimals(double value)

versión ligeramente modificada de su

int GetSignificantDigits(double value)

y este es el código

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

   // suma de decimales
   int digits = GetDigits(value);

   // excluyendo los ceros finales
   while(MathMod(value, 10) == 0)
   {
      digits--;
   }

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

Mi problema es que necesito eliminar los dígitos no significativos, y por alguna razón no soy capaz de hacerlo utilizando sólo @Trunc,

así que terminé usando algo como:

Funciona exactamente como se necesita, dándome la cadena más pequeña posible para todos los números, pero me preguntaba si se puede optimizar y aún así obtener la cadena más pequeña con números redondos como 1.00000000

Gracias

Lo siento, usted necesita entender lo que son los dígitos significativos como me parece que su código es confuso los conceptos básicos.
¿Qué estás tratando de hacer .. por favor, explique en palabras sencillas, sin código. Dar ejemplo de lo que se encuentra mal y cuáles son sus expectativas.
 
amrali #:
Lo siento, tienes que entender lo que son dígitos significativos ya que tu código confunde los conceptos básicos.
¿Qué estás tratando de hacer .. por favor, explique en palabras sencillas, sin código. Dar ejemplo de lo que se encuentra mal y cuáles son sus expectativas.

Gracias por tu tiempo, y sí, no estoy totalmente seguro de entender 'dígitos significativos'.

Básicamente necesito 'Imprimir' el número más corto posible. Por ejemplo

1.0000000 -> 1

1.0090000 -> 1.009

123.00100 -> 123.001

Para mí 'dígitos significativos' significa: dígito que cambia el valor de un número si se elimina, por lo que los ceros al final no son significativos.


Por cierto, desde la última actualización de Windows la función Round(double, int) hace que MT4 se bloquee. El primer código que publiqué funcionaba perfectamente y desde ayer por la noche congela completamente el cliente MT4.

 
Cristian Dan Fechete #:

Gracias por tu tiempo, y sí, no estoy totalmente seguro de entender "dígitos significativos".

Básicamente necesito 'Imprimir' el número más corto posible. Por ejemplo:

1.0000000 -> 1

1.0090000 -> 1.009

123.00100 -> 123.001

Para mí "dígitos significativos" significa: dígito que cambia el valor de un número si se elimina, por lo que los ceros al final no son significativos.


Por cierto, desde la última actualización de Windows la función Round(double, int) está haciendo que MT4 se bloquee. El primer código que publiqué funcionaba perfectamente y desde ayer por la noche congela completamente el cliente MT4.

La función Print() o la conversión de double a string como (string)dbl obtendrá el menor número posible de dígitos significativos, sin necesidad de manipular el número primero. Esta es una característica incorporada en MQL. (Ya he propuesto una solución para el equipo de desarrolladores, y se fusionó en el código).

Todo lo que necesitas hacer es
cadena num_str = cadena(número).
o, Print(número);

Es por eso que una función dedicada a imprimir o formatear números a la cadena más corta posible no es necesaria dentro de la biblioteca, la funcionalidad ya está soportada por el lenguaje MQL.

Utilice DoubleToString() sólo si necesita controlar el número de dígitos después del punto decimal. Si el parámetro digits es mayor que los dígitos decimales de su número, los 0 se añadirán a la cadena devuelta, por ejemplo
DoubleToString(1.09, 5) devuelve la cadena "1.09000".

Si el parámetro dígitos es menor que los dígitos decimales del número, el número se aproximará, como DoubleToString(1.12345, 2) devuelve la cadena "1.12". La confusión surge de la incapacidad de diferenciar los números de las cadenas.

Tenga en cuenta que los números que terminan en ceros como 1.09, 1.090, 1.0900, 1.09000 y 1.090000 se almacenan como el mismo número fp de doble precisión en las variables. Estos sólo se pueden conseguir mediante la introducción directa por parte del usuario como entradas manuales. Dentro del programa todos esos números se almacenan como el mismo número que es 1.09, los 0 finales no se almacenan.

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

Las funciones de redondeo como round, ceil y floor cambian (aproximan) el número de entrada a otro número que es el número doble más cercano con el recuento especificado de dígitos decimales después del punto decimal, o el número que contiene el número total especificado de dígitos significativos en el caso de RoundToSignificantDigits().

Espero que esto aclare la confusión sobre la conversión de números fp de doble precisión a cadena.