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。例如 X.5 -> 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 sprintfn" );
    printf( "%0.1fn", val );
    printf( "%0.5fn", val );
    printf( "%0.*fn", ( highprecision ? 5 : 1 ), val );
    highprecision = 1;
    printf( "%0.*fn", ( highprecision ? 5 : 1 ), val );
    printf( "nUsing sprintfn" );
    char buffer[128];
    sprintf( buffer, "%0.1f", val );
    printf( "%sn", buffer );
    sprintf( buffer, "%0.5f", val );
    printf( "%sn", buffer );
    sprintf( buffer, "%0.*f", ( highprecision ? 5 : 1 ), val );
    printf( "%sn", buffer );
    highprecision = 1;
    sprintf( buffer, "%0.*f", ( highprecision ? 5 : 1 ), val );
    printf( "%sn", buffer );
    printf( "nUsing dynamic allocationn" );
    char * fval = returnString( val, 0 );
    printf( "%sn", fval ? fval : "" );
    if( fval ) free( fval );
    fval = returnString( val, 1 );
    printf( "%sn", fval ? fval : "" );
    if( fval ) free( fval );
    printf( "nUsing static buffern" );
    char * ptr = returnStringStatic( val, 0 );
    printf( "%sn", ptr );
    ptr = returnStringStatic( val, 1 );
    printf( "%sn", ptr );
    printf( "nUsing given buffern" );
    ptr = returnStringGivenBuffer( buffer, val, 0 );
    printf( "%sn", ptr );
    ptr = returnStringGivenBuffer( buffer, val, 1 );
    printf( "%sn", 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

你确定这段代码不会崩溃吗?您的缓冲区太小了,我认为您可能会溢出缓冲区。无论如何,偶数有效和赔率不起作用的原因可能是因为当您执行诸如 int 十进制Val = (x-integerVal)*10000 之类的事情时;事情四舍五入。例如,int x = (int)((2.29 - 2)*10) 会给你 2,而不是 3。在此示例中,解决方法是在乘以 10 之前添加 .05。我相信你能弄清楚一般规则。

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可用或可用。

检查返回类型。

Houpis 先生的回答是最好的追求,但为了你的启发,你可能需要将计算作为浮点数:

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

此外,返回字符串的声明应该是 char* 而不是 char。

最新更新