Почему Arduino округляет некоторые целые числа, но не другие, когда настольный компьютер этого не делает?

Я пишу (на C) некоторые низкоуровневые процедуры манипулирования цифрами, которые преобразуют int в float и наоборот для Arduino. Я столкнулся с некоторым загадочным поведением с Arduino, округляющим некоторые числа, но не другие. Поэтому я нашел и извлек оскорбительный код в простую программу, которую я скомпилировал на настольном ПК Linux с GCC и на Arduino - они дают разные результаты. Почему? Вот эти программы. Цель состоит в том, чтобы взять число с плавающей запятой 1.3140 и число с плавающей запятой 2.3140 и разделить их на два целых числа. В первом случае я ожидаю "1" и "3140", а во втором - "2" и "3140" (терминал "0" важен для моего приложения). Код Arduino:

void setup() {
double dnum,x,y;
uint16_t intpart,fracpart;

Serial.begin(9600);

dnum = 1.3140;

 x=modf(dnum,&y);
 intpart = (uint16_t)y;
 fracpart = (uint16_t)(10000.0*x);

Serial.println(dnum,4);
Serial.println(intpart);
Serial.println(fracpart);

}

и результат, который я получаю, таков:

1.3140
1
3140

Фантастика! Успех. Но держись. Если я изменю жестко закодированное число на dnum = 2.3140, я получу следующее:

2.3140
2
3139

Что? Почему он округлил вторую часть вниз? Я делаю что-то в корне неправильно? Поэтому я написал небольшую программу на языке Си для своего ПК (скомпилированную с помощью gcc). Вот код:

#include<stdint.h>
#include<stdio.h>
#include<math.h>

  
int main (int argc, char *argv[])
{
 double dnum,x,y;
 uint16_t intpart,fracpart;

 dnum = 2.3140;
 
  x=modf(dnum,&y);
  intpart = (uint16_t)y;
  fracpart = (uint16_t)(10000.0*x);
 
 printf("Int_part = %u\nFrac_Part = %u\n",intpart,fracpart);
 
 
 return 0;
}

При запуске я получаю следующий результат:

Int_part = 2
Frac_Part = 3140

Теперь это то, что я ожидаю - и чтобы быть тщательным, я также выполнил вышеупомянутую программу C с dnum = 1.3140; и я снова получил "правильный" ожидаемый результат:

Int_part = 1
Frac_Part = 3140

Так почему же Arduino округляет дробную часть вниз, когда я иногда преобразую ее в целое число, а не в другие? Я попробовал это на подлинном Arduino Uno, клоне Arduino uno и клоне Arduino nano, и все они дают одинаковые результаты. В "теории" это может быть какая-то разница в терпимости компилятора к некоторым несоответствиям кодирования, которые я сделал (gcc более терпим), но может ли кто-нибудь сказать мне, что я сделал не так и как заставить Arduino вести себя (так, как я хочу)? Спасибо.

, 👍4


1 ответ


Лучший ответ:

5

Когда вы приводите число с плавающей запятой к целому числу, оно всегда округляется до нуля. Если вам нужно поведение от раунда к ближайшему, вы должны использовать функцию Arduino round() .

Что касается происхождения расхождения, то оно проистекает из двух фактов:

  1. Числа, которые вы используете, не совсем представимы как числа с плавающей запятой.
  2. Двойник на Arduino Uno фактически эквивалентен float (только 32 бита).

Когда вы пишете оператор типа dnum = 1.3140;, компилятор сохраняет в dnum число с плавающей запятой, которое ближе всего к десятичному числу 1.3140.

Сравните:

float32(1.3140) = 1.31400001049041748046875
float64(1.3140) = 1.31400000000000005684341886080801486968994140625
float32(2.3140) = 2.3139998912811279296875
float64(2.3140) = 2.31400000000000005684341886080801486968994140625

Из этих 4 примеров только float32 (2.3140) округляется в меньшую сторону.

,

Очень ясно - спасибо. Я не знал о 32-битном ограничении Arduino, но вы меня просветили. Я также подтвердил ваш ответ, сделав 'dnum' плавающим в моей программе для настольных ПК, и он дает те же ответы, что и мой Arduino, так что практика и теория согласны. Я не думаю, что round() поможет мне здесь, мне просто нужно признать, что я не могу получить желаемую точность на Arduino (без дополнительного кода "произвольной точности", который переполнит мою программу в моем приложении)., @WildCat

@WildCat: Если вам нужно всего 1 + 4 десятичных цифры или около того, как в приведенном вами примере, должно быть достаточно float, при условии, что вы получаете дробные цифры с round (1e4 * x). Значение float равно примерно 7 десятичным разрядам (FLT_EPSILON1.19e-7)., @Edgar Bonet