О флоатах на микроконтроллерах

Nov 10, 2021 14:21

Я уже писал о кусочно-линейной аппроксимации для сравнительно быстрого вычисления тех же температур или сопротивлений по данным в ADU с АЦП и калибровочной кривой, представленной набором коэффициентов. И даже использовал этот способ. Однако, хорош он лишь для всяких Cortex-M3 и выше - где есть аппаратное деление. А вот для Cortex-M0 лучше данный способ еще сильней переделать: заменить деление на комбинацию умножения и сдвига. Как оказалось в споре на одном форуме, даже расширение uint32_t до uint64_t, чтобы выполнить заданное умножение без переполнения, не приводит к существенному снижению производительности. И то же деление uint32_t на 10 значительно шустрей выполнить, приведя uint32_t к uint64_t: умножаем на 52429 и сдвигаем 19 раз вправо (правда, здесь будет "нечестное" деление: местами как floor(x/10), а местами - как ceil(x/10); для более честного способа нужны числа побольше).

Как оптимально вычислить множитель и сдвиг, я не придумал, поэтому будем простым перебором искать число, соответствующее заданной (или, если нет такого, то наилучшей) точности. Естественно, позаботимся о том, чтобы не было переполнения uint64_t (т.е. умножать можно максимум на 1^32-1):

function [N Shift] = findNR(K, dk) N = 1; Shift = 1; delta = 1; Ngood = 1; Shiftgood = 1; deltagood = 1; Nmax = bitshift(1,32) - 1; for Shift = 3:31; N = round(K*bitshift(1,Shift)); if(N > Nmax) break; endif delta = abs(bitshift(Nmax*N, -Shift)/Nmax - K); if(delta < dk) break; endif if(delta < deltagood) deltagood = delta; Ngood = N; Shiftgood = Shift; endif endfor if(delta > dk) printf("tolerance %g not found, use %g\n", dk, deltagood); Shift = Shiftgood; N = Ngood; endif printf("X*%g approx X*%d >> %d\n", K, N, Shift); endfunction
Пусть у нас есть набор коэффициентов: 1.23, 3.25 и 7.11. В первом случае, чтобы подобрать рациональную дробь, представляющую эти коэффициенты с точностью не хуже 0.001, надо было сделать так:

[n d]=rat([1.23 3.25 7.11], 0.001) n = 16 13 711 d = 13 4 100
Можно повысить точность:

[n d]=rat([1.23 3.25 7.11], 0.00001) n = 123 13 711 d = 100 4 100
(при этом если мы работаем с выхлопом АЦП, то вполне уложимся в uint32_t даже с такой точностью).
Можно проверить (хотя и ежу понятно, что 123/100 будет 1.23, но вот в предыдущий случай интересней):

n./d ans = 1.2308 3.2500 7.1100
Для поиска множителя и сдвига пишем скриптик:

function [N Shift] = findNR(K, dk) N = 1; Shift = 1; delta = 1; Ngood = 1; Shiftgood = 1; deltagood = 1; Nmax = bitshift(1,32) - 1; for Shift = 3:31; N = round(K*bitshift(1,Shift)); if(N > Nmax) break; endif delta = abs(bitshift(Nmax*N, -Shift)/Nmax - K); if(delta < dk) break; endif if(delta < deltagood) deltagood = delta; Ngood = N; Shiftgood = Shift; endif endfor if(delta > dk) printf("tolerance %g not found, use %g\n", dk, deltagood); Shift = Shiftgood; N = Ngood; endif printf("X*%g approx X*%d >> %d\n", K, N, Shift); endfunction
(но, все-таки, надежней было бы его на С написать, т.к. октава де-факто всегда пользуется числами двойной точности и можно нарваться на косяки).
Теперь найдем те же коэффициенты:

for K=[1.23 3.25 7.11]; [N S]=findNR(K, 1e-3); endfor X*1.23 approx X*315 >> 8 X*3.25 approx X*26 >> 3 X*7.11 approx X*455 >> 6
Проверим:

315/bitshift(1,8) ans = 1.2305 26/bitshift(1,3) ans = 3.2500 455/bitshift(1,6) ans = 7.1094
А теперь попробуем повысить точность до 1e-5:

for K=[1.23 3.25 7.11]; [N S]=findNR(K, 1e-5); endfor X*1.23 approx X*80609 >> 16 X*3.25 approx X*26 >> 3 X*7.11 approx X*465961 >> 16
Даже здесь для данных с 12-битного АЦП операции не выходят за границы uint32_t! Проверим точности:

80609/bitshift(1,16) ans = 1.2300 26/bitshift(1,3) ans = 3.2500 465961/bitshift(1,16) ans = 7.1100
- шикарно! И при этом тратится намного меньше времени на арифметику!
Понятно, что один раз в секунду 10 температур посчитать - можно как-то и не париться с упрощением арифметики, но вот если нужно намного чаще молотить и использовать деления на константу, то уж лучше поэкономить ресурсы для более полезных вещей.
Reposted from dreamwidth: https://eddy-em.dreamwidth.org/308552.html.

octave, stm32, c

Previous post Next post
Up