atof 的奇怪行为
Strange behavior with atof
我有一个 Arduino,它从我的智能手机(通过蓝牙)接收一条包含 unix 时间戳的消息。现在,我正在尝试将我的 DS1307 与该时间戳同步。
但是,它不起作用,因此,我开始搜索并在将包含时间戳的 C 样式数组转换为浮点数时发现了一些奇怪的行为。
// Copy the time into "timeBuff"
int timeLength = siz - _CAT_LENGTH_;
char timeBuff[timeLength+1];
memcpy(timeBuff, &msg[_CAT_LENGTH_], timeLength);
timeBuff[timeLength] = '[=10=]';
// For debugging
Serial.print(F("timeBuff: "));
Serial.println(timeBuff);
// Convert timeBuff string into a variable of type long
float deviceTime = atof(timeBuff);
// Now, deviceTime != what we printed above
Serial.print(F("Epoch time: "));
Serial.println(deviceTime);
前 5 行将消息的右侧部分复制到字符数组中,并添加终止符零。
之后,我们打印timeBuff
的内容,并将其转换为float
存储在deviceTime
中。最后,我们打印 deviceTime
.
这是我第一次测试的结果
timeBuff: 1476113620
Epoch time: 1476113664.00
这是第二次测试
timeBuff: 1476115510
Epoch time: 1476115456.00
为什么atof
的结果和我们传给它的字符串不一样?
在大多数平台上 float
以 IEEE-754 单精度格式表示,这是具有 24 位 (23+1) 尾数的 32 位浮点格式。它根本没有足够的精度来表示您的数字。您的数字需要大约 32 位来表示。当存储在 float
.
中时,大于 24 位的整数值通常会丢失精度
精度损失将表现为尾部比特丢失,最后剩余的比特四舍五入
1476113620 -> 01010111111110111011010011010100
1476113664 -> 01010111111110111011010100000000
这是一个非常简单的程序,它更清楚地说明了同样的问题,去掉了涉及时间戳或 atof()
的任何复杂情况,结果证明它们是无关紧要的:
#include <stdio.h>
int main()
{
float f = 1234567890.;
double d = 1234567890.;
printf("f = %f\n", f);
printf("d = %f\n", d);
return 0;
}
我鼓励您编译这个小程序并 运行 它。当我这样做时,我看到:
f = 1234567936.000000
d = 1234567890.000000
起初这似乎是不可能的。看起来它一定是编译器或 printf
中的一个错误,或者其他什么。如果我将简单的数字“1234567890”分配给变量 f
,它怎么会打印不出来呢?答案是,因为类型 float
没有足够的精度。
每个人都明白有限的精度意味着您可能无法准确表示像 1.234567890 这样的数字,它可能会被破坏成 1.234567936 或其他东西。这里要认识到的关键是,任何浮点类型(float
或 double
或其他任何类型)的有效位数都是有限的,句号.重要的不仅仅是小数点右边的精度数字。对于大数,您甚至无法精确表示小数点左侧的所有数字。 (事实上,这一点——你的精度有限,适用于小数点的两边——就是 "floating point" 中的单词 "floating" 实际上 表示.)
我有一个 Arduino,它从我的智能手机(通过蓝牙)接收一条包含 unix 时间戳的消息。现在,我正在尝试将我的 DS1307 与该时间戳同步。
但是,它不起作用,因此,我开始搜索并在将包含时间戳的 C 样式数组转换为浮点数时发现了一些奇怪的行为。
// Copy the time into "timeBuff"
int timeLength = siz - _CAT_LENGTH_;
char timeBuff[timeLength+1];
memcpy(timeBuff, &msg[_CAT_LENGTH_], timeLength);
timeBuff[timeLength] = '[=10=]';
// For debugging
Serial.print(F("timeBuff: "));
Serial.println(timeBuff);
// Convert timeBuff string into a variable of type long
float deviceTime = atof(timeBuff);
// Now, deviceTime != what we printed above
Serial.print(F("Epoch time: "));
Serial.println(deviceTime);
前 5 行将消息的右侧部分复制到字符数组中,并添加终止符零。
之后,我们打印timeBuff
的内容,并将其转换为float
存储在deviceTime
中。最后,我们打印 deviceTime
.
这是我第一次测试的结果
timeBuff: 1476113620
Epoch time: 1476113664.00
这是第二次测试
timeBuff: 1476115510
Epoch time: 1476115456.00
为什么atof
的结果和我们传给它的字符串不一样?
在大多数平台上 float
以 IEEE-754 单精度格式表示,这是具有 24 位 (23+1) 尾数的 32 位浮点格式。它根本没有足够的精度来表示您的数字。您的数字需要大约 32 位来表示。当存储在 float
.
精度损失将表现为尾部比特丢失,最后剩余的比特四舍五入
1476113620 -> 01010111111110111011010011010100
1476113664 -> 01010111111110111011010100000000
这是一个非常简单的程序,它更清楚地说明了同样的问题,去掉了涉及时间戳或 atof()
的任何复杂情况,结果证明它们是无关紧要的:
#include <stdio.h>
int main()
{
float f = 1234567890.;
double d = 1234567890.;
printf("f = %f\n", f);
printf("d = %f\n", d);
return 0;
}
我鼓励您编译这个小程序并 运行 它。当我这样做时,我看到:
f = 1234567936.000000
d = 1234567890.000000
起初这似乎是不可能的。看起来它一定是编译器或 printf
中的一个错误,或者其他什么。如果我将简单的数字“1234567890”分配给变量 f
,它怎么会打印不出来呢?答案是,因为类型 float
没有足够的精度。
每个人都明白有限的精度意味着您可能无法准确表示像 1.234567890 这样的数字,它可能会被破坏成 1.234567936 或其他东西。这里要认识到的关键是,任何浮点类型(float
或 double
或其他任何类型)的有效位数都是有限的,句号.重要的不仅仅是小数点右边的精度数字。对于大数,您甚至无法精确表示小数点左侧的所有数字。 (事实上,这一点——你的精度有限,适用于小数点的两边——就是 "floating point" 中的单词 "floating" 实际上 表示.)