【问题标题】:Inconsistent rounding of cast integers from floats in CC中浮点数的强制整数舍入不一致
【发布时间】:2016-01-19 18:18:14
【问题描述】:

我一整天都在为这个难以解决的小问题而烦恼,我很确定在表面下发生了一些我不知道的事情。非常欢迎任何反馈或帮助。

我有一个函数,它接受一个浮点值并返回一串 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 语句和 bool 的原因是有两个浮点数可以传递给函数。一个有 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。例如。 X.5 - > X.49。

显然这里有问题,不精确的小数被截断了。 A)我该如何解决这个问题?和 B) 考虑到算术,我猜应该使用 *10 但 *100 是 10 的数量级,它使我最接近正确的输出。

非常感谢任何帮助。

【问题讨论】:

  • 为什么要使用复杂的方法来写出 %f?
  • 另外,您返回的类型错误。
  • 您需要在四舍五入之前添加一半的分辨率。所以如果你要四舍五入到最接近的 0.01,你需要先加上 0.005。
  • 逻辑似乎在努力避免直接解决方案,例如sprintf (buf, "%6.1f", x)。为什么?
  • 我认为这是我被规定的方式,我正在处理的设备接受 char 变量的返回,并且我的写入 LCD 方法可以使用它。我不确定以这种方式返回类型的原因,但这是我被告知的方式,它一直运行良好。这是否是正确的编码实践是另一回事,因此感谢您的反馈。 @GeorgeHoupis 我还将研究仅使用浮点值来简化 sprint() ,我不知道我可以使用具有不同小数位的多个浮点数来做到这一点。

标签: c casting floating-point precision


【解决方案1】:

您可以使用 %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

【讨论】:

  • 可能我不是很清楚,抱歉,这已经是漫长的一天。需要此函数将提供的浮点值转换为 ASCII 字符串。这就是 sprintf 和 itoa 函数调用的原因。虽然您的回答确实有助于理解设置精度,但是在我的问题的上下文中,我很困惑为什么 *10 不是从浮点数中获取正确十进制数的方法。
  • 您的答案对于托管环境来说是可以的,例如一台电脑。在一个典型的裸机嵌入式系统上,通过向printf 添加浮点支持来大量膨胀代码(如果您不需要浮点支持,通常这些库会使用精简的函数)。
  • 使用 IEEE 浮点数并不是无限精度。您不能期望以 100% 的精度修改原始值。如果您想要字符串中的值,您仍然可以使用具有与 printf 完全相同的功能的 sprintf,唯一的区别是第一个参数(即目标缓冲区)。
  • @James:请参阅printf 系列的联机帮助页和格式字符串的语法。但也请参阅我的其他评论。
  • @Olaf 谢谢,但系统需要使用浮点数,所以使用这些库是必要的。 GeorgeHopis,我是否理解正确,然后我需要某种方式来四舍五入这些值?我不知道浮点数并不像我想象的那么精确,尤其是对于少量小数位
【解决方案2】:

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

【讨论】:

  • 这个答案确实为我解决了这个问题(非常感谢!),但是关于正确的编码实践或更好的方法,上面提出了许多有效的观点,所以我将改变我实现功能目的的方式。不过再次感谢您的简洁回复!
【解决方案3】:

在各种情况下打印分数的 OP 方法都会失败

  1. 分数接近1.0returnString(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 或在函数完成后以其他方式可用。

检查返回类型。

【讨论】:

    【解决方案4】:

    先生。 Houpis 的答案是最好的追求,但为了您的启迪,您可能需要将计算作为浮点数进行:

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

    另外,returnString 的声明应该是 char* 而不是 char。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-23
      • 1970-01-01
      • 2021-03-02
      • 2012-01-02
      • 2021-10-09
      • 1970-01-01
      • 1970-01-01
      • 2015-09-07
      相关资源
      最近更新 更多