This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Rounding Floats



I would like to round a floating points to various decimal places. What a pain in the neck. Here is what I did. I had tried many algorithms but they used arithmetic and produced bad results. I resorted to converting to a string. 2 questions:

- is there a better way to round a floating point number?

- I had used a %f in the sprint and it caused an invalid instruction trap. What's up with that?

double db_round(double value, int nDecimalPlaces)
{
  char buf[16];
  Uint16 u16Size;
  /* find out number of digits to left of decimal. */
  sprintf(buf, "%d",  (Uint32)value);
  u16Size = strlen(buf);
  sprintf(buf, "%.*g", nDecimalPlaces+u16Size, value);
  return atof(buf);
}

  • Charles,

    How about using the modf() function.  Suppose you wanted to round to 3 decimal places (note: 10^3 =1000):

    #include <math.h>
    float x=34.67853, y;
    modf(x*1000+0.5, (double *)&y);
    y = y/1000;       // y = 34.679

    Rather than hard-coding the 1000, you could write a simple loop to compute the scaling value based on a passed parameter n.  Something like this:

    scale = 1;
    for(i=0; i<n; i++)  scale = scale * 10;

    You can work out the details here.  You can also create a seperate rounding function for each 'n'.  This trades off code size for execution speed.

    I don't know if this will be more efficient than your method.  It might be.  sprintf() is cycle hungry.

    So far as %f support in sprintf() goes, I suspect it is missing.  I tried it too and it doesn't work.  Tried printf() as well.  All the C I/O functions use the same low-level function.  I've asked the compiler guys about it.  If I hear anything I will post.

    Regards,

    David

     

  • On the %f support in C I/O (e.g., printf, sprintf), I got some suggestions from the compiler people and ran more tests.  I know what is going on.

    The root cause of the problem is insufficient stack size.  I had my stack sized to 0x400 words for my test code.  Turned out I needed 0x04DB of stack.  What happened was the stack overflowed during printf() execution and corrupted the code/data section that immediately followed it in memory.  This in turn led to the code crashing.  Apparently %f eats up a lot more stack than say %d format specifier.

    I increased the stack size, and the %f worked.  Note that 0x4DB was the amount of stack used in my test code.  I wouldn't cut it too tight.  Try 0x1000 to get started (if you can), and work your way down.  When determining the needed stack size, fill the stack with a value (e.g., 0xDEAD) before running your code so you can see how much is used (inspect using a memory window in CCS after running the code.  Inspect the memory carefully.  I noticed the stack usage is not contiguous.  You can have a large unused block in the middle and then higher up a block of stack that is used.  Why I don't know exactly.  Maybe some sort of memory alignment thing with the C I/O).  Reason to size the stack is that if nothing follows the stack immediately in memory, the stack may overflow and you wouldn't know it (if there is valid RAM following the stack).  Then in the future you move things around in memory and the crash problem comes back.  You don't want this.  So, size the stack up now so you know how large it needs to be.

    Oh, incidentally, C I/O functions on embedded processors are ugly!  They eat up stack.  This is why you don't use 'em.

    Regards,

    David

  • This worked great!!

    Next problem, going from a float/double to an int. lets say you got a number 1000500.0, casting it to an Uint32, I get 1000499.

    I'm using

    x >= 0 ? (long)(x+0.5) : (long)(x-0.5)

    Is this the appropriate way to do this?

  • Charles,

    It looks like you're trying to round at the same time you're converting to integer?  Otherwise, why are you adding in the 0.5?  I tested this using c2000 cgtools v6.2.7:

    float32 x1=1000500.0;
    float32 x2=-1000500.0;
    int32 y1, y2;
    y1 = (x1 >= 0)? (long)(x1+0.5) : (long)(x1-0.5);
    y2 = (x2 >= 0)? (long)(x2+0.5) : (long)(x2-0.5);

    I get y1=1000500, and y2=-1000500.

    I ran this on F28069 device, with floating point enabled (HW floating point), and floating point disabled (uses software library).  I get the same results in both cases.

    Regards,

    David

     

     

  • Have you tried

    y1 = (long)x1;

    or

    y2 = (long)x2;

    y1 would be 100499 and y2 would be -100499 and not the desired result.

  • charles saperstein said:

    Have you tried

    y1 = (long)x1;

    or

    y2 = (long)x2;


    y1 would be 100499 and y2 would be -100499 and not the desired result.


     
    Yes, I did actually try that first.  It gives me the same results as with rounding.
     
    float32 x1=1000500.0;
    float32 x2=-1000500.0;
    int32 y1, y2;
    y1 = (long)(x1);
    y2 = (long)(x2);
     
     
    Floating point is not an exact system.  As numbers get larger, you loose precision (defined as the distance between a number and the next number up or down).  Adding 0.5 is not always going to do anything for you.  For example:
     
    volatile float32 z;
    z = 1.0E10;
    z = z + 0.5;
     
    The IEEE representation of 1.0E10 is 0x501502F9 (we'll see below that this is actually slightly less than 1.0E10, because 1.0E10 cannot be exactly represented with IEEE single-precision floating point).  You then add 0.5 to it, and you still have 0x501502F9 (try it on C2000 hardware).  That is because 0.5 is less than the precision that exists at 1.0E10.  If you expand the 0x501502F9 in IEEE single-precision format (1 sign, 8 exponent, 23 fraction, from left to right), you get s=0, exp=160, and frac  =1377017/2^23 = 0.164153218.
     
    Now the IEEE single-precision floating point number is:
     
    v=[(-1)^s]*[2^(e-127)]*(1.f)
     
     = [(-1)^0]*[2^(160-127)*(1.164153218)
     
     = 9,999,999,998 = 0.9999999998E10
     
    and the next number up is 0x501502FA, which is 1.000000103e10.
     
    Note that even the above calculations are off little bit because my calculator has finite precision.  We could calculate the IEEE precision at this value as (2^e)*(2-23) = 2^(e-23) = 2^10 = 1024.  So assuming you round up, if you add anything less than 1024/2 to 1.0E10, you're going to end up with 1.0E10.  The difference between the two numbers I computed above is 1032.  That is due to finite precision in my calculator.
     
    Point is that even adding 0.5 does not guarantee a rounding across all possible single-precision floating point values.  You're dealing with binary numbers here.  Decimal doesn't translate evenly.  If humans had 8 fingers on each hand, we wouldn't be in this bind now!
     
    Regards,
    David