Почему 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 вести себя (так, как я хочу)? Спасибо.
@WildCat, 👍4
1 ответ
Лучший ответ:
Когда вы приводите число с плавающей запятой к целому числу, оно всегда
округляется до нуля. Если вам нужно поведение от раунда к ближайшему, вы должны
использовать функцию Arduino round()
.
Что касается происхождения расхождения, то оно проистекает из двух фактов:
- Числа, которые вы используете, не совсем представимы как числа с плавающей запятой.
Двойник
на 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)
округляется в меньшую сторону.
- устаревшее преобразование из строковой константы в 'char*'
- как быстро loop() работает в Arduino
- как отправить аргумент объектам ESP8266WebServer в функции
- Arduino синтаксический анализ строки с использованием sscanf
- Объявление переменной внутри основного цикла
- Как создать проект ардуино с несколькими исходными файлами?
- Как преобразовать символ Unicode в «Unicode HEX Position» в Arduino
- lcd.createChar() позволяет использовать только 8 произвольных символов
Очень ясно - спасибо. Я не знал о 32-битном ограничении Arduino, но вы меня просветили. Я также подтвердил ваш ответ, сделав 'dnum' плавающим в моей программе для настольных ПК, и он дает те же ответы, что и мой Arduino, так что практика и теория согласны. Я не думаю, что round() поможет мне здесь, мне просто нужно признать, что я не могу получить желаемую точность на Arduino (без дополнительного кода "произвольной точности", который переполнит мою программу в моем приложении)., @WildCat
@WildCat: Если вам нужно всего 1 + 4 десятичных цифры или около того, как в приведенном вами примере, должно быть достаточно
float
, при условии, что вы получаете дробные цифры сround (1e4 * x)
. Значениеfloat
равно примерно 7 десятичным разрядам (FLT_EPSILON
≈1.19e-7
)., @Edgar Bonet