浮点值未正确存储
Float value not being stored correctly
我正在编写一个函数,假设将浮点数 "length" 传递给它,然后显示一个类似输出的计时器。浮动 "length" 意味着以分钟为单位。
当我运行它时,输出应该是:02:03:27:00。相反,它显示 02:03:26:100,虽然在技术上是正确的,但它是 A) 不是它应该如何显示和 B) 表明某处存在可能在未来导致不良结果的错误。
在使用计算器手动计算并发现所有数学计算都是合理的之后。然后我注释掉格式化零的部分,看看是否是导致错误的原因,问题仍然存在。然后我在每次计算后都输入了 printf,发现虽然 "length" 设置为 123.45,但它被存储为 123.449997?
我不知道它是如何做到这一点的。而且因为我不知道这是怎么发生的,所以我无法为它编写可靠的修复程序。
int main()
{
float length;
float working;
int hour;
int min;
int sec;
int centi_sec;
char hzero[2];
char mzero[2];
char szero[2];
char czero[2];
length=123.45;
working=floor(length);
working=(length-working)*60;
sec=floor(working);
working-=floor(working);
centi_sec=(working*100)+.5;
working=floor(length);
hour=floor((working/60));
min=working-(hour*60);
if(hour<10){
hzero[0]='0';
hzero[1]="";
}
else if(hour==0){
hzero[0]='0';
hzero[1]='0';
}
else{
hzero[0]="";
hzero[1]="";
}
if(min<10){
mzero[0]='0';
mzero[1]="";
}
else if(min==0){
mzero[0]='0';
mzero[1]='0';
}
else{
mzero[0]="";
mzero[1]="";
}
if(sec<10){
szero[0]='0';
szero[1]="";
}
else if(sec==0){
szero[0]='0';
szero[1]='0';
}
else{
szero[0]="";
szero[1]="";
}
if(centi_sec<10){
czero[0]='0';
czero[1]="";
}
else if(centi_sec==0){
czero[0]='0';
czero[1]='0';
}
else{
czero[0]="";
czero[1]="";
}
printf("%s%d:%s%d:%s%d:%s%d\n", hzero, hour, mzero, min, szero, sec, czero, centi_sec);
system("pause");
}
我还写了一个简短的程序,只是为了弥补这个问题并没有受到我错过的完整程序中某些东西的影响,而且它也有同样的问题。
int main()
{
float length=123.45;
printf("%f\n", length);
system("pause");
}
P.S。当我使用 printf 解决问题时,我发现 printf 弄乱了零的格式。不是太大的问题,因为当我删除它们时,格式又回到了它应该的样子。 printf 会弄乱格式仍然没有任何意义。如果有人也能对此提供答案,我们将不胜感激。
提前致谢。
您的代码中的问题与浮点精度无关,精度有限制,并且由于舍入运算会引入错误,但是对于两位小数应该没有问题,而且确实如此'如果在内部将值打印为 123.449997
并不重要,如果您这样做
printf("%.2f\n", 123.449997);
123.45
将被打印,并且对于涉及该值的任何算术运算,结果将是正确的,具有足够的小数位精度,2.
你遇到的最重要的问题是你的字符串不能是那样的字符串,因为没有空间来终止 '[=16=]'
。
并且数学也是错误的,因为如果 centi_sec
大于或等于 100
那么应该增加一秒并且 centi_sec
应该减去 100
,同样适用于 sec
和 min
,依此类推。
这些
char hzero[2];
char mzero[2];
char szero[2];
char czero[2];
应该阅读
char hzero[3];
char mzero[3];
char szero[3];
char czero[3];
你也不应该重复自己,使用函数
#include <math.h>
#include <stdio.h>
void zeropad(int value, char str[3])
{
if (value < 10)
{
str[0] = '0';
str[1] = value + '0';
}
else
{
str[0] = (value - value % 10) / 10 + '0';
str[1] = value % 10 + '0';
}
str[2] = '[=13=]';
}
int main()
{
float length;
float working;
int hour;
int min;
int sec;
int centi_sec;
char hzero[3];
char mzero[3];
char szero[3];
char czero[3];
length = 123.45;
working = floor(length);
working = (length - working) * 60;
sec = floor(working);
working -= floor(working);
centi_sec = (working * 100) + .5;
working = floor(length);
hour = floor(working / 60);
min = working - (hour * 60);
if (centi_sec >= 100)
{
sec += 1;
centi_sec -= 100;
}
if (sec >= 60)
{
min += 1;
sec -= 60;
}
if (min >= 60)
{
hour += 1;
min -= 60;
}
zeropad(hour, hzero);
zeropad(min, mzero);
zeropad(sec, szero);
zeropad(centi_sec, czero);
printf("%s:%s:%s:%s\n", hzero, mzero, szero, czero);
}
浮点数的准确性有限,并非所有数字都可以用浮点变量精确表示。
我的第一个建议是传递一个包含 4 个整数字段的小结构
int hours
int minutes
int seconds
int fractionSecondX100
这样就可以避免整个问题集都用浮点数了。
但是,如果您必须使用浮点值,那么 google:“如何在 C 中处理浮点值”,这将 return 很多 'hits' 在 google 之后,阅读几个参考网页,以便了解如何处理浮动并了解预期结果。
出于数学原因,小数的二进制表示在所有情况下都不是精确的。您可以阅读更多相关信息 here
为避免此问题,您需要将最小单位的一半添加到输入中。
在这种情况下,这将是 1.0/60/100/2
。
length = 123.45;
const float epsilon = 1.0/60/100/2;
length += epsilon;
您曾尝试过与
类似的事情
centi_sec=(working*100)+.5;
但这只对 centi_sec
有影响,对其他数字没有影响。改回
centi_sec=(working*100);
还请按照@iharob 的建议更改数组大小。
编辑:您可以完全避免数组:
#include <math.h>
#include <stdio.h>
int main()
{
const float epsilon = 1.0/60/100/2;
float length;
float working;
int hour;
int min;
int sec;
int centi_sec;
length=123.45;
length += epsilon;
working=floor(length);
working=(length-working)*60;
sec=floor(working);
working-=floor(working);
centi_sec=(working*100);
working=floor(length);
hour=floor((working/60));
min=working-(hour*60);
printf("%02d:%02d:%02d:%02d\n", hour, min, sec, centi_sec);
// system("pause");
}
这有效。
您已经分配了一个 二进制浮点 值和十进制实数值。 二进制浮点数不能准确表示所有实数十进制值。
单精度二进制浮点数有利于精确表示约6位十进制有效数字,123.449997为9位;所以你已经超过了承诺的精度。默认情况下,%f 格式说明符显示 6 位小数,但在这种情况下超出了可用精度。
要么使用能够以合理精度显示的格式说明符:
printf( "%.3f\n", length ) ;
或使用 double
这对 15 位有效数字有效。
有几个理由不在具有高内存带宽和硬件浮点单元(即所有现代台式计算机)的目标上使用双精度。如果您正在处理真正大量的数据并且需要减少处理时间并且不需要精度,则单精度很有用。
我正在编写一个函数,假设将浮点数 "length" 传递给它,然后显示一个类似输出的计时器。浮动 "length" 意味着以分钟为单位。
当我运行它时,输出应该是:02:03:27:00。相反,它显示 02:03:26:100,虽然在技术上是正确的,但它是 A) 不是它应该如何显示和 B) 表明某处存在可能在未来导致不良结果的错误。
在使用计算器手动计算并发现所有数学计算都是合理的之后。然后我注释掉格式化零的部分,看看是否是导致错误的原因,问题仍然存在。然后我在每次计算后都输入了 printf,发现虽然 "length" 设置为 123.45,但它被存储为 123.449997?
我不知道它是如何做到这一点的。而且因为我不知道这是怎么发生的,所以我无法为它编写可靠的修复程序。
int main()
{
float length;
float working;
int hour;
int min;
int sec;
int centi_sec;
char hzero[2];
char mzero[2];
char szero[2];
char czero[2];
length=123.45;
working=floor(length);
working=(length-working)*60;
sec=floor(working);
working-=floor(working);
centi_sec=(working*100)+.5;
working=floor(length);
hour=floor((working/60));
min=working-(hour*60);
if(hour<10){
hzero[0]='0';
hzero[1]="";
}
else if(hour==0){
hzero[0]='0';
hzero[1]='0';
}
else{
hzero[0]="";
hzero[1]="";
}
if(min<10){
mzero[0]='0';
mzero[1]="";
}
else if(min==0){
mzero[0]='0';
mzero[1]='0';
}
else{
mzero[0]="";
mzero[1]="";
}
if(sec<10){
szero[0]='0';
szero[1]="";
}
else if(sec==0){
szero[0]='0';
szero[1]='0';
}
else{
szero[0]="";
szero[1]="";
}
if(centi_sec<10){
czero[0]='0';
czero[1]="";
}
else if(centi_sec==0){
czero[0]='0';
czero[1]='0';
}
else{
czero[0]="";
czero[1]="";
}
printf("%s%d:%s%d:%s%d:%s%d\n", hzero, hour, mzero, min, szero, sec, czero, centi_sec);
system("pause");
}
我还写了一个简短的程序,只是为了弥补这个问题并没有受到我错过的完整程序中某些东西的影响,而且它也有同样的问题。
int main()
{
float length=123.45;
printf("%f\n", length);
system("pause");
}
P.S。当我使用 printf 解决问题时,我发现 printf 弄乱了零的格式。不是太大的问题,因为当我删除它们时,格式又回到了它应该的样子。 printf 会弄乱格式仍然没有任何意义。如果有人也能对此提供答案,我们将不胜感激。
提前致谢。
您的代码中的问题与浮点精度无关,精度有限制,并且由于舍入运算会引入错误,但是对于两位小数应该没有问题,而且确实如此'如果在内部将值打印为 123.449997
并不重要,如果您这样做
printf("%.2f\n", 123.449997);
123.45
将被打印,并且对于涉及该值的任何算术运算,结果将是正确的,具有足够的小数位精度,2.
你遇到的最重要的问题是你的字符串不能是那样的字符串,因为没有空间来终止 '[=16=]'
。
并且数学也是错误的,因为如果 centi_sec
大于或等于 100
那么应该增加一秒并且 centi_sec
应该减去 100
,同样适用于 sec
和 min
,依此类推。
这些
char hzero[2];
char mzero[2];
char szero[2];
char czero[2];
应该阅读
char hzero[3];
char mzero[3];
char szero[3];
char czero[3];
你也不应该重复自己,使用函数
#include <math.h>
#include <stdio.h>
void zeropad(int value, char str[3])
{
if (value < 10)
{
str[0] = '0';
str[1] = value + '0';
}
else
{
str[0] = (value - value % 10) / 10 + '0';
str[1] = value % 10 + '0';
}
str[2] = '[=13=]';
}
int main()
{
float length;
float working;
int hour;
int min;
int sec;
int centi_sec;
char hzero[3];
char mzero[3];
char szero[3];
char czero[3];
length = 123.45;
working = floor(length);
working = (length - working) * 60;
sec = floor(working);
working -= floor(working);
centi_sec = (working * 100) + .5;
working = floor(length);
hour = floor(working / 60);
min = working - (hour * 60);
if (centi_sec >= 100)
{
sec += 1;
centi_sec -= 100;
}
if (sec >= 60)
{
min += 1;
sec -= 60;
}
if (min >= 60)
{
hour += 1;
min -= 60;
}
zeropad(hour, hzero);
zeropad(min, mzero);
zeropad(sec, szero);
zeropad(centi_sec, czero);
printf("%s:%s:%s:%s\n", hzero, mzero, szero, czero);
}
浮点数的准确性有限,并非所有数字都可以用浮点变量精确表示。
我的第一个建议是传递一个包含 4 个整数字段的小结构
int hours
int minutes
int seconds
int fractionSecondX100
这样就可以避免整个问题集都用浮点数了。
但是,如果您必须使用浮点值,那么 google:“如何在 C 中处理浮点值”,这将 return 很多 'hits' 在 google 之后,阅读几个参考网页,以便了解如何处理浮动并了解预期结果。
出于数学原因,小数的二进制表示在所有情况下都不是精确的。您可以阅读更多相关信息 here
为避免此问题,您需要将最小单位的一半添加到输入中。
在这种情况下,这将是 1.0/60/100/2
。
length = 123.45;
const float epsilon = 1.0/60/100/2;
length += epsilon;
您曾尝试过与
类似的事情centi_sec=(working*100)+.5;
但这只对 centi_sec
有影响,对其他数字没有影响。改回
centi_sec=(working*100);
还请按照@iharob 的建议更改数组大小。
编辑:您可以完全避免数组:
#include <math.h>
#include <stdio.h>
int main()
{
const float epsilon = 1.0/60/100/2;
float length;
float working;
int hour;
int min;
int sec;
int centi_sec;
length=123.45;
length += epsilon;
working=floor(length);
working=(length-working)*60;
sec=floor(working);
working-=floor(working);
centi_sec=(working*100);
working=floor(length);
hour=floor((working/60));
min=working-(hour*60);
printf("%02d:%02d:%02d:%02d\n", hour, min, sec, centi_sec);
// system("pause");
}
这有效。
您已经分配了一个 二进制浮点 值和十进制实数值。 二进制浮点数不能准确表示所有实数十进制值。
单精度二进制浮点数有利于精确表示约6位十进制有效数字,123.449997为9位;所以你已经超过了承诺的精度。默认情况下,%f 格式说明符显示 6 位小数,但在这种情况下超出了可用精度。
要么使用能够以合理精度显示的格式说明符:
printf( "%.3f\n", length ) ;
或使用 double
这对 15 位有效数字有效。
有几个理由不在具有高内存带宽和硬件浮点单元(即所有现代台式计算机)的目标上使用双精度。如果您正在处理真正大量的数据并且需要减少处理时间并且不需要精度,则单精度很有用。