Выполнение 32-битной арифметики на встроенном устройстве
Сейчас я работаю с Arduino Uno и пытаюсь понять, как с его помощью выполнять 32-битные арифметические операции.
Мне нужно вычислить следующее выражение: √(2 n/a), в котором n
— целое число, а a
— константа с плавающей запятой.
Я совершенно не уверен, как мне это сделать, поэтому любой совет будет здесь полезен.
@Carlton Banks, 👍-1
Обсуждение2 ответа
Лучший ответ:
Вы написали:
16 наиболее значимых должны быть точными.
Вы в этом абсолютно уверены? Пожалуйста, пересмотрите свое мнение. Это легко, когда задавать вопрос, просить о чем-то лучшем, чем то, что вы строго нужно... пока вы не осознаете цену удовлетворения этих ожиданий.
Недавно я работал над реализацией с фиксированной точкой квадратный корень. Он предназначен для работы с числами в диапазоне [0, 1), представленными в 0.16 формат, т.е. без знака, 0 бит целая часть, 16 бит дробная часть. Она основана на полиномиальном приближении
√x ≈ 0,1908901 + 1,5392302 x − 1,4475870 x2 + 1,0217778 x3 − 0,3043110 x4
что весьма неплохо для x ∈ [0.25, 1]. Числа в (0, 0.25) могут быть переместился в этот интервал путем последовательных умножений на 4, а затем разделив результат на 2. Другими словами, используя тот факт, что
√x = √(4 x) / 2
Максимальная ошибка составляет 2,56e-4, т.е. 16,8 ulps (единиц в последнем место). В худшем случае для выполнения потребуется 298 циклов ЦП. Примечание что для достижения этой производительности мне пришлось реализовать умножение в ассемблере. Иначе gcc дал бы мне неэффективный код.
Вот код:
/*
* Convert x to a 16-bit fixed point number
* with n bits after the binary point.
*/
#define FIXED(x, n) ((uint16_t)((float)(x) * (1UL << (n)) + .5))
/*
* Fixed point multiplication.
*
* Multiply two fixed point numbers in unsigned 0.16 format.
* Returns result in the same format.
* Rounds to nearest, ties rounded up.
*/
static uint16_t mul_fix_u16(uint16_t x, uint16_t y)
{
uint16_t result;
#if defined(__AVR_HAVE_MUL__) && defined(__AVR_HAVE_MOVW__)
/* Optimized ASM version. */
asm volatile(
"mul %B1, %B2\n\t"
"movw %A0, r0\n\t"
"ldi r19, 0x80\n\t"
"clr r18\n\t"
"mul %A1, %A2\n\t"
"add r19, r1\n\t"
"adc %A0, r18\n\t"
"adc %B0, r18\n\t"
"mul %B1, %A2\n\t"
"add r19, r0\n\t"
"adc %A0, r1\n\t"
"adc %B0, r18\n\t"
"mul %A1, %B2\n\t"
"add r19, r0\n\t"
"adc %A0, r1\n\t"
"adc %B0, r18\n\t"
"clr r1"
: "=&r" (result)
: "r" (x), "r" (y)
: "r18", "r19"
);
#else
/* Generic C version. Compiles to inefficient 32 bit code. */
result = ((uint32_t) x * y + 0x8000) >> 16;
#endif
return result;
}
/*
* Fixed point square root.
* Argument and result in unsigned 0.16 format.
*
* If defined(REALLY_ACCURATE):
* max error = 7.67e-6
* execution time:
* 937 cycles (worst case)
* 828.1 cycles (average)
* else
* max error = 2.56e-4
* execution time:
* 298 cycles (worst case)
* 184.7 cycles (average)
*/
uint16_t sqrt_fix(uint16_t x)
{
if (x == 0) return 0;
uint8_t n;
#ifdef REALLY_ACCURATE
if (x == 0xffff) return 0xffff;
uint16_t x0 = x;
#endif
for (n = 0; x < 0x4000; n++) x <<= 2; // scale the operand
uint16_t y;
y = FIXED(0.30435, 15);
y = FIXED(1.02173, 15) - mul_fix_u16(x, y);
y = FIXED(1.44754, 15) - mul_fix_u16(x, y);
y = FIXED(1.53925, 15) - mul_fix_u16(x, y);
y = FIXED(0.19089, 16) + (mul_fix_u16(x, y) << 1);
while (n--) y >>= 1; // scale the result
#ifdef REALLY_ACCURATE
y = (y + ((uint32_t)x0 << 16) / y + 1) / 2; // Babylonian iteration
#endif
return y;
}
Если вам действительно нужен очень точный результат, вы можете
#define REALLY_ACCURATE
. Это добавит одну итерацию
Вавилонский метод, который по сути является методом Ньютона, примененным к
квадратный корень:
y ← (y + x/y) / 2
где y — это наше приближение √x. Это выглядит как легкая победа: просто деление, сложение и сдвиг бита. И это делает результат практически идеально, с максимальной ошибкой 7,67e-6 (0,502 ulps). Но время выполнения увеличивается более чем в три раза, при 937 циклов в худшем случае. Это потому, что чип AVR не имеет аппаратная поддержка для разделения и программная альтернатива, предоставляемая компилятор ужасно медленный.
Вероятно, вы могли бы указать [_Fract
](https://gcc.gnu.org/wiki/avr-gcc#Fixed-Point_Support) в качестве типа в последних (>=4.8) версиях avr-gcc и получить хотя бы небольшую помощь от компилятора., @Ignacio Vazquez-Abrams
@IgnacioVazquez-Abrams: Действительно! Мне еще предстоит протестировать эту функцию. Она вполне может решить проблему неэффективного умножения., @Edgar Bonet
Поскольку вы используете числа с плавающей точкой и вам нужно нечто большее, чем просто быстрое приближение, я бы порекомендовал один из следующих методов:
Вавилонский метод
Поцифровое
Первый вариант — это быстрое приближение с известной границей погрешности, второй вариант даст известный точный результат, но с большим количеством шагов вычисления в целом. Оба варианта, очевидно, округляют до точности вашего типа данных.
РЕДАКТИРОВАТЬ:
Для действительно быстрых вычислений ознакомьтесь с сумасшедшим быстрым обратным квадратным корнем
первого алгоритма на этой странице. Отказ от ответственности: мне никогда не приходилось реализовывать квадратный корень таким образом, но я делал другие, например, Sine(x).
ПРАВКА 2: @pipe высказал хорошее замечание. Если вы просто хотите, чтобы это работало, используйте sqrt(x)
Метод Babylonian очень медленный, если у вас нет аппаратной поддержки для деления. Я пробовал его, см. мой ответ. Быстрый обратный квадратный корень и ответ stackoverflow, на который вы ссылаетесь, оба используют метод Babylonian., @Edgar Bonet
- Разные и самые быстрые способы вычисления синусов и косинусов в Arduino
- Избегайте математических вычислений с плавающей запятой, чтобы ускорить Arduino
- Снять гравитацию с акселерометра MPU-6050
- Как получить среднее значение 16-битного АЦП?
- создание анализатора гармоник мощности, который будет измерять амплитуды основной и кратных ей частот (например, 50 Гц, 100 Гц, 150 Гц, 200 Гц,...)
- Как вычесть микро()?
- Elechhouse Mo3 Shield Play случайная песня
- Как использовать SPI на Arduino?
Я не уверен, что понимаю вопрос... складывать, вычитать, умножать и делить можно., @Carlton Banks
16 наиболее значимых должны быть точными., @Carlton Banks
Недавно возник вопрос о том, как вычислить квадратный корень, используя целочисленную арифметику. Поищите немного., @Eugene Sh.
двоичный.
a
является константой и потенциально может быть сохранена как 32-битное число с фиксированной запятой,n
— это входные данные для системы, он продолжает увеличиваться каждый раз, когда формула должна быть вычислена. Я думаю, его также можно сохранить вuint32_t
, @Carlton BanksНепонятно, какую проблему вы пытаетесь решить. Все это вполне возможно в стандарте C., @pipe
Также обратите внимание, что sqrt(2n/a)=sqrt(n)*(sqrt(2)/sqrt(a)). Дробь является константой (при условии, что a>0 и n>=0)., @pipe