简体   繁体   English

从C中的浮点数转换整数的舍入不一致

[英]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. 我有一个函数,它接受一个浮点值,并返回一串ASCII字符以输出到LCD屏幕上。 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. If语句和布尔值的原因是可以将两个浮点数传递给该函数。 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. 一个带有1个小数位,一个带有最多5个小数。我不必大惊小怪的5个小数位,因为我稍后在程序中比较两个浮点数,并且相关的小数似乎是大致一致的。

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. 像这样使用时(根据上述逻辑,这是我期望使用的),无论X值如何,输出的字符串都将读取“ X.0”。

Increasing it to 100: 将其增加到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. (当浮点数为X.2时,X.2很好),但是我使用奇数取整(X.3浮点数打印X.2,X.5-> X.4)等。

When I increase it to 1000, I begin to see the start of the problem: 当我将其增加到1000时,我开始看到问题的开始:

int decimalVal = (x-integerVal)*1000;

For even values, X.4 for example, I get X.40 printed. 对于偶数,例如X.4,我得到X.40。 And with odd numbers I get 0.01 less than the original float. 使用奇数时,我得到的浮点数将比原始浮点数少0.01。 EgX5 - > X.49. 例如,X5-> X.49。

Obviously there's something amiss here and non-exact decimals are being cut off. 显然这里有些不对劲,不精确的小数将被截断。 A) how can I fix this? A)我该如何解决? 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. B)给定算术,我猜应该使用* 10,但是* 100是10的数量级,这使我最接近正确的输出。

Any help is much appreciated. 任何帮助深表感谢。

You can write the precision you want for floating-point numbers with %f. 您可以使用%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 在各种情况下,OP打印分数的方法均失败

  1. fraction is close to 1.0 . 分数接近1.0 returnString(0.999999, isTriggTemp)

  2. x value greater than INT_MAX . x值大于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. 确保函数完成后,缓冲区是static或可用的。

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; 无论如何,偶数有效且赔率无效的原因可能是因为当您执行诸如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. 例如,int x =(int)((2.29-2)* 10)将给您2,而不是3。在此示例中,解决方法是在乘以10之前加上.05。我确定您可以算出排除一般规则。

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. 另外,您对returnString的声明应为char *而不是char。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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