Libraries: Math Utils - page 5

 
FXAI #:
These are three handy functions for comparison and rounding of floating-point numbers and formatting money:

1. `bool DoubleEquals(double x, double y, double eps)` compares two double values `x` and `y` with a given epsilon value `eps` and returns a boolean value indicating if they are equal within the given tolerance.

2. `double RoundTo(double value, int digits)` rounds a double value `value` to the given number of decimal `digits`.

3. `string FormatMoney(double amount)` formats a double value `amount` as a string representing a currency amount. It formats the amount with two decimal places, replaces the decimal point with a comma, and inserts spaces every three digits for readability. It also adds the currency symbol obtained from `AccountInfoString(ACCOUNT_CURRENCY)` at the end.

Thank you so much for that. However, these functions are already implemented in the library (even with more robust results than yours), but with different names.

// Check whether two numbers are equal up to "n" digits of precision.
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)

// accurate decimal rounding to avoid unexpected results.
double Round(const double v);                       
double Round(const double value, const int digits); 
double Round(const double value, const double step);

// Formats double with thousands separator and specified decimals.
string FormatDouble(const double number, const int digits, const string separator=",");

 

Hello @amrali, thanks for your contribution.

Maybe this is a bug?

I expected second print to be "0.0001".

If is a bug, how to fix? If not, whats wrong in my code?

Thank you.

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

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


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

Hello @amrali, thanks for your contribution.

Maybe this is a bug?

I expected second print to be "0.0001".

If is a bug, how to fix? If not, whats wrong in my code?

Thank you.


Print the hexadecimal representations and you will understand that the spread is far from the true real value 0.0001 (that is because round-off errors during subtraction).

So, you have to use rounding procedures.

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

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

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

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

There are sublte differences you should notice:

StripError() rounds at the 16th signifiant digit 0.00009999999999998899 (0's are not counted).

Round(x, 16) is rounding at the 16th digits after the decimal point  0.00009999999999998899

 
jonlinper #: I expected second print to be "0.0001".

Floating-point has an infinite number of decimals. It's you, not understanding floating-point and that some numbers can't be represented exactly. (like 1/10.)
          Double-precision floating-point format - Wikipedia

See also The == operand. - MQL4 programming forum (2013)

If you want to see the correct number of digits, convert it to a string with the correct/wanted accuracy.
          question about decima of marketinfo() - MQL4 programming forum (2016)

 
William Roeder #:

Floating-point has an infinite number of decimals. It's you, not understanding floating-point and that some numbers can't be represented exactly. (like 1/10.)
          Double-precision floating-point format - Wikipedia

See also The == operand. - MQL4 programming forum (2013)

If you want to see the correct number of digits, convert it to a string with the correct/wanted accuracy.
          question about decima of marketinfo() - MQL4 programming forum (2016)

Dear William, thanks for the clarification, however I do not agree with you regards "infinite number of decimals". FP numbers actually have finite number of decimal digits. (For example 0.1 has exactly 52 digits after the decimal point). 

Pease use DoubleToStringExact(0.1) from my library to print them all. Also, you can check the complete decimal string using this calculator here: https://www.exploringbinary.com/floating-point-converter/
Also, notice that the complete decimal string must always end with the digit "5".

0.1000000000000000055511151231257827021181583404541015625
 

What is the most optimized way to Print only significant digits with doubles.

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

This function works great for 99,9% of the numbers but it has issues with round numbers like 1.0000000000

My issues is that I need to strip non significant digits, and for some reason I am not able to do it using only @Trunc,

so I ended up using something like:

string Normalize_Double_ToString(double n, int d)
{
   // Step 1 - it helps exclude trailing zeros
   n = Round(n, d);

   // Step 2 - count number of Significant Decimals
   int sd = GetSignificantDecimals(n);

   // Step 3 - we don't want more than specified in @d
   if (sd > d){ sd = d; }

   // Step 4 - remove unwanted decimals without the negative random rounding 
   double t = Trunc(n, sd);

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

   // Step 5 - set precision
   string s = DoubleToString(t, sd);

   return s;
}

It works exactly as needed, giving me the smallest string posible for all numbers, but I was wondering if it can be optimized and still get smallest string with round numbers like 1.00000000

Thank you

 

Just realized that I am using

int GetSignificantDecimals(double value)

Slightly modified version of your

int GetSignificantDigits(double value)

and this is the code

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

   // sum of decimals
   int digits = GetDigits(value);

   // excluding trailing zeros
   while(MathMod(value, 10) == 0)
   {
      digits--;
   }

   return digits;
}
 
Cristian Dan Fechete #:

What is the most optimized way to Print only significant digits with doubles.

This function works great for 99,9% of the numbers but it has issues with round numbers like 1.0000000000

My issues is that I need to strip non significant digits, and for some reason I am not able to do it using only @Trunc,

so I ended up using something like:

It works exactly as needed, giving me the smallest string posible for all numbers, but I was wondering if it can be optimized and still get smallest string with round numbers like 1.00000000

Thank you

Sorry, you need to understand what are significant digits as I find your code is confusing the basic concepts.
What are you trying to do.. please explain in plain words, no code. Give example of what you find wrong and what are your expectations.
 
amrali #:
Sorry, you need to understand what are significant digits as I find your code is confusing the basic concepts.
What are you trying to do.. please explain in plain words, no code. Give example of what you find wrong and what are your expectations.

Thx for you time, and yes, I am not totally sure I understand 'significant digits'

I basically need to 'Print' the shortest possible number. For example:

1.0000000 -> 1

1.0090000 -> 1.009

123.00100 -> 123.001

For me 'significant digits' means: digit that changes the value of a number if removed, so trailing zeros are not significant.


By the way, since Windows last update the function Round(double, int) is causing MT4 to block. The first code I posted was working perfectly and since yesterday night It completely freezes MT4 client.

 
Cristian Dan Fechete #:

Thx for you time, and yes, I am not totally sure I understand 'significant digits'

I basically need to 'Print' the shortest possible number. For example:

1.0000000 -> 1

1.0090000 -> 1.009

123.00100 -> 123.001

For me 'significant digits' means: digit that changes the value of a number if removed, so trailing zeros are not significant.


By the way, since Windows last update the function Round(double, int) is causing MT4 to block. The first code I posted was working perfectly and since yesterday night It completely freezes MT4 client.

Print() function or casting double to string like (string)dbl will get the shortest possible number of significant digits, no need to manipulate the number first. This a built-in feature in MQL. (I already proposed a fix to the developers team, and it was merged into code).

All you need to do is:
string num_str = string(number).
or, Print(number);

That is why a dedicated function to Print or format numbers to the shortest possible string is not needed inside the library, the functionality is already supported by MQL language.

Only use DoubleToString() if you need to control the number of digits after the decimal point. If digits parameter is greater than the decimal digits in your number, the 0's will be appended to the returned string, for example 
DoubleToString(1.09, 5) returns the string "1.09000". 

If the digits parameter is less than the number's deciaml digits, the number will be approximated, like DoubleToString(1.12345, 2) returns the string "1.12". You confusion arises from inability to differentiate numbers from strings.

Note that numbers ending in zeros like 1.09, 1.090, 1.0900, 1.09000 and 1.090000 are stored as the same Double-precision fp number in variables. These can only be achieved using direct entry by the user as manual inputs. Inside the program all those numbers are stored as the same number which is 1.09, trailing 0's are not stored.

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

Rounding functions like round, ceil and floor changes (approximates) the input number to another number that is the nearest double number with the specified count of decimal digits after the decimal point, or the number containing the specified total number of significant digits in case of RoundToSignificantDigits().

I hope that will clear the confusion about converting double-precision fp numbers to string.