Which is faster - Floating-Point or Integer arithmetic?

 
Pedro Taveira:
That's exactly what I was thinking.

Thanks again!


There are several ways to compare doubles, but be careful when working with (int)s because it's super-easy to mess up and accidentally cast double to int at the wrong place. int(1.999999) = 1 not 2 ...not to mention the performance difference is so negligible it's not worth the keystrokes for the extra code. 


This is what metaquotes recommends...

(NormalizeDouble(num1-num2,digits)==0.0)

I like this better...

(fabs(num1-num2) < step)

This is what I actually use (CDouble library

CDouble num1 = 3.14, num2 = 3.1399999999999;
Print(num1 == num2) //overloaded operator - automatically compares doubles the right way. No need for extra code.
//true


 
This is what your code would look like... 

#include <Double.mqh>
void OnStart()
{
   CDouble lotsize(PRECISION_LOT_STEP);
   lotsize = 0.01;
   for(int i=0; i<10; i++)
   {
      lotsize += lotsize.Step();
      printf("[%d] = %s", i, lotsize.ToString());     
   }
}
KS      0       14:31:59.745    Foo (EURUSD,M1) [0] = 0.02
LN      0       14:31:59.748    Foo (EURUSD,M1) [1] = 0.03
LH      0       14:31:59.748    Foo (EURUSD,M1) [2] = 0.04
HR      0       14:31:59.748    Foo (EURUSD,M1) [3] = 0.05
HL      0       14:31:59.748    Foo (EURUSD,M1) [4] = 0.06
LG      0       14:31:59.748    Foo (EURUSD,M1) [5] = 0.07
DQ      0       14:31:59.748    Foo (EURUSD,M1) [6] = 0.08
PK      0       14:31:59.748    Foo (EURUSD,M1) [7] = 0.09
QF      0       14:31:59.748    Foo (EURUSD,M1) [8] = 0.1
CP      0       14:31:59.748    Foo (EURUSD,M1) [9] = 0.11
 

Just for fun I ran some performance tests comparing doubles to the eighth digit. 

Method 1:

bool IsEqual1(const double num1,const double num2)
{
   return (NormalizeDouble(num1-num2,8)==0.0);
}
1: 936 ms for 100000000 iterations


Method 2:

bool IsEqual2(const double num1,const double num2)
{
   return (fabs(num1-num2) < 1e-08);
}
2: 235 ms for 100000000 iterations


Method 3: note - <int> was not appropriate for rounding since it couldn't hold the amount of data required for storing (doubles/step). Had to switch to <long>

bool IsEqual3(const double num1,const double num2)
{
   long tick1 = (long)round(num1 / 1e-08);
   long tick2 = (long)round(num2 / 1e-08);
   return (tick1==tick2);
}
3: 1531 ms for 100000000 iterations


void OnStart()
{
   ulong iterations = (int)pow(10,8);
   double num1 = 0.14;
   double num2 = 0.1399999999999;
   ///////////////////////////////////////////////////////////////////
   long not_equal=0;
   ulong m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      if(!IsEqual1(num1*(double)i,num2*(double)i))
         not_equal++;
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("1: %d ms for %d iterations",m,iterations);
   printf("not equal = %d",not_equal);
   ///////////////////////////////////////////////////////////////////
   not_equal=0;
   m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      if(!IsEqual2(num1*(double)i,num2*(double)i))
         not_equal++;
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("2: %d ms for %d iterations",m,iterations);
   printf("not equal = %d",not_equal);
   ///////////////////////////////////////////////////////////////////
   not_equal=0;
   m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      if(!IsEqual3(num1*(double)i,num2*(double)i))
         not_equal++;
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("3: %d ms for %d iterations",m,iterations);
   printf("not equal = %d",not_equal);
}
//+------------------------------------------------------------------+

bool IsEqual1(const double num1,const double num2)
{
   return (NormalizeDouble(num1-num2,8)==0.0);
}

bool IsEqual2(const double num1,const double num2)
{
   return (fabs(num1-num2) < 1e-08);
}

bool IsEqual3(const double num1,const double num2)
{
   long tick1 = (long)round(num1 / 1e-08);
   long tick2 = (long)round(num2 / 1e-08);
   return (tick1==tick2);
}
 

I realized that the tests weren't apples to apples since method two wasn't properly rounding. Here is a straight up apples to apples function operation comparison, and the performance results turned out the exact same as before. 

void OnStart()
{
   ulong iterations = (int)pow(10,8);
   double num1 = 0.14;
   double num2 = 0.1399999999999;
   ///////////////////////////////////////////////////////////////////
   long not_equal=0;
   ulong m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      if(!IsEqual1(num1*(double)i,num2*(double)i,8))
         not_equal++;
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("1: %d ms for %d iterations",m,iterations);
   printf("not equal = %d",not_equal);
   ///////////////////////////////////////////////////////////////////
   not_equal=0;
   m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      if(!IsEqual2(num1*(double)i,num2*(double)i,8))
         not_equal++;
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("2: %d ms for %d iterations",m,iterations);
   printf("not equal = %d",not_equal);
   ///////////////////////////////////////////////////////////////////
   not_equal=0;
   m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      if(!IsEqual3(num1*(double)i,num2*(double)i,8))
         not_equal++;
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("3: %d ms for %d iterations",m,iterations);
   printf("not equal = %d",not_equal);
}
//+------------------------------------------------------------------+

bool IsEqual1(const double num1,const double num2,const int digits)
{
   return (NormalizeDouble(num1-num2,digits)==0.0);
}

bool IsEqual2(const double num1,const double num2,const int digits)
{
   double point = 1.0 / pow(10, digits+1);
   return (fabs(num1-num2) < point*5);
}

bool IsEqual3(const double num1,const double num2,const int digits)
{
   double point = 1.0 / pow(10, digits);
   return ((long)round(num1 / point) == (long)round(num2 / point));
}
 
nicholishen: Just for fun I ran some performance tests comparing doubles to the eighth digit. 

Method 1:

Method 2:

Method 3: note - <int> was not appropriate for rounding since it couldn't hold the amount of data required for storing (doubles/step). Had to switch to <long>

  1. An "int", can easily accommodate price quotes in ticks or points, and can certainly accommodate volume/lots in lot-step. There is no need to use "long".
  2. Your performance tests are somewhat flawed, because the conversion is not continuously repeated, but only done once or twice per bar or tick, because the rest of the calculations are all done in pure integer as apposed to in floating point. I can certainly attest to the increase in efficiency and performance of an EA coded for "int" instead of "double" from experience - It is not just a "theory"!
 
Fernando Carreiro:
  1. An "int", can easily accommodate price quotes in ticks or points, and can certainly accommodate volume/lots in lot-step. There is no need to use "long".
  2. Your performance tests are somewhat flawed, because the conversion is not continuously repeated, but only done once or twice per bar or tick, because the rest of the calculations are all done in pure integer as apposed to in floating point. I can certainly attest to the increase in efficiency and performance of an EA coded for "int" instead of "double" from experience - It is not just a "theory"!
1. Sometimes it is - provided you only require minimum precision. If you are calculating slopes or something similar which requires a higher precision then no, it is not adequate. 

2. This is incorrect. These aren't compiler tricks as you allude to in point #2. I've altered the test to make for a non-biased/scientifically-fair comparison of three different rounding methods including the built-in NormalizeDouble. Even when I switch method #3 back to <int> (*not universally usable for doubles) it is still slower by a factor of 2.


New testing method: IsEqual1(price[rand()%2],price[rand()%2],_Digits)

1: 1292 ms for 100000000 iterations
2: 744 ms for 100000000 iterations
3: 1455 ms for 100000000 iterations

void OnStart()
{
   ulong iterations = (int)pow(10,8);
  
   MqlTick tick;
   SymbolInfoTick(_Symbol,tick); 
   double price[2];
   price[0] = tick.bid;
   price[1] = tick.ask;
   srand(_RandomSeed);
   
   
   ///////////////////////////////////////////////////////////////////
   long not_equal=0;
   ulong m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      if(!IsEqual1(price[rand()%2],price[rand()%2],_Digits))
         not_equal++;
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("1: %d ms for %d iterations",m,iterations);
   printf("not equal = %d",not_equal);
   ///////////////////////////////////////////////////////////////////
   not_equal=0;
   m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      if(!IsEqual2(price[rand()%2],price[rand()%2],_Digits))
         not_equal++;
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("2: %d ms for %d iterations",m,iterations);
   printf("not equal = %d",not_equal);
   ///////////////////////////////////////////////////////////////////
   not_equal=0;
   m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      if(!IsEqual3(price[rand()%2],price[rand()%2],_Digits))
         not_equal++;
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("3: %d ms for %d iterations",m,iterations);
   printf("not equal = %d",not_equal);
}
//+------------------------------------------------------------------+

bool IsEqual1(const double num1,const double num2,const int digits)
{
   return (NormalizeDouble(num1-num2,digits)==0.0);
}

bool IsEqual2(const double num1,const double num2,const int digits)
{
   double point = 1.0 / pow(10, digits+1) * 5;
   return (fabs(num1-num2) < point);
}

bool IsEqual3(const double num1,const double num2,const int digits)
{
   double point = 1.0 / pow(10, digits);
   return ((int)round(num1 / point) == (int)round(num2 / point));
}
 
nicholishen:
1. Sometimes it is - provided you only require minimum precision. If you are calculating slopes or something similar which requires a higher precision then no, it is not adequate. 

2. This is incorrect. These aren't compiler tricks as you allude to in point #2. I've altered the test to make for a non-biased/scientifically-fair comparison of three different rounding methods including the built-in NormalizeDouble. Even when I switch method #3 back to <int> (*not universally usable for doubles) it is still slower by a factor of 2.

New testing method: IsEqual1(price[rand()%2],price[rand()%2],_Digits)

I am afraid I do not agree, but I also don't feel like getting into greater detail about it either. Lets just agree to disagree!

 
Fernando Carreiro:

I am afraid I do not agree, but I also don't feel like getting into greater detail about it either. Lets just agree to disagree!


I would normally agree... sometimes personalities clash and people have opinions and sometimes you have to agree to disagree, but this is not an opinion-based matter -- it is a scientific test backed up by scientific evidence. I agree with the evidence that method #2 is by far the most efficient method for comparing doubles. 

EDIT* I'd like to add, if you have any evidence to the contrary, I will happily change my mind! :)
 
nicholishen: I would normally agree... sometimes personalities clash and people have opinions and sometimes you have to agree to disagree, but this is not an opinion-based matter -- it is a scientific test backed up by scientific evidence. I agree with the evidence that method #2 is by far the most efficient method for comparing doubles. EDIT* I'd like to add, if you have any evidence to the contrary, I will happily change my mind! :)

Yes, I do have evidence to the contrary and I have tried to steer you in that direction, by stating that your test is doing continuous conversions, when that is not the case of how it is implemented. Sometimes a real-life implementation can be very different to a "lab" simulation.

It is not a question of clashing personalities. It is just that I don't feel like trying to explain all the details  because I am just not in the mood for it right now (its Friday and I am watching some recorded TV-Shows).

So, I will leave you with your conviction but I will continue to use integer based EA's because I have had the benefit of using and testing both types in "action" and know which type is more efficient.

EDIT: Here is a hint - Compare example 1 to example 2:

  1. Convert to "int", followed by 100 integer operations, and convert back to "double".
  2. Read "double", followed by 100 floating point operations, and write it back.

Do you see the difference now to your continuous conversion test?

 
Fernando Carreiro:

Yes, I do have evidence to the contrary and I have tried to steer you in that direction, by stating that your test is doing continuous conversions, when that is not the case of how it is implemented. Sometimes a real-life implementation can be very different to a "lab" simulation.

It is not a question of clashing personalities. It is just that I don't feel like trying to explain all the details  because I am just not in the mood for it right now (its Friday and I am watching some recorded TV-Shows).

So, I will leave you with your conviction but I will continue to use integer based EA's because I have had the benefit of using and testing both types in "action" and know which type is more efficient.


No, I think you're missing my point that with a proper double comparison you don't have to cast. That IS the optimization. Other than that there are no tricks and each bit of code is fully executed on each iteration. It's just plain-faster to avoid casting and go for a universal double comparison. 

Regarding your first point, it's only evidence when you publish a test that can be replicated. After-all, that is the scientific method.

I'm trying to skew the test results in favor of your method based on your description that int comparison is much faster. I created a CompareDouble function which returns 0=equal, 1=greater, -1=less which has more than one comparison. This is where the <int> version should shine since there are more comparisons, but it's negligibly faster than Method #1 and still slower than Method #2.


1: 2519 ms for 100000000 iterations
2: 1414 ms for 100000000 iterations
3: 2305 ms for 100000000 iterations


void OnStart()
{
   ulong iterations = (ulong)pow(10,8);
  
   MqlTick tick;
   SymbolInfoTick(_Symbol,tick); 
   double price[3];
   price[0] = tick.bid;
   price[1] = tick.ask;
   price[2] = tick.ask + 1.0*_Point;
   srand(_RandomSeed);
   
   
   ///////////////////////////////////////////////////////////////////
   long result=0;
   ulong m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      result+= CompareDoubles1(price[rand()%3],price[rand()%3]);
        
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("1: %d ms for %d iterations",m,iterations);
   printf("result = %d",result);
   ///////////////////////////////////////////////////////////////////
   result=0;
   m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      result+= CompareDoubles2(price[rand()%3],price[rand()%3]);
        
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("2: %d ms for %d iterations",m,iterations);
   printf("result = %d",result);
   ///////////////////////////////////////////////////////////////////
   result=0;
   m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      result+= CompareDoubles3(price[rand()%3],price[rand()%3]);
        
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("3: %d ms for %d iterations",m,iterations);
   printf("result = %d",result);
}
//+------------------------------------------------------------------+

int CompareDoubles1(const double num1,const double num2)
{
   double res = NormalizeDouble(num1-num2,_Digits);
   if(res==0.0)
      return 0;
   if(res > 0.0)
      return 1;
   return -1;
}

int CompareDoubles2(const double num1,const double num2)
{
   double res = num1-num2;
   if(fabs(res) < _Point / 10.0 * 5)
      return 0;
   if(res > 0.0)
      return 1;
   return -1;
}

int CompareDoubles3(const double num1,const double num2)
{
   int res =  ((int)round(num1 / _Point) - (int)round(num2 / _Point));
   if(res==0)
      return 0;
   if(res > 0)
      return 1;
   return -1;
}
Strategy Testing - Algorithmic Trading, Trading Robots - MetaTrader 5
Strategy Testing - Algorithmic Trading, Trading Robots - MetaTrader 5
  • www.metatrader5.com
The Strategy Tester allows you to test and optimize trading strategies (Expert Advisors) before using them for live trading. During testing, an Expert Advisor with initial parameters is once run on history data. During optimization, a trading strategy is run several times with different sets of parameters which allows selecting the most...
 
nicholishen:


No, I think you're missing my point that with a proper double comparison you don't have to cast. That IS the optimization. Other than that there are no tricks and each bit of code is fully executed on each iteration. It's just plain-faster to avoid casting and go for a universal double comparison. 

Regarding your first point, it's only evidence when you publish a test that can be replicated. After-all, that is the scientific method.

I'm trying to skew the test results in favor of your method based on your description that int comparison is much faster. I created a CompareDouble function which returns 0=equal, 1=greater, -1=less which has more than one comparison. This is where the <int> version should shine since there are more comparisons, but it's negligibly faster than Method #1 and still slower than Method #2.

Your tests are still using continuous conversions/casting. I have already stated, that it is not the case and even gave your you an example in the hint!

Do as you wish! I will not be posting any further on the subject! Have a good evening!