Все float возвращают округленное целое число в меньшую сторону.

Поэтому я очень озадачен полученными результатами. Кажется, что float работают неправильно, и я не понимаю, почему.

Я пытался прочитать показания датчика температуры и преобразовать показания в шкалу Цельсия. Однако, когда я попытался разделить значение (int между 145-165) на 1024 и умножить на 5, я бы получил значение с плавающей запятой 0,00. Вот код, который я использовал

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

}

void loop() {
  int sensorVal = analogRead(A0);
    Serial.print("Sensor Value: ");
    Serial.println(sensorVal);

  float voltage = ((sensorVal/1024) * 5);
    Serial.print("Voltage Value: ");
    Serial.println(voltage);

}

Это вернет только значения 0,00 для напряжения, хотя сам датчик работает нормально, возвращая значения от 146 до 165 в зависимости от того, сколько тепла я прикладываю. Это не имело никакого смысла, поэтому я попробовал это с некоторыми другими значениями, например.

Serial.print(float (10 / 6));

и

Serial.print(float (5 / 2));

который должен был вернуть 1,67 и 2,50 соответственно, но вместо этого вернул 1,00 и 2,00

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

Любая помощь будет здоровой!

, 👍1

Обсуждение

Подсказка: попробуйте плавающее напряжение = ((sensorVal/1024.0) * 5.0);, @Mikael Patel

К вашему сведению, ваши промежуточные результаты усечены, а не округлены. ( 0/11 даст 1, так как ответ ≈1,81, и просто уберет десятичную часть. Для сравнения, round(20.0/11.0) даст вам значение 2,0, так как 1,8181 округляется в большую сторону. до 2.), @Duncan C


3 ответа


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

1

Вы выполняете целочисленную арифметику. Попробуйте добавить .0 в конец ваших значений, где вам нужны результаты с плавающей запятой.

Например...


  float voltage = ((sensorVal/1024.0) * 5.0);

Помните, что результаты аналогового чтения представляют собой целое число. Таким образом, преобразование в исходный код с плавающей запятой происходит только тогда, когда «=» «выполняется». Другой вариант — сразу же перевести его в float.


  float voltage = ((((float)sensorVal)/1024.0) * 5.0);

Or more simply


  float voltage = ((float)sensorVal)/1024.0 * 5.0;
,

Это очень помогло. Я понял, что должен был также инициализировать свой sensorVal как число с плавающей запятой., @Mitch Ostler


2

GMc объяснил, что делать, но не объяснил подробно, почему ваш код не дает результатов с плавающей запятой.

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

В C/C++ во время вычисления выражения компилятор также решает, какие типы данных использовать для каждой части. целочисленные выражения оцениваются с использованием целочисленной математики. Если какая-либо из частей выражения имеет более крупный тип или тип с плавающей запятой, оба значения «повышаются» до более крупного или более сложного типа перед вычислением выражения.

в вашем коде

float voltage = ((sensorVal/1024) * 5);

Бит sensorVal/1024 оценивается первым. Поскольку и sensorVal, и 1024 являются целыми числами, компилятор использует целочисленную математику. Это дает результат int, поэтому любая дробная часть усекается и теряется.

Затем он оценивает result*5 (где result – это результат первой части, sensorVal/1024, который является целым числом. ) Обе эти части являются целыми числами, поэтому результатом этого выражения является целое число.

Наконец, вы просите компилятор сохранить результат в число с плавающей запятой voltage. Затем компилятор преобразует целочисленный результат всего выражения в число с плавающей запятой, но вы потеряли любую часть вычислений с плавающей запятой.

Переписав строку как

float voltage = ((sensorVal/1024.0) * 5.0);

Компилятор делает то, что вы хотите. Бит sensorVal/1024.0 теперь является целочисленным числом с плавающей запятой, поэтому компилятор «повышает» sensorVal до числа с плавающей запятой, а затем выполняет математические операции с плавающей запятой. Вторая часть теперь представляет собой число с плавающей запятой, поэтому результат также является числом с плавающей запятой.

float voltage = ((sensorVal/1024.0) * 5);

Также сработает и в этом случае (где 5 — целочисленная константа). Это работает, потому что float_result*5 — это целочисленная математика с плавающей запятой, и компилятор будет продвигать 5 в значение с плавающей запятой и выполнить математические операции с плавающей запятой. чтобы получить окончательный результат. Однако безопаснее использовать все константы с плавающей запятой, чтобы случайно не выполнить часть вычислений с использованием целочисленной математики и не обрезать эту часть перед преобразованием в число с плавающей запятой.

Как вы сказали, неплохо было бы сделать sensorVal числом с плавающей запятой. (Опять же, чтобы избежать случайного выполнения целочисленной математики в части вашего выражения и получения неверных результатов в процессе. В этом случае создание sensorVal с плавающей запятой не требуется, но с другим выражением вы можете закончить с целочисленной математикой для части расчета.)

,

4

Вы уже получили пару совершенно хороших ответов. тем не менее я буду предложить немного другую идиому:

float voltage = sensorVal * (5.0 / 1024);

или, может быть, лучше:

const float V_REF = 5.0;  // at the top of the sketch

float voltage = sensorVal * (V_REF / 1024);

Причина, по которой этот вариант немного лучше других, заключается в том, что деление – даже целочисленное деление – очень затратно работа на меньших ардуино. Написав преобразование, как в строки выше, соотношение V_REF / 1024 становится константой времени компиляции, который оценивается компилятором во время сборки. Тогда только многое более дешевое умножение должно выполняться самой Arduino при запуске время. Ничего страшного, если вы не ограничены во времени, но это легко висящие плоды с точки зрения оптимизации.

Вы можете написать 1024.0 вместо 1024, если это кажется более безопасным. я склоняюсь к оставьте .0 только для напряжения, потому что 1024 — это целое число, тогда как «5» в «5 volts» концептуально является действительным числом (это аналоговое напряжение, и даже не точно 5 В, если вы потрудитесь измерить это). Но это всего лишь личное предпочтение в конце концов.

,

Хороший момент, чтобы избежать разделения во время выполнения. (проголосовали), @Duncan C

Должен признать, @edgar_bonet, мне очень трудно поверить в то, что вы сказали - в конце концов, оптимизирующие компиляторы должны проверять «константную арифметику», «вычислять ее за вас» и просто применять результат. То есть x/1023.0 * 5.0 следует оптимизировать как (X/204.6). Поскольку я вам не поверил (извините за это), я попробовал простую программу и был **ошеломлен**, увидев, что она действительно выполняет деление констант в сгенерированном ассемблере — если вы не поместите эту часть выражения в скобка., @GMc

Что также было интересно, так это то, что в моей первой версии я просто указал int reading = AnalogRead(A0); результат с плавающей запятой = чтение / 1023.0 * 5.0; в моей функции setup() и ничего больше. Короче говоря, компилятор оптимизировал весь блок кода из сгенерированного ассемблера (потому что я ничего не делал с результатом). Только когда я вставил Serial.println(result);, компилятор выдал вычисление. Я собирался проголосовать против вас (потому что я думал, что вы были неправы), но после проверки и того, что вы правы, вы получаете от меня положительный голос., @GMc

@GMc: компилятор не может оптимизировать x / 1023.0 * 5.0 в x * (5.0/1023.0), потому что эти два выражения, хотя и эквивалентны в области _real_ чисел, не эквивалентны для _числов с плавающей запятой. Компилятору не разрешается выполнять оптимизацию, которая может изменить результат вычисления. То есть, если вы не дадите ему такую опцию, как -funsafe-math-optimizations, которая по умолчанию отключена., @Edgar Bonet

@edgar_bonet спасибо за понимание и отличное дополнение к ответу, я обязательно изучу это дальше., @GMc