Menu

Optimization issue?

Osito
2007-11-03
2012-09-26
  • Osito

    Osito - 2007-11-03

    I have some code that takes a double, multiplies by ten, casts as an int and sets an int to that result, and then sets the double to that to round it off (and remove the decimal point). First of all, is there a better way to do this? I do not actually need the int for anything else:

    calculated_setpoint *= 10.00;
    calculated_setpoint_as_int = (int)calculated_setpoint;
    calculated_setpoint = calculated_setpoint_as_int;

    Secondly, I've noticed it doesn't quite work right. I think something is getting optimized out possibly, because if I do this during debug:

    calculated_setpoint *= 10.00;
    sprintf(log,"%.3lf",calculated_setpoint);
    MessageBox(NULL,log,MBWARNING,MB_ICONEXCLAMATION|MB_OK);
    calculated_setpoint_as_int = (int)calculated_setpoint;
    calculated_setpoint = calculated_setpoint_as_int;

    I get a different answer. For instance, if calculated_setpoint starts as 23.4, it shows up as 233.981 in the message box, and the end result printed as (calculated_setpoint/10.0) with format %.1lf is "23.4". If I leave out the sprintf and MessageBox, the same exact thing prints as "23.3", as if instead of rounding 233.981 it simply truncated the result. I can also get it to work properly by removing my -O2 optimization, but then I get a much bigger executable. So is there a way to make it work while leaving -O2 enabled?

    Mostly I'm curious how it optimizes those lines to give the different result.

     
    • Anonymous

      Anonymous - 2007-11-05

      >> I still think there's something more going on than floating point precision issues.
      I don't.

      >> Something must be getting optimized out in one case and not in another
      >> to generate the different end results, right?
      Optimisation can change the order of operations, causing different intermediate values with different precision. It is entirely expected that the result will be different in such circumstances.

      Consider the following code that demonstrates the effect of changing the order of evaluation.

      include <stdio.h>

      include <stdlib.h>

      int main()
      {
      double a = (100.0 * 0.0001) / 999.9 ;
      double b = (100.0 / 999.9) * 0.0001 ;

      printf( &quot;a:%f b:%f\n&quot;, a, b ) ;
      if( a == b )
      {
          printf( &quot;a and b are equal\n&quot; ) ;
      }
      else
      {
          printf( &quot;a and b are not equal\n&quot; ) ;
      }
      
      system(&quot;pause&quot;) ;
      

      }

      Now I built this as unoptimised code on MS VC++ 2005. I have no reason to believe that it won't produce the same result on Dev-C++. a and b differ only in the order of the expressions that calculate them, but they do not result in values for which a==b evaluates to true.

      Clifford

       
      • Osito

        Osito - 2007-11-05

        But now you're talking about different compilers. I'm talking about only one compiler, and my calculations are not in a different order as written, whereas yours are.

        If your point is just that optimization changes the order, that's fine, but your example doesn't help me understand my situation better. I'm still curious to know what the optimizer did to my code to make it produce the different results. I guess the printf just prevented the optimization, right?

        The bottom line is, it was bad code and now it's fixed. I used sprintf and atoi to get an exact result regardless of precision issues.

         
    • Anonymous

      Anonymous - 2007-11-05

      >> But now you're talking about different compilers.
      No I am not, I am talking about any compiler. I just happened to have VC++ on the machine I was at. Did you try it in Dev-C++? I did, it behaves the same way as I said it would.

      >> ...and my calculations are not in a different order as written, whereas yours are.
      You missed my point entirely. I was demonstrating that an reordering of a calculation that mathematically has no effect on the result does in fact affect the precision. The point is that the optimiser will re-order calculations is it is more efficient to do so. I was merely forcing a reordering to demonstrate how 'equivalent code' (which is what the optimiser generates) can result in a different result.

      >> I'm still curious to know what the optimizer did to my code to make it produce the different results.
      You would have to look at the generated assembler code to see what it did. I suggest that you try to reproduce teh effect with as sall a piece of code as possible, optimised code is not always easy to follow. The pragmatic approach is to beware that this can legitimately happen and code your FP operations with care. There is not much point is second guessing the optimiser.

      >> I guess the printf just prevented the optimization, right?
      Probably not in itself. But it may have affected how the optimiser behaved because the way in which the variables were used the optimiser may have had to calculate in a different order to have a value ready at a particular location. You have got me curious though, I will take a longer look at your examples.

       
    • Anonymous

      Anonymous - 2007-11-05

      Your example compiles with the following warnings:

      main.cpp:19: warning: too many arguments for format
      main.cpp:29: warning: too many arguments for format
      main.cpp:38: warning: too many arguments for format
      main.cpp: At global scope:
      main.cpp:7: warning: unused parameter 'argc'
      main.cpp:7: warning: unused parameter 'argv'

      Although it does not affect the result. I thought perhaps that it was the merely the act of passing the value as an argument that affected the result, and that seems ot be the case. However it does depend what you call; I am guessing that anything that might be in-lined (because it is in the same module or is a 'built-in' as defined here http://gcc.gnu.org/onlinedocs/gcc-3.4.6/gcc/Other-Builtins.html#Other-Builtins) does not 'correct' the result. I chose ldexp() since it is one of the few standard math functions not built-in and modified the first block as follows:

      printf("\nNo intermediate printf:\n");
      calculated_setpoint=atof(x);
      printf("Before: %.3lf\n",calculated_setpoint);
      calculated_setpoint = 10.00;
      ldexp(calculated_setpoint, 10 ) ; // DUMMY FP CALL
      *
      calculated_setpoint_as_int = (int)calculated_setpoint;
      calculated_setpoint = calculated_setpoint_as_int;
      printf("After: %.3lf\n",calculated_setpoint);
      printf("I get 233.000\n");

      As I suggested earlier it is not the printf() per-se but how the calculated_setpoint value is used within the code that affects how it can be optimised. I admit to not being able to explain exactly why this happens, but suffice it to say that with FP you have to always be careful with your assumptions because in neither case is the compiler doing anything wrong as such.

      Since I mentioned it before, VC++ does not exhibit this behaviour on your code (at least not with defaulr 'release' options).

      Clifford

       
      • Osito

        Osito - 2007-11-05

        Yeah I ignored those compiler warnings. I accidentally left extra items in the printfs but it didn't affect the results. Thanks for taking the time to check it out a little more.

         
    • Anonymous

      Anonymous - 2007-11-04

      1)You don't need the intermediat variable:

      calculated_setpoint *= 10.00;
      calculated_setpoint = (double)((int)calculated_setpoint);

      The cast back to double is not even necessary as it is implicit:

      calculated_setpoint = (int)calculated_setpoint;

      If you prefer there is a function to do just that http://www.cppreference.com/stdmath/floor.html

      calculated_setpoint *= 10.00;
      calculated_setpoint = floor( calculated_setpoint );

      I would not like to guess which is more efficient if there is even any difference, but the use fo floor() is far easier to read.

      2) I'd like to try it, but you have not made it easy enough and the description is confusing and seems to be describing code that is not included. Post a complete test code example - something that compiles without modification and exhibits the problem.

      In general however I think that you are playing fast and loose with floating point. You have to realise that 23.399999999 truncates to 23.3 whereas 23.400000001 truncates to 23.4 but both could be floating point approximations of 23.4. So there is probably nothing "wrong" so to speak, it is simply in the nature of floating point representations. The reordering of calculations that may take place when using optimisation could easilt account for these tiny differences. Your rounding solution simply magnifies teh error.

      I believe that apparent your attempt to limit the number of decimal places to 1 is the result of flawed thinking. There is no guarantee that 23.4 (or any other real number)is precisely representable as a discrete in the floating point value. So with all that manipulation you just end up with something close to (but either side of) 23.4. It is far better to store the value accurately and simply allow the display or string formatting function (in your case sprintf) to display the value in a readable form.

      The alternative is to use 'fixed point' - which is what your calculated_setpoint_as_int represents. Fixed point arithmetic is more predictable than floating point (as it is just integer arithmetic with scaling).

      Clifford

       
      • Osito

        Osito - 2007-11-04

        Thanks Clifford. I looked at floor(), but I guess what I really want is round(). If I have 23.399999999 I would rather it be 23.4 - I guess for some reason I thought casting did rounding but I guess it just does truncating.

        In hindsight, I definitely should have used fixed point. I tried to make it generic so it could handle big numbers and lots of decimal places, but for these calculations everyone uses one decimal place so I should have just stored the setpoint as "tenths of a degree C" in a signed int.

        Here are some interesting (to me at least) examples of this behavior:

        include <stdio.h>

        include <stdlib.h>

        // use -O2 with gcc-4.2.1

        int main(int argc, char *argv[])
        {
        double calculated_setpoint;
        int calculated_setpoint_as_int;
        char x[999] = "23.4";

        printf("\nNo intermediate printf:\n");
        calculated_setpoint=atof(x);
        printf("Before: %.3lf\n",calculated_setpoint);
        calculated_setpoint *= 10.00;
        calculated_setpoint_as_int = (int)calculated_setpoint;
        calculated_setpoint = calculated_setpoint_as_int;
        printf("After: %.3lf\n",calculated_setpoint);
        printf("I get 233.000\n",calculated_setpoint);

        printf("\nWith intermediate printf:\n");
        calculated_setpoint=atof(x);
        printf("Before: %.3lf\n",calculated_setpoint);
        calculated_setpoint *= 10.00;
        printf("During: %.3lf\n",calculated_setpoint);
        calculated_setpoint_as_int = (int)calculated_setpoint;
        calculated_setpoint = calculated_setpoint_as_int;
        printf("After: %.3lf\n",calculated_setpoint);
        printf("I get 234.000\n",calculated_setpoint);

        printf("\nAlternate, no printf but print value10 on Before line:\n");
        calculated_setpoint=atof(x);
        printf("Before: %.3lf\n",calculated_setpoint
        10.0);
        calculated_setpoint *= 10.00;
        calculated_setpoint_as_int = (int)calculated_setpoint;
        calculated_setpoint = calculated_setpoint_as_int;
        printf("After: %.3lf\n",calculated_setpoint);
        printf("I get 234.000\n",calculated_setpoint);

        system("PAUSE");
        return 0;
        }

         
    • Anonymous

      Anonymous - 2007-11-04

      >> I looked at floor(), but I guess what I really want is round().
      Indeed. You can also do rounding as follows:

      calculated_setpoint = (double)((int)(calculated_setpoint + 0.5));

      >> I guess for some reason I thought casting did rounding but I guess it just does truncating.
      Exactly, as defined by the standard.

      >> Here are some interesting (to me at least) examples of this behaviour: ...
      Let that stand then as a good example to others - this catches out almost everyone. I discovered it the hard way while still a student (which is better than looking a professional fool I suppose) programming in BASIC (C was far less common back then, and C++ but a twinkle in Bjarne's green-screen). I'd just completed in a project which I knew to be flawed but could not figure out why - the very next lesson the tutor explained about floating point precision, and it dawned. I had no computer time left to fix it, but I at least had time to annotate the report to explain the flaw and its solution.

      Clifford

       
      • Osito

        Osito - 2007-11-05

        I still think there's something more going on than floating point precision issues. The three examples in my last post do exactly the same calculations on exactly the same starting data, at least according to the code as written. Something must be getting optimized out in one case and not in another to generate the different end results, right? Whether the correct answer is really 234 or 233, I would have expected them all the be the same. I was surprised to see that printing the value out in the middle of the calculation affected the end result.

        What I really need is a signed short containing the rounded value of my double-precision calculations. Would it be appropriate to sprintf the quantity calculated_setpoint*10.0 to a string using %.0lf format, and then use atoi to get the exact value I need in the short? Does sprintf round up and down as needed? For instance, in the case of 23.4, whether it's really 23.3999 or 23.4001 to start with, will the string come out as "234" in both cases, and then atoi will give me what I want?

        BTW, it turns out that round() from math.h is a bad option - the MS compiler doesn't support it, so if you want portability even between Windows compilers you're outta luck. Same with the int16_t suggestion from my last thread - MS doesn't have stdint.h or int16_t so I had to add #ifdef _MSC_VER and provide a typedef for int16_t myself.

         

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.