繁体   English   中英

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

[英]Inconsistent rounding of cast integers from floats in C

由于这个小问题,我整天都在梳头,事实证明这个问题很难解决,而且我很确定有些事情正在发生,而我还没有意识到。 任何反馈或帮助都将受到欢迎。

我有一个函数,它接受一个浮点值,并返回一串ASCII字符以输出到LCD屏幕上。 因此,该函数是:

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;
}

If语句和布尔值的原因是可以将两个浮点数传递给该函数。 一个带有1个小数位,一个带有最多5个小数。我不必大惊小怪的5个小数位,因为我稍后在程序中比较两个浮点数,并且相关的小数似乎是大致一致的。

我的查询源于此行:

 int decimalVal = (x-integerVal)*10;

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

将其增加到100:

 int decimalVal = (x-integerVal)*100;

仅给出偶数的正确值。 (当浮点数为X.2时,X.2很好),但是我使用奇数取整(X.3浮点数打印X.2,X.5-> X.4)等。

当我将其增加到1000时,我开始看到问题的开始:

int decimalVal = (x-integerVal)*1000;

对于偶数,例如X.4,我得到X.40。 使用奇数时,我得到的浮点数将比原始浮点数少0.01。 例如,X5-> X.49。

显然这里有些不对劲,不精确的小数将被截断。 A)我该如何解决? B)给定算术,我猜应该使用* 10,但是* 100是10的数量级,这使我最接近正确的输出。

任何帮助深表感谢。

您可以使用%f为浮点数编写所需的精度。 甚至动态地进行精度级别:

#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;
}

结果:

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打印分数的方法均失败

  1. 分数接近1.0 returnString(0.999999, isTriggTemp)

  2. x值大于INT_MAX

  3. 负数。

一种更可靠的方法是先缩放然后分解为整数/分数部分。

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
   ...

[编辑]

具有前导零的简单方法:

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

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

当然代码可以使用

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

确保函数完成后,缓冲区是static或可用的。

检查退货类型。

您确定此代码不会崩溃吗? 您的缓冲区太小了,我认为您可能会溢出缓冲区。 无论如何,偶数有效且赔率无效的原因可能是因为当您执行诸如int decimalVal =(x-integerVal)* 10000; 事情四舍五入。 例如,int x =(int)((2.29-2)* 10)将给您2,而不是3。在此示例中,解决方法是在乘以10之前加上.05。我确定您可以算出排除一般规则。

侯皮斯先生的答案是最好的选择,但是为了您的兴趣,您可能需要以浮点数的形式进行计算:

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

另外,您对returnString的声明应为char *而不是char。

暂无
暂无

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

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