Which is faster - Floating-Point or Integer arithmetic? - page 2

 
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>


Now tell us, why your recommended method (CDouble to avoid any misreading) is not in your benchmark ? 

I am already laughing...just curious to see if you will admit the truth or find a way to distort it. 

Without offense please.


Beside that your tests are ok. But Fernando is right too, you just don't find a way to communicate constructively.

There is no "common language to communicate" 


EDIT: Maybe my post will appear as being not constructive, sorry about that, I don't have "bad" intention for sure.
 
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.

Maybe you will understand my "hint" better with this very simple "unscientific" adaptation of your test code: (EDIT: I have added a "long" test as well)

For LoopCount = 10:

        1: 9503 ms for 100000000 iterations
        2: 7287 ms for 100000000 iterations
        3: 6676 ms for 100000000 iterations
        4: 8119 ms for 100000000 iterations

For LoopCount = 100:

        1: 39518 ms for 100000000 iterations
        2: 37187 ms for 100000000 iterations
        3: 26537 ms for 100000000 iterations
        4: 32618 ms for 100000000 iterations

For LoopCount = 1000:

        1: 318924 ms for 100000000 iterations
        2: 311014 ms for 100000000 iterations
        3: 211671 ms for 100000000 iterations
        4: 267377 ms for 100000000 iterations
#property strict
#property show_inputs

input int LoopCount = 100;

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);
   ///////////////////////////////////////////////////////////////////
   result=0;
   m = GetMicrosecondCount();
   for(ulong i=0;i<iterations;i++)
      result+= CompareDoubles4(price[rand()%3],price[rand()%3]);
        
   m=GetMicrosecondCount()-m;
   m/=1000;
   printf("4: %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);
   double res2 = res;
   for( double j = LoopCount; j > 0; j-- )
      res2 += j;
   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;
   double res2 = res;
   for( double j = LoopCount; j > 0; j-- )
      res2 += j;
   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));
   int res2 = res;
   for( int j = LoopCount; j > 0; j-- )
      res2 += j;
   if(res==0)
      return 0;
   if(res > 0)
      return 1;
   return -1;
}

int CompareDoubles4(const double num1,const double num2)
{
   long res =  ((long)round(num1 / _Point) - (long)round(num2 / _Point));
   long res2 = res;
   for( long j = LoopCount; j > 0; j-- )
      res2 += j;
   if(res==0)
      return 0;
   if(res > 0)
      return 1;
   return -1;
}
 
Fernando Carreiro:

Maybe you will understand my "hint" better with this very simple "unscientific" adaptation of your test code: (EDIT: I have added a "long" test as well)

I'm sorry, but under what possible scenario would this ever be implemented into practical use? You would literally need to have more comparisons than you have data. I can see this might be useful for sorting a large data-set of prices, but what else would you use it for? 
 
nicholishen: I'm sorry, but under what possible scenario would this ever be implemented into practical use? You would literally need to have more comparisons than you have data. I can see this might be useful for sorting a large data-set of prices, but what else would you use it for? 

For the very scenario that I have been trying to explain to you, started by this very thread and that the OP even recognised a need for!

That in an EA one would convert incoming quote data into integer format, and manipulated henceforth as integers, such as defining s/r levels, breakout levels, donchian channels, trailing stops, risk calculations, order volumes and many other variables, for which one would only need to convert back when placing or updating an order.

That is the difference between your "lab" test, and the real-life application which I (and at least 3 others that I now of) use in EAs for better and more efficient data handling, especially for price action EAs where one does not make use of Indicators, but not only for those cases!

 
Fernando Carreiro:

For the very scenario that I have been trying to explain to you, started by this very thread and that the OP even recognised a need for!

That in an EA one would convert incoming quote data into integer format, and manipulated henceforth as integers, such as defining s/r levels, breakout levels, donchian channels, trailing stops, risk calculations, order volumes and many other variables, for which one would only need to convert back when placing or updating an order.

That is the difference between your "lab" test, and the real-life application which I (and at least 3 others that I now of) use in EAs for better and more efficient data handling, especially for price action EAs where one does not make use of Indicators, but not only for those cases!


To be fair, OP started the thread about rounding lots, and if it were just that then the int-method would be least efficient. I'm also testing your theory and I cannot reproduce your results. Do you have a better example? Do you think Metaquotes might have changed something?

Signal1(double) = 99 ms. Counter = 2499798
Signal2(int) = 219 ms. Counter = 2499798


 

#include <Arrays\ArrayDouble.mqh>
void OnStart()
{
//---
  
   srand(_RandomSeed);
   int n = (int)pow(10,7);
   double pseudo_bid[2];
   pseudo_bid[0] = 1.23456;
   pseudo_bid[1] = 1.23455;
   CArrayDouble d;
   Comment(StringFormat("Reserving %d = %s",n,(string)d.Reserve(n)));

   for(int i=0;i<n;i++)
      d.Add(pseudo_bid[rand()%2]);
         
   ulong counter=0;
   ulong ms = GetMicrosecondCount();
   for(int i=0;i<n;i++)
      if(Signal1(d[i]))
         counter++;
   printf("Signal1(double) = %d ms. Counter = %d",(GetMicrosecondCount()-ms)/1000,counter);
   
   counter=0;
   ms = GetMicrosecondCount();
   for(int i=0;i<n;i++)
      if(Signal2(d[i]))
         counter++;
   printf("Signal2(int) = %d ms. Counter = %d",(GetMicrosecondCount()-ms)/1000,counter);
}
//+------------------------------------------------------------------+
bool Signal1(const double bid)
{
   static double last_bid=0.0;
   static double level = 1.23456;
   
   bool res = false;
   if((bid >= level && last_bid < level) || (bid <= level && last_bid > level))
      res= true;
   last_bid = bid;
   return res;
}
bool Signal2(const double bid)
{
   static int last_bid=0;
   static int level = 123456;
   
   int bid_int = (int)round(bid/1e-05);
   
   bool res = false;
   if((bid_int >= level && last_bid < level) || (bid_int <= level && last_bid > level))
      res= true;
   last_bid = bid_int;
   return res;
}
 
nicholishen: To be fair, OP started the thread about rounding lots, and if it were just that then the int-method would be least efficient. I'm also testing your theory and I cannot reproduce your results. Do you have a better example? Do you think Metaquotes might have changed something?

It seems that you still don't get it! No, MetaQuotes did not change anything! Even with the example changes I provided, your new test is still constantly repeating the conversion processes at every iteration, so obviously you can't reproduce the results!

I have already tried to explain, that once something is converted, that information is stored as an integer and does not need to be continually converted every time it is needed. There is only one or two conversions per tick, but many integer based operations taking place during the same event or iteration. So for every tick event, there is 1 or 2 conversions, but several 10-100 integer operations, such as addition, subtraction, multiplication, division, comparison. This is the reason why I added the extra loop of additions to your other example, so as to simulate that idea.

To me this seems so obvious because I put into practice every day but I just don't know how else to explain this to you!

 
Fernando Carreiro:

It seems that you still don't get it! No, MetaQuotes did not change anything! Even with the example changes I provided, your new test is still constantly repeating the conversion processes at every iteration, so obviously you can't reproduce the results!

I have already tried to explain, that once something is converted, that information is stored as an integer and does not need to be continually converted every time it is needed. There is only one or two conversions per tick, but many integer based operations taking place during the same event or iteration. So for every tick event, there is 1 or 2 conversions, but several 10-100 integer operations, such as addition, subtraction, multiplication, division, comparison. This is the reason why I added the extra loop of additions to your other example, so as to simulate that idea.

To me this seems so obvious because I put into practice every day but I just don't know how else to explain this to you!


So we can agree, the <int> method requires a bare minimum of 3+ (additional) operations just to break-even with time it took to convert the double int.  So ultimately what's pay-off? A quicker back-test?


If I was really trying to optimize for the best performance in a live trading environment (not for the backtester), I would implement a hot-path (like real quants do). Something like this for example

void OnTick()
{
   if(!HotPath())
      SlowPath(); //slow path
}

bool HotPath()
{
   if(bid <= hotpath_max && bid >= hotpath_min && TimeCurrent() < hotpath_expire)
   {
      return OrderSend(bid);
   }
   return false;
}

void SlowPath()
{
   ... get ready for next tick. Check for order-status - get indicator data - set hotpath variables - etc.
}

So if you were to implement a hot-path using the <int> method, you'd only have two double comparisons but you'd have a minimum of 2 double conversions which would be way less efficient. Let's be real, you wouldn't use it on the hot-path, you'd use it on the slow-path and then convert to doubles for use in the hot-path -- which wouldn't have a meaningful impact since both methods would complete the slow-path well before the next tick arrives. 


This leads us to only one possible ultimate-optimization using the <int> method, back-testing. Now here is where our opinions will likely diverge. I would personally rather spend a couple of bucks on MQL5-cloud-testing than take the extra time to code/debug potentially hundreds (as you put it) int<->double conversions.  

 
nicholishen:

So we can agree, the <int> method requires a bare minimum of 3+ (additional) operations just to break-even with time it took to convert the double int.  So ultimately what's pay-off? A quicker back-test?

If I was really trying to optimize for the best performance in a live trading environment (not for the backtester), I would implement a hot-path (like real quants do). Something like this for example

So if you were to implement a hot-path using the <int> method, you'd only have two double comparisons but you'd have a minimum of 2 double conversions which would be way less efficient. Let's be real, you wouldn't use it on the hot-path, you'd use it on the slow-path and then convert to doubles for use in the hot-path -- which wouldn't have a meaningful impact since both methods would complete the slow-path well before the next tick arrives.

This leads us to only one possible ultimate-optimization using the <int> method, back-testing. Now here is where our opinions will likely diverge. I would personally rather spend a couple of bucks on MQL5-cloud-testing than take the extra time to code/debug potentially hundreds (as you put it) int<->double conversions.  

Point 1: Agree and yes, back-testing speed is also important There is always a pay-off (back-test or live trading), because the use of integers is not just for speed alone but also to have prices and volumes always in an aligned state with regards to Tick Size and Lot Step. I can also state that for most EAs, there is almost always more than 3 integer operations taking place, and the extra speed is in fact quite an advantage when you have multiple EAs running on a terminal, where every CPU cycle counts (and so does RAM usage).

Point2: I already use a similar method to what you call "Hot Path" (I confess I had never heard it called that). It is also a method that has been discussed many times on the forum.

Point 3: Not every EA is running or being back-tested solely on MT5. I still have plenty of EAs (the majority) being used on MT4 where there is no MQL5 Cloud Testing. But even on MT5, I still apply the same principles because I share the code source between both and always try to code EAs and Indicators so as to work on both systems. Code once, use on both!

Point 4: I did not say 100 conversions - I said 1 or 2 conversions and 100 purely Integer operations. It seems you still continue to not want to understand this point.

Final point! We are never going to agree on this, so it is futile to continue. I will continue coding my way and you, your way! You are happy with your coding and I am happy with mine. That is all that really needs to be said!

 
nicholishen:

...

This leads us to only one possible ultimate-optimization using the <int> method, back-testing. Now here is where our opinions will likely diverge. I would personally rather spend a couple of bucks on MQL5-cloud-testing than take the extra time to code/debug potentially hundreds (as you put it) int<->double conversions.  

It's not only about backtests, but even here your reasoning is weird. Did you already use the MT5 Cloud ? If your code is not efficient you will pay 2, 3, 10 times the bill.

Most people are using a lot of charts, with indicators and EAs, speed performance is always important. I don't understand at all your "I do not care about speed attitude".

You know very well why you didn't included your CDouble in your benchmark. Why such attitude ?


As Fernando said, there is no problem to code like you want, using all OOP or whatever. Like we are doing all.

But a lot of people are reading this forum, trying to learn, and they deserve to have all arguments pros and cons.

 
But a lot of people are reading this forum, trying to learn, and they deserve to have all arguments pros and cons.

That's exactly what I'm doing, which is why I'm a little perplexed that you guys are treating this like a personal attack. This is a scientific discussion about the pros, cons, and specific algorithm application, and as such there should be no feelings or subjective reasoning involved. I didn't include CDouble because that was not where this conversation had veered. The OP had asked what was the best way to round doubles and the CDouble static methods would far outperform converting double to int back to double by a very wide margin.

The only time it would make sense to use the int method is in very unique scenarios where you have +4:1 operations to conversion ratio. Now this is only counting one way. On any given tick you have at the bare minimum convert the bid to int. On a valid signal, the bare-minimum is:

bid -> int
int -> entry
int -> sl
int -> tp
int -> lots

That's a requirement of over 20 additional operations to break-even, and we haven't even begun to talk about the overhead of converting indicator data, existing order data, etc. I can think of only a small minority of situations where that would apply. So the bottom line is this; if you have a unique situation with >4:1 (operation:conversion) and you want to take the extra time to convert the entire EA to int and back then the int method is best. Otherwise, in every other situation another method is best.