Преобразование Float в int не работает прямо в методе с использованием varargs

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

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

int charsToIntValue(int argc, ...) {
  int multiplier = (int) pow(10, argc - 1);
  va_list argp;
  va_start(argp, argc);
  Serial.println(multiplier);
  ...
  va_end(argp);
}

По какой-то причине множитель всегда хранится как 1 меньше фактического результата. например, если charsToIntValue(4, 1, 2, 3, 4) при выполнении множитель сохраняется как 999, а не как 1000. Однако, когда он печатается как float, он показывает 1000,00. И всякий раз, когда я меняю его на константу ((int) pow(10, 3)), он отлично работает, возвращая 1000.

Я устал хранить значение argc в локальной переменной, но это, похоже, ничего не изменило.

Полный текст проекта приведен здесь для контекста: https://github.com/Pyrodron/charger-destination-board

Файл, о котором идет речь,-charger-destination-board.ino; строки с 32 по 51

, 👍0

Обсуждение

Минимальная тестовая настройка вокруг вашего "charsToIntValue" не показывает мне, о чем вы говорите. Я не думаю, что это имеет какое-то отношение к поддержке переменных функций. И Ардуино тоже. Я просто добавлю, что pow(10, argc - 1); можно было бы лучше написать как небольшую функцию, которая принимает int, возвращает int и состоит из оператора switch без float вообще., @timemage

В настоящее время я не уверен, возвращает ли pow() float. Если да, то такое поведение кажется нормальным. Поплавки могут сохранять только приблизительное значение искомого числа. Фактическое значение float, которое вычисляется, может быть что-то вроде 999.99. Когда вы их преобразуете в int, цифры за десятичной точкой выбрасываются, оставляя 999. Это то, что вам нужно принять, когда вы хотите работать с поплавками. И это одна из причин, почему часто лучше просто использовать целочисленное представление., @chrisl

@timemage Я черпал вдохновение из вашего комментария и реализовал свою собственную функцию питания, а не использовал библиотеку math.h. Теперь это работает!, @Pyro


2 ответа


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

2

Вдохновившись комментарием timemage, я решил в конечном счете реализовать свою собственную функцию power, которая принимает два аргумента int и возвращает int. Вместо того, чтобы использовать функцию pow в библиотеке <math.h>, которая возвращает double, когда мне нужен только int.

int power(int x, int y) {
  return y == 0 ? 1 : x * power(x, y - 1);
}

Теперь программа работает по назначению!

,

У меня были некоторые сомнения по поводу того, что это действительно решило проблему, потому что я проверил несколько значений экспоненты, которые вы использовали, чтобы увидеть, удалось ли pow() *получить нужные вам значения. Оказывается, avr-gcc будет использовать более высокую точность, если оптимизатор может определить во время компиляции, независимо от того, делает ли он это контекстом "constexper", что означает, что наличие аргумента, квалифицированного как volatile, приведет к тому, что "pow ()" будет оцениваться с меньшей точностью. Просто еще одна причина не использовать float на AVR., @timemage

Когда оптимизатор включилpow(10, 4)", результат имел поле significand 0b00111000011111111111110 и где оптимизатор не мог определить показатель степени во время компиляции, поле significand 0b00111000100000000000000. Первый преобразуется в "int" с ожидаемым (для некоторых значений "expected") значением 10000, в то время как последний дает значение ближе к 9984. Также вы должны знать, что в avr-gcc действительно нет двойника. Это отдельный тип для целей перегрузки, но он реализован как single precision float`., @timemage

@timemage вся полезная информация. Спасибо, @Pyro


0

Как объяснил @chrisl в комментарии, проблема в том, что pow() не возвращает точный результат, а только приближение. Он отлично работает с константами времени компиляции, потому что в этом случае он вычисляется во время компиляции компилятором на вашем компьютере.

Простое решение: замените cast на int на round():

int multiplier = round(pow(10, argc - 1));

Это работает, но ужасно неэффективно, так как pow() включает в себя вычисление как экспоненты, так и логарифма, что очень дорого для Arduino на базе AVR. Функция int-only в вашем собственном ответе , безусловно, лучше. Однако обычно рекомендуется избегать рекурсии на ардуино с низким объемом памяти, таких как Uno. Предпочитайте итеративный алгоритм, если можете.

Кроме того, сложность вашей функции int-only равна O(y). Существует хорошо известный алгоритм для целочисленных степеней, который выглядит следующим образом O(log(y)):

int power(int x, int y)
{
    int z = 1;  // Invariant: result = x^y * z
    while (y) {
        if (y & 1) z *= x;
        y >>= 1;
        x *= x;
    }
    return z;
}

Может быть, я придираюсь, так как ваш y не может выйти за пределы 5, не переполняя результат...

,

"отлично работает с константами времени компиляции, потому что", как я уже сказал в комментариях к собственному ответу O. P., на самом деле все гораздо сложнее. Это не имеет никакого отношения к тому, являются ли они технически "постоянными". Вы можете выявить разницу в этом поведении,не переходя между постоянными выражениями и непостоянными выражениями. Стандарт предусматривает различия в точности между этими двумя случаями. Так это и есть фин. Но тот факт, что это даст два совершенно разных результата вне контекста constexpr, вероятно, является ошибкой., @timemage