MathRound fails for one particular number

 

Good evening!

I am using 

MathRound(Double,2);

to round a couple of doubles to two decimal places. Works, apart from one double:

2017.12.31 19:20:12.891 2017.01.02 08:00:00   Rounded Double: 0.17
2017.12.31 19:20:12.891 2017.01.02 08:00:00   Rounded Double: 0.13
2017.12.31 19:20:12.891 2017.01.02 08:00:00   Rounded Double: 0.17
2017.12.31 19:20:12.891 2017.01.02 08:00:00   Rounded Double: 0.23
2017.12.31 19:20:12.891 2017.01.02 08:00:00   Rounded Double: 0.34
2017.12.31 19:20:12.891 2017.01.02 08:00:00   Rounded Double: 0.46
2017.12.31 19:20:12.891 2017.01.02 08:00:00   Rounded Double: 0.6899999999999999
2017.12.31 19:20:12.891 2017.01.02 08:00:00   Rounded Double: 0.93
2017.12.31 19:20:12.891 2017.01.02 08:00:00   Rounded Double: 1.37

Any ideas?

 
syrvn 0: Good evening! I am using  ... to round a couple of doubles to two decimal places. Works, apart from one double: Any ideas?

That is not the documented way of using the MathRound() function. There is only one parameter, not two! It won't even compile that way, unless you have done something else entirely!

MathRound

The function returns a value rounded off to the nearest integer of the specified numeric value.

double  MathRound(
   double  value      // value to be rounded
   );

Parameters

value

[in]  Numeric value before rounding.

Return Value

Value rounded till to the nearest integer.

Note

Instead of MathRound() you can use round().

 
syrvn 0:

to round a couple of doubles to two decimal places. Works, apart from one double:


Is NormalizeDouble() what you want?

https://www.mql5.com/en/docs/convert/normalizedouble

 
Fernando Carreiro:

That is not the documented way of using the MathRound() function. There is only one parameter, not two! It won't even compile that way, unless you have done something else entirely!


He probably means NormalizeDouble... 


syrvn0, 

You have to use DoubleToString (for some *buggy* reason) when printing doubles to log. 

void OnStart()
{
   double number = NormalizeDouble(0.6899999999999999,2);
   Print(number==0.69 ? "Is equal":"Is NOT equal");
   Print(number);
   Print(DoubleToString(number));
}
// Is equal

// 0.6899999999999999

// 0.69000000
 
  1. nicholishen: You have to use DoubleToString (for some *buggy* reason) when printing doubles to log. 
    There's no "buggy reason". Floating point has infinite number of decimals, it's your not understanding floating point and that some numbers can't be represented exactly. (like 1/10.)
              Double-precision floating-point format - Wikipedia, the free encyclopedia

  2. Print(number==0.69 ? "Is equal":"Is NOT equal");
    Doubles are rarely equal.
              The == operand. - MQL4 and MetaTrader 4 - MQL4 programming forum

 
whroeder1:
  1. There's no "buggy reason". Floating point has infinite number of decimals, it's your not understanding floating point and that some numbers can't be represented exactly. (like 1/10.)
              Double-precision floating-point format - Wikipedia, the free encyclopedia

  2. Doubles are rarely equal.
              The == operand. - MQL4 and MetaTrader 4 - MQL4 programming forum

You must have missed the entire part where I normalized that double and validated the value and it still prints wrong. Not a bug, huh? Ok...
 
No it's not. The "normalized" value couldn't be represented.
 
whroeder1:
No it's not. The "normalized" value couldn't be represented.
That literally makes no sense
 
nicholishen: That literally makes no sense

He means that the value "0.69" cannot be exactly represented given the way a floating point number works (based on binary and not decimal representation) - hence, why the value gets represented as "0.68999999..." (see below).

You can never really "normalize" it and it is the main reason why both @whroeder1 and myself contest the use of the NormalizeDouble() function. It should in the very least be renamed to something like "RoundDigits()" because it is more of a "rounding" function and does not "normalize" in any way or fashion.


https://www.h-schmidt.net/FloatConverter/IEEE754.html

EDIT: Please note that the above images are for examples of representation in the 4-byte "float", and not the 8-byte "double" which offers more precision but still cannot represent the value "0.69" exactly due to the "binary" nature of the format.

EDIT2: For future readers that have difficulty understanding (or accepting) this concept, of decimal values not having an exact representation with a "float" or a "double", here is an article worth reading:

Why 0.1 Does Not Exist In Floating-Point

Many new programmers become aware of binary floating-point after seeing their programs give odd results: “Why does my program print 0.10000000000000001 when I enter 0.1?”; “Why does 0.3 + 0.6 = 0.89999999999999991?”; “Why does 6 * 0.1 not equal 0.6?” Questions like these are asked every day, on online forums like stackoverflow.com.

The answer is that most decimals have infinite representations in binary. Take 0.1 for example. It’s one of the simplest decimals you can think of, and yet it looks so complicated in binary:


Decimal 0.1 In Binary ( To 1369 Places

The bits go on forever; no matter how many of those bits you store in a computer, you will never end up with the binary equivalent of decimal 0.1.

... Read the rest of the article at: http://www.exploringbinary.com/why-0-point-1-does-not-exist-in-floating-point/

 
Fernando Carreiro:

He means that the value "0.69" cannot be exactly represented given the way a floating point number works (based on binary and not decimal representation) - hence, why the value gets represented as "0.68999999..."

You can never really "normalize" it and it is the main reason why both @whroeder1 and myself contest the use of the NormalizeDouble() function. It should in the very least be renamed to something like "RoundDigits()" because it is more of a "rounding" function and does not "normalize" in any way or fashion.


https://www.h-schmidt.net/FloatConverter/IEEE754.html
Thanks, but my point was the print function has one job, convert params into a concatenated string and write to the log. I personally consider it "buggy" that this function can't take a rounded double and properly print it to the log, but what do I know...
 
nicholishen: Thanks, but my point was the print function has literally one job, convert params into a concatenated string and write to the log. I personally consider it "buggy" that this function can't take a rounded double and properly print it to the log, but what did I know...

No, it is not buggy and is in fact documented (and even suggests an alternative), that by default it will output double values using 16 digits for the conversion. So given that the value cannot be represented in only 2 decimal positions, it will always output the multi decimal approximation as is this case.

Data of double type are shown with the accuracy of up to 16 digits after a decimal point, and can be output either in traditional or in scientific format, depending on what entry will be more compact. Data of float type are output with 5 digits after a decimal point. To output real numbers with another accuracy or in a predefined format, use the PrintFormat() function.
Reason: