Два значения с плавающей запятой не добавляются и не печатаются на ESP-WROOM-32.

У меня есть плата Olimex ESP32-PoE, на которой есть ESP-WROOM-32.

Я только что обнаружил странное поведение при печати чисел с плавающей запятой на доске.

Эскиз

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  float a=34567891.234;
  float b=2.3456;
  a++;
  Serial.print(a+b, 4);
  Serial.println("");
  delay(1000);
}

Эскиз чрезвычайно прост: я хочу сложить два числа с плавающей запятой, одно большое, а другое маленькое, чтобы напечатать результат их сложения до 4 знаков.

Вывод

на последовательной консоли в Arduino IDE

34567896.0000
34567896.0000
34567896.0000
34567896.0000
34567896.0000
34567896.0000
34567896.0000
34567896.0000
34567896.0000
34567896.0000
34567896.0000
34567896.0000
34567896.0000

Каждый раз, когда я увеличиваю float a с помощью a++, я ожидаю, что он увеличится на единицу и распечатаю результат, но вывод выше полностью отключен.

Без приращения

void loop() {
  // put your main code here, to run repeatedly:
  float a=34567891.234;
  float b=2.3456;
  Serial.print(a+b, 4);
  Serial.println("");
  delay(1000);

}

выход

34567896.0000
34567896.0000
34567896.0000

Тот же результат.

Это специфичная плата или Serial.print не поддерживает float?

, 👍1

Обсуждение

Попробуйте двойку. 34567891.234 требует 35 бит точности для хранения десятичных знаков, но числа с плавающей запятой имеют только 23., @Dave X

Есть аналогичный вопрос, но это меня еще больше смущает: https://stackoverflow.com/questions/8561393/is-using-increment-operator-on-floats-bad-style, @Jot


2 ответа


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

2

Как уже говорилось в комментариях и ответе Дэйва X, число с плавающей точкой ограничено. разрешение. Это разрешение задается константой FLT_EPSILON, что представляет собой разницу между 1 и наименьшим числом с плавающей запятой, превышающим 1. В любой системе, соответствующей стандарту IEEE 754, FLT_EPSILON равен 2-23, или около 1.19e-7. Это означает, что в интервале [1, 2], действительные числа, которые можно точно представить с помощью чисел с плавающей запятой. кратны FLT_EPSILON. Разрешение, однако, ухудшается по мере того, как вы рассматриваете большие числа. В интервале [2, 4] это вдвое больше как грубо. В пределах [4, 8] оно в четыре раза грубее и так далее:

interval     resolution
------------------------------------------
1    – 2     2^-23 ≈ 1.19e-7 (FLT_EPSILON)
2    – 4     2^-22
...
2^23 – 2^24  1
2^24 – 2^25  2
2^25 – 2^26  4
...

Ваше число a чуть больше 225. В рамках этого дальность, числовое разрешение (также известное как «ulp»: единица измерения на последнем месте) равно 4 (последняя строка в таблице выше). Таким образом, плавающие числа в этом диапазоне могут представляют собой только точные кратные 4. Когда ты пишешь

double a=34567891.234;

компилятор заменяет это ближайшим числом с плавающей точкой, которое оказывается 34567892. То же самое для b, но в этом случае ошибка округления мала. и несущественный (около -1.1e-7). Когда ты пишешь

a++;

вы заменяете a плавающей точкой, ближайшей к a+1, что сам по себе является a. Так что инструкция не имеет никакого эффекта. Когда ты это сделаешь

Serial.print(a+b, 4);

вы вычисляете число с плавающей запятой, ближайшее к 34567892+2,3456, что 34567896.

Вместо этого double имеет разрешение DBL_EPSILON, которое 2-52 или около 2,22e-16. Это дает вам некоторую точность запас, но имейте в виду, что Arduino на базе AVR на самом деле не поддерживают удваивается и воспринимает ключевое слово double как синоним float. Этот может стать проблемой только в том случае, если вам когда-нибудь придется переместить свой код в более традиционный Arduino.

,

Я не знал о синониме AVR с двойным плавающим числом. Спасибо., @Dave X


3

Попробуйте двойные значения вместо чисел с плавающей запятой.

Вашему начальному значению 34567891,234 требуется 35-битная точность для отслеживания десятичных знаков, но у чисел с плавающей точкой точность только 24 бита. Таким образом, чтобы иметь достаточную точность для отслеживания разницы между 34567891.234 и 34567891.234+1, вам необходимо хранилище с более высокой точностью.

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  double a=34567891.234;
  double b=2.3456;
  a++;
  Serial.print(a+b, 4);
  Serial.println("");
  delay(1000);
}
,