簡體   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