浮点(双精度)值在具有其他值的字符串中损坏
Float (double) value gets corrupted in a string with other values
我正在使用 ATMEGA328PB AVR 微控制器,在通过 UART 发送某些浮点数时 运行 遇到了问题。
我已经使用我的编译器 (avr-gcc) 启用了浮点,它大部分都可以工作。
例如,这段代码:
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
sprintf(outbuffer, "%f\n", floatVal);
usart1TXString(outbuffer);
在 UART 上提供正确的输出:
3.866311
我也想打印一个32位的int。这也有效:
char outbuffer[255];
sprintf(outbuffer, "%ld\n", transform(combVal));
usart1TXString(outbuffer);
并在 UART 上提供正确的输出:
117864
(这两个输出正是我所期望的)
当我尝试将这两段代码组合成一个(甚至是连续的)传输时出现问题:
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
sprintf(outbuffer, "%f,%ld\n", floatVal, transform(combVal));
usart1TXString(outbuffer);
产生这个输出:
584991570000.000000,117864
大数字不会改变,除非我的 floatVal 是一个非常不同的值。
我试着把它分成两个不同的传输,但没有成功:
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
sprintf(outbuffer, "%f\n", floatVal);
usart1TXString(outbuffer);
char outbuffertwo[255];
sprintf(outbuffertwo, ",%ld\n", transform(combVal));
usart1TXString(outbuffertwo);
产生相同的错误结果。
584991570000.000000,117419
我也试过使用 dtostrf() 而不是 sprintf():
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
dtostrf(floatVal, 6, 4, outbuffer);
usart1TXString(outbuffer);
有效。但是一旦我尝试添加另一个值,它就会再次中断:
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
dtostrf(floatVal, 6, 4, outbuffer);
usart1TXString(outbuffer);
char outbuffertwo[255];
sprintf(outbuffertwo, ",%ld\n", transform(combVal));
usart1TXString(outbuffertwo);
返回 UART 输出的相同损坏值。
我尝试增加缓冲区的大小(即使它应该已经足够大了)。
我认为这不重要,但以防万一:
'tempResult' 是一个无符号的 64 位整数,它是从 ADC 接收到的 16 位值的累加。
'oversample' 是一个 16 位整数,它是指定的过采样量。目前 50.
'floatVal' 是双数。
'combVal' 是一个 32 位整数,包含一个 24 位 ADC 值,以二进制补码表示。
transform() 是一个函数,用来处理两个补码,看起来像这样:
int32_t transform(int32_t value)
{
if (value > MAX_VALUE)
{
value -= MODULO;
}
return value;
}
损坏 %f 的 %ld 是怎么回事?
编辑:即使我只用一个包含正确数字的变量替换 transform(combVal)
,它仍然会破坏另一个值。
我发现了问题所在,这是我说我认为无关紧要的事情之一。具体是这样的:
'tempResult' is an unsigned 64 bit int that is an accumulation of 16 bit values received from an ADC.
来自 avr-libc 常见问题解答:
float and double are 32 bits (this is the only supported floating point format)
我将问题缩小到这一行:
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
因为如果我用这个替换它:
floatVal = 3.123456
一切如预期。
将 uint64_t tempResult;
更改为 uint32_t tempResult;
可以解决所有问题。我只需要不要对过采样太过分,一切都会好起来的。
长话短说:在 8 位 AVR 上滥用浮点数导致了 UB,这是 UB 喜欢做的事情(有时工作并导致其他人很困惑)。
我正在使用 ATMEGA328PB AVR 微控制器,在通过 UART 发送某些浮点数时 运行 遇到了问题。
我已经使用我的编译器 (avr-gcc) 启用了浮点,它大部分都可以工作。
例如,这段代码:
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
sprintf(outbuffer, "%f\n", floatVal);
usart1TXString(outbuffer);
在 UART 上提供正确的输出:
3.866311
我也想打印一个32位的int。这也有效:
char outbuffer[255];
sprintf(outbuffer, "%ld\n", transform(combVal));
usart1TXString(outbuffer);
并在 UART 上提供正确的输出:
117864
(这两个输出正是我所期望的)
当我尝试将这两段代码组合成一个(甚至是连续的)传输时出现问题:
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
sprintf(outbuffer, "%f,%ld\n", floatVal, transform(combVal));
usart1TXString(outbuffer);
产生这个输出:
584991570000.000000,117864
大数字不会改变,除非我的 floatVal 是一个非常不同的值。
我试着把它分成两个不同的传输,但没有成功:
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
sprintf(outbuffer, "%f\n", floatVal);
usart1TXString(outbuffer);
char outbuffertwo[255];
sprintf(outbuffertwo, ",%ld\n", transform(combVal));
usart1TXString(outbuffertwo);
产生相同的错误结果。
584991570000.000000,117419
我也试过使用 dtostrf() 而不是 sprintf():
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
dtostrf(floatVal, 6, 4, outbuffer);
usart1TXString(outbuffer);
有效。但是一旦我尝试添加另一个值,它就会再次中断:
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
char outbuffer[255];
dtostrf(floatVal, 6, 4, outbuffer);
usart1TXString(outbuffer);
char outbuffertwo[255];
sprintf(outbuffertwo, ",%ld\n", transform(combVal));
usart1TXString(outbuffertwo);
返回 UART 输出的相同损坏值。
我尝试增加缓冲区的大小(即使它应该已经足够大了)。
我认为这不重要,但以防万一:
'tempResult' 是一个无符号的 64 位整数,它是从 ADC 接收到的 16 位值的累加。
'oversample' 是一个 16 位整数,它是指定的过采样量。目前 50.
'floatVal' 是双数。
'combVal' 是一个 32 位整数,包含一个 24 位 ADC 值,以二进制补码表示。
transform() 是一个函数,用来处理两个补码,看起来像这样:
int32_t transform(int32_t value)
{
if (value > MAX_VALUE)
{
value -= MODULO;
}
return value;
}
损坏 %f 的 %ld 是怎么回事?
编辑:即使我只用一个包含正确数字的变量替换 transform(combVal)
,它仍然会破坏另一个值。
我发现了问题所在,这是我说我认为无关紧要的事情之一。具体是这样的:
'tempResult' is an unsigned 64 bit int that is an accumulation of 16 bit values received from an ADC.
来自 avr-libc 常见问题解答:
float and double are 32 bits (this is the only supported floating point format)
我将问题缩小到这一行:
floatVal = ((double)tempResult / (double)oversample) * 0.000457777;
因为如果我用这个替换它:
floatVal = 3.123456
一切如预期。
将 uint64_t tempResult;
更改为 uint32_t tempResult;
可以解决所有问题。我只需要不要对过采样太过分,一切都会好起来的。
长话短说:在 8 位 AVR 上滥用浮点数导致了 UB,这是 UB 喜欢做的事情(有时工作并导致其他人很困惑)。