简体   繁体   中英

Inconsistent rounding of cast integers from floats in C

Been tearing my hair out all day over this little problem I have that is proving hard to fix and I'm fairly sure there are things going on under the surface that I am not aware of. Any feedback or help is hugely welcomed.

I have a function which takes in a float value and returns a string of ASCII characters for output onto an LCD screen. The function is thus:

char returnString(float x, bool isTriggTemp)
{
    char numberString[3];
    char bufferString[2];
    char bufferStringDec[7];

    int integerVal = (int)x;

    if(isTriggTemp == false)
    {
        int decimalVal = (x-integerVal)*10000;
        sprintf(numberString, "%s.%s", itoa(bufferString,integerVal,10),
             itoa(bufferStringDec,decimalVal,10));
    }
    else
    {
        int decimalVal = (x-integerVal)*1000; 

        sprintf(numberString, "%s.%s", itoa(bufferString,integerVal,10),
             itoa(bufferStringDec,decimalVal,10));
    }

    return numberString;
}

The reason for the If statement and the bool is that there are two floats that can be passed to the function. One with 1 decimal place, and one with up to 5. I'm not to fussed about the 5 decimal place number as I am comparing the two floats later in the program and the relevant decimals seem roughly consistent.

My query stems from this line:

 int decimalVal = (x-integerVal)*10;

When used like this, which is what I would expect to have to use given the above logic, the outputted string reads "X.0" regardless of the X value.

Increasing it to 100:

 int decimalVal = (x-integerVal)*100;

Gives me the correct value for only even numbers. (X.2 is fine when the float is X.2), but with odd numbers I seem to get a rounded down version (X.3 float prints X.2, X.5 -> X.4) etc.

When I increase it to 1000, I begin to see the start of the problem:

int decimalVal = (x-integerVal)*1000;

For even values, X.4 for example, I get X.40 printed. And with odd numbers I get 0.01 less than the original float. EgX5 - > X.49.

Obviously there's something amiss here and non-exact decimals are being cut off. A) how can I fix this? and B) Given the arithmetic I would have guessed that *10 should be used but *100 is the order of 10 that gives me the closest to the correct output.

Any help is much appreciated.

You can write the precision you want for floating-point numbers with %f. And even do the precision level dynamically:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// Use malloc (drawback: need to track return and deallocate to prevent memory leak.)
char * returnString( float x, int isTriggTemp )
{
    char * buffer = malloc( 128 );
    if( buffer )
        sprintf( buffer, "%0.*f", isTriggTemp ? 4 : 5, x );
    return buffer;
}

// Use static buffer (drawback: non-reentrant)
char * returnStringStatic( float x, int isTriggTemp )
{
    static char buffer[128];
    sprintf( buffer, "%0.*f", isTriggTemp ? 4 : 5, x );
    return buffer;
}

// Use given buffer (drawback: caller needs to be aware of buffer size needs and additional parameters are involved)
char * returnStringGivenBuffer( char * buffer, float x, int isTriggTemp )
{
    sprintf( buffer, "%0.*f", isTriggTemp ? 4 : 5, x );
    return buffer;
}

int main( int argc, char ** argv )
{
    float val = 3.14159;
    int highprecision = 0;

    printf( "Using sprintf\n" );
    printf( "%0.1f\n", val );
    printf( "%0.5f\n", val );
    printf( "%0.*f\n", ( highprecision ? 5 : 1 ), val );
    highprecision = 1;
    printf( "%0.*f\n", ( highprecision ? 5 : 1 ), val );

    printf( "\nUsing sprintf\n" );
    char buffer[128];
    sprintf( buffer, "%0.1f", val );
    printf( "%s\n", buffer );
    sprintf( buffer, "%0.5f", val );
    printf( "%s\n", buffer );
    sprintf( buffer, "%0.*f", ( highprecision ? 5 : 1 ), val );
    printf( "%s\n", buffer );
    highprecision = 1;
    sprintf( buffer, "%0.*f", ( highprecision ? 5 : 1 ), val );
    printf( "%s\n", buffer );

    printf( "\nUsing dynamic allocation\n" );
    char * fval = returnString( val, 0 );
    printf( "%s\n", fval ? fval : "" );
    if( fval ) free( fval );
    fval = returnString( val, 1 );
    printf( "%s\n", fval ? fval : "" );
    if( fval ) free( fval );

    printf( "\nUsing static buffer\n" );
    char * ptr = returnStringStatic( val, 0 );
    printf( "%s\n", ptr );
    ptr = returnStringStatic( val, 1 );
    printf( "%s\n", ptr );

    printf( "\nUsing given buffer\n" );
    ptr = returnStringGivenBuffer( buffer, val, 0 );
    printf( "%s\n", ptr );
    ptr = returnStringGivenBuffer( buffer, val, 1 );
    printf( "%s\n", ptr );

    return 0;
}

Results:

Using sprintf
3.1
3.14159
3.1
3.14159

Using sprintf
3.1
3.14159
3.14159
3.14159

Using dynamic allocation
3.14159
3.1416

Using static buffer
3.14159
3.1416

Using given buffer
3.14159
3.1416

OP approach to printing a fraction fails under various situations

  1. fraction is close to 1.0 . returnString(0.999999, isTriggTemp)

  2. x value greater than INT_MAX .

  3. Negative numbers.

A more reliable method to to scale first and then break into integer/fraction parts.

char *returnString(float x, bool isTriggTemp) {
   float scale = 10000;
   x = roundf(x * scale);
   float ipart = x/scale;
   float dpart = fmodf(fabsf(x), scale);
   itoa(bufferString,ipart,10);
   itoa(bufferStringDec,dpart,10);  // May need to add leading zeros
   ...

[Edit]

An easy way to have leading zeros:

   static char bufferString[20+7];
   char bufferStringDec[7];

   itoa(bufferStringDec,dpart + scale,10);
   bufferStringDec[0] = '.';  // Overwrite leading `1`.
   return strcat(bufferStringDec, bufferStringDec);

Of course code could use

void returnString(char *numberString, size_t size, float x, bool isTriggTemp) {
  snprintf(numberString, sizeof numberString, "%.*f", isTriggTemp ? 3 : 4, x);
}

Insure buffers are static or otherwise available after the function completes.

Check return type.

Are you sure this code doesn't crash? Your buffers are so small I think you might be overflowing your buffers. Anyway, the reason evens work and odds don't may be because when you do things like int decimalVal = (x-integerVal)*10000; things get rounded down. For example, int x = (int)((2.29 - 2)*10) will give you 2, not 3. In this example, the fix is to add .05 before you multiply by 10. I'm sure you can figure out the general rule.

Mr. Houpis answer is the best thing to pursue, but for your edification, you probably need to do the calculations as floats:

int decimalVal = (x - (float) integerVal) * 10000.0;

In addition, your declaration for returnString should be char* rather than char.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM