从 C 中的浮点数转换整数的不一致舍入
Inconsistent rounding of cast integers from floats in C
我一整天都在为这个小问题焦头烂额,事实证明我很难解决这个问题,而且我相当确定在表面之下发生了一些我不知道的事情。非常欢迎任何反馈或帮助。
我有一个函数,它接受一个浮点值和 returns 一串 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 为 floating-point 数字编写您想要的精度。甚至动态地进行精度级别:
#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
先生Houpis 答案是最好的追求,但为了您的启迪,您可能需要将计算作为浮点数:
int decimalVal = (x - (float) integerVal) * 10000.0;
此外,您对 returnString 的声明应该是 char* 而不是 char。
OP 打印分数的方法在各种情况下都失败了
分数接近1.0
。 returnString(0.999999, isTriggTemp)
x
值大于 INT_MAX
.
负数。
一种更可靠的方法,先缩放然后分解成 integer/fraction 个部分。
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
或函数完成后可用。
检查 return 类型。
你确定这段代码不会崩溃吗?您的缓冲区太小了,我认为您的缓冲区可能溢出了。无论如何,偶数起作用而赔率不起作用的原因可能是因为当您执行诸如 int decimalVal = (x-integerVal)*10000; 之类的操作时。事情变得四舍五入。例如, int x = (int)((2.29 - 2)*10) 会给你 2,而不是 3。在这个例子中,解决方法是在乘以 10 之前添加 .05。我相信你可以计算出一般规则。
我一整天都在为这个小问题焦头烂额,事实证明我很难解决这个问题,而且我相当确定在表面之下发生了一些我不知道的事情。非常欢迎任何反馈或帮助。
我有一个函数,它接受一个浮点值和 returns 一串 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 为 floating-point 数字编写您想要的精度。甚至动态地进行精度级别:
#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
先生Houpis 答案是最好的追求,但为了您的启迪,您可能需要将计算作为浮点数:
int decimalVal = (x - (float) integerVal) * 10000.0;
此外,您对 returnString 的声明应该是 char* 而不是 char。
OP 打印分数的方法在各种情况下都失败了
分数接近
1.0
。returnString(0.999999, isTriggTemp)
x
值大于INT_MAX
.负数。
一种更可靠的方法,先缩放然后分解成 integer/fraction 个部分。
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
或函数完成后可用。
检查 return 类型。
你确定这段代码不会崩溃吗?您的缓冲区太小了,我认为您的缓冲区可能溢出了。无论如何,偶数起作用而赔率不起作用的原因可能是因为当您执行诸如 int decimalVal = (x-integerVal)*10000; 之类的操作时。事情变得四舍五入。例如, int x = (int)((2.29 - 2)*10) 会给你 2,而不是 3。在这个例子中,解决方法是在乘以 10 之前添加 .05。我相信你可以计算出一般规则。