Python与Arduino USB通信浮点数精度异常问题排查
Arduino与Python浮点数通信精度误差问题解析
你遇到的这个浮点数精度误差问题,本质是单精度浮点数的二进制存储限制导致的,咱们来具体拆解原因:
核心成因:单精度vs双精度的精度差异
- Arduino里的
float是32位单精度浮点数,它的有效尾数只有23位,大概对应6-7位十进制有效数字。很多十进制小数(比如0.112、0.23)无法转换成有限长度的二进制小数,单精度只能用最接近的近似值来存储。 - Python里的
float是64位双精度浮点数,精度更高。当Arduino把单精度存储的近似值通过串口传过来,Python解析成双精度时,就会把这个近似值的细节完整显示出来,也就是你看到的4112.11181641、-7631.22998047这类“误差值”。
为什么.5结尾的数没问题?
像4112.5、-7631.5这样的数,对应的二进制是有限长度的(0.5是2的-1次方),单精度浮点数可以精确存储,所以传输后Python解析出来也完全准确。
代码层面的验证
你可以在Arduino里加一行代码打印values.c的值,比如:
Serial.println(values.c, 10);
会发现Arduino本身存储的4112.112已经是4112.11181640625了——这就是单精度存储的近似值,回传给Python后,自然会显示出这个精确的近似结果。
可选的解决思路
如果需要更高精度的数值传输,有几个方向可以尝试:
- 定点数传输:把浮点数放大成整数(比如保留3位小数就乘以1000),传输整数后再在Python里转回来,完全避免浮点数精度问题。
- 字符串传输:把数值转换成字符串后传输,Python再解析成浮点数,适合对传输效率要求不高的场景。
- 注意精度约定:如果不需要超高精度,可以在Python里对收到的数值做四舍五入,比如保留3位小数:
round(number3, 3)。
附:你提供的代码片段
Python代码
import os import struct import serial import time print('HELLO WORLD!!!!\nI AM PYTHON READY TO TALK WITH ARDUINO\nINSERT PASSWORD PLEASE.') ser=serial.Serial("COM5", 9600) #Serial port COM5, baudrate=9600 ser.close() ser.open() #open Serial Port a = int(raw_input("Enter number: ")) #integer object b = int(raw_input("Enter number: ")) #integer object c = float(raw_input("Enter number: ")) #float object d = float(raw_input("Enter number: ")) #float object time.sleep(2) #wait ser.write(struct.pack("2i2f",a,b,c,d)) #write to port all all number bytes if a == 22 : if b == -22 : if c == 2212.113 : if d == -3131.111 : print("Congratulations!!! Check the ledpin should be ON!!!") receivedbytes=ser.read(16) #read from Serial port 16 bytes=2 int32_t + 2 floats from arduino (number1,number2,number3,number4,)=struct.unpack("2i2f",receivedbytes) #convert bytes to numbers print "Arduino also send me back ",str(number1),",",str(number2),",",str(number3),",",str(number4) else : print("WRONG PASSWORD") os.system("pause") #wait for user to press enter
Arduino代码
struct sendata { //data to send volatile int32_t a=53; volatile int32_t b=-2121; volatile float c=4112.5; volatile float d=-7631.5; }; struct receive { //data to receive volatile int32_t a; //it will not work with int volatile int32_t b; volatile float c; volatile float d; }; struct receive bytes; struct sendata values; const int total_bytes=16; //total bytes to send int i; byte buf[total_bytes]; //each received Serial byte saved into byte array void setup() { Serial.begin(9600); pinMode(13,OUTPUT); //Arduino Mega ledpin } void loop() { } void serialEvent() { //Called each time Serial data is received if (Serial.available()==total_bytes){ //Receive data first saved toSerial buffer,Serial.available return how many bytes are saved.The Serial buffer space is limited. while(i<=total_bytes-1){ buf[i] = Serial.read(); //Save each byte from Serial buffer to byte array i++; } memmove(&bytes,buf,sizeof(bytes)); //Move each single byte memory location of array to memory field of the struct,the numbers are reconstructed from bytes. if (bytes.a==22){ //Access each struct number. if (bytes.b==-22){ if (bytes.c==2212.113){ if (bytes.d==-3131.111){ //If the password is right Serial.write((const uint8_t*)&values,sizeof(values)); //Write struct to Serial port. delay(100); digitalWrite(13,HIGH);//Turn ON LED. } } } } }
内容的提问来源于stack exchange,提问作者panagiotis96




