Преобразования типа чисел с фиксированной точкой.
- Войдите на сайт для отправки комментариев
Число с фиксированной точкой - почти такое же, как целое, но отмасштабированное на число кратное 2^n.
Например, для 32-разрядного числа формата 16.16 старшие 16 разрядов хранят целую часть, а младшие - дробную, например, значение "2.5" будет представлено как 0x00028000.
Хотелось бы иметь быстрые (!) преобразования 32-разрядных чисел форматов 16.16, 32.0, 24.8 друг в друга а также в 16-разрядные форматы 16.0 и 8.8, а также в 8-разрядный 8.0. Н у и не забыть про 24-разрядные числа, поддерживаемые AVR.
Форматы 32.0, 16.0 и 8.0 - обычные целые числа unsigned long, unsigned int и byte.
На Паскале что-то похожее (но очень неоптимальное) может выглядеть так:
uses strings; type tb2 = record b : array[0..1]of byte; end; type tb4 = record b : array[0..3]of byte; end; function p32_16r8(d : longint) : smallint; begin (tb2(result)).b[0] := (tb4(d)).b[1]; (tb2(result)).b[1] := (tb4(d)).b[2]; end; function p32_16r0(d : longint) : smallint; begin (tb2(result)).b[0] := (tb4(d)).b[0]; (tb2(result)).b[1] := (tb4(d)).b[1]; end; function p32_16r16(d : longint) : smallint; begin (tb2(result)).b[0] := (tb4(d)).b[2]; (tb2(result)).b[1] := (tb4(d)).b[3]; end; var i : smallint; d : longint; begin d := $12345678; i := p32_16r8(d); writeln(hex(d)); writeln(hex(i)); i := p32_16r0(d); writeln(hex(i)); i := p32_16r16(d); writeln(hex(i)); end.
а что-то похожее написать на С++ у меня не получилось (это фрагмент, строки 11, 13 и 16)
union BytesInLong { unsigned long d; byte b[4]; }; union BytesInInt { unsigned int i; byte b[2]; }; inline unsigned int c32_16r8(unsigned long d) { unsigned int r; ((BytesInInt)(r)).b[0] = ((BytesInLong)d).d[1]; return r; }
сообщает:
Arduino: 1.6.5 (Windows XP), Плата"Arduino Mega or Mega 2560, ATmega2560 (Mega 2560)" F:\Arduino\arduino-1.6.5-r2\hardware\tools\avr/bin/avr-g++ -c -g -Os -w -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -mmcu=atmega2560 -DF_CPU=16000000L -DARDUINO=10605 -DARDUINO_AVR_MEGA2560 -DARDUINO_ARCH_AVR -IF:\Arduino\arduino-1.6.5-r2\hardware\arduino\avr\cores\arduino -IF:\Arduino\arduino-1.6.5-r2\hardware\arduino\avr\variants\mega C:\DOCUME~1\ANDRIA~1.CNI\LOCALS~1\Temp\build5207322130411912797.tmp\byte_pr.cpp -o C:\DOCUME~1\ANDRIA~1.CNI\LOCALS~1\Temp\build5207322130411912797.tmp\byte_pr.cpp.o byte_pr.ino: In function 'unsigned int c32_16r8(long unsigned int)': byte_pr.ino:16:81: error: no matching function for call to 'BytesInInt::BytesInInt(unsigned int&)' byte_pr.ino:16:81: note: candidates are: byte_pr.ino:13:7: note: BytesInInt::BytesInInt() byte_pr.ino:13:7: note: candidate expects 0 arguments, 1 provided byte_pr.ino:13:7: note: BytesInInt::BytesInInt(const BytesInInt&) byte_pr.ino:13:7: note: no known conversion for argument 1 from 'unsigned int' to 'const BytesInInt&' byte_pr.ino:16:105: error: no matching function for call to 'BytesInLong::BytesInLong(long unsigned int&)' byte_pr.ino:16:105: note: candidates are: byte_pr.ino:11:7: note: BytesInLong::BytesInLong() byte_pr.ino:11:7: note: candidate expects 0 arguments, 1 provided byte_pr.ino:11:7: note: BytesInLong::BytesInLong(const BytesInLong&) byte_pr.ino:11:7: note: no known conversion for argument 1 from 'long unsigned int' to 'const BytesInLong&' no matching function for call to 'BytesInInt::BytesInInt(unsigned int&)'
насколько я понял, класс требует соответствующего конструктора, в котором, опять же, потребуется доступа к отдельным байтам, т.е. как раз того, что хочется получить в результате. Ну и эффективность такого подхода внушает сомнения в оптимальности.
Хочется, чтобы в теле программы, например, преобразование 16.16->8.8 выглядело просто как чтение двух байт из серединки числа с перезаписью их в новое число. Т.е 4 ассемблерные инструкции.
Аналогичная диагностика полдучается и при
typedef struct b2 { byte b[2]; }; typedef struct b4 { byte b[4]; }; inline unsigned int c32_16r8(unsigned long d) { unsigned int r; ((struct b2)(r)).b[0] = ((struct b4)d).d[1]; return r; }
сейчас компилятор разворачивает такие конструкци как деление:
(unsigned int)i = (unsigned long)d/65536; // 16.16->16.0 16e: 80 91 24 02 lds r24, 0x0224 172: 90 91 25 02 lds r25, 0x0225 176: a0 91 26 02 lds r26, 0x0226 17a: b0 91 27 02 lds r27, 0x0227 17e: bd 01 movw r22, r26 180: 88 27 eor r24, r24 182: 99 27 eor r25, r25 184: 70 93 23 02 sts 0x0223, r23 188: 60 93 22 02 sts 0x0222, r22 (unsigned int)i = (unsigned long)d/65536; // 16.16->8.8 16e: 80 91 24 02 lds r24, 0x0224 172: 90 91 25 02 lds r25, 0x0225 176: a0 91 26 02 lds r26, 0x0226 17a: b0 91 27 02 lds r27, 0x0227 17e: 69 2f mov r22, r25 180: 7a 2f mov r23, r26 182: 8b 2f mov r24, r27 184: 99 27 eor r25, r25 186: 70 93 23 02 sts 0x0223, r23 18a: 60 93 22 02 sts 0x0222, r22
в идеале приведенные выше примеры должны выглядеть так:
a0 91 26 02 lds r26, 0x0226 ; число long по расположено адресам 0x0224-0x0227 b0 91 27 02 lds r27, 0x0227 70 93 23 02 sts 0x0223, r27 ; число int по расположено адресам 0x0222-0x0223 60 93 22 02 sts 0x0222, r26 90 91 25 02 lds r25, 0x0225 ; число long по расположено адресам 0x0224-0x0227 a0 91 26 02 lds r26, 0x0226 70 93 23 02 sts 0x0223, r26 60 93 22 02 sts 0x0222, r25
Мне в свое время не нашлось способа уговорить компилятор так делать .. ещё один момент, который отписывал разрабам gcc, повторю тут:
long арифметика компиляется так: загрузка всех операндов в 8 регистров (или 4 если с константой), словная операция с 2-я младшими (если такая есть) и побайтная со старшими байтами. В то время как поочередное выполнение с каждым байтом .. освобождает 3(6) регистро РОН вообще как класс и соответственно улучшает перераспределение регистров особенно в случае получения/передачи long в параметрах/результатах .. и вообще, компилятор слабо учитывает что Меги - 8-и разрядные микроконтроллеры и все числа большей разрядности обрабатываются побайтно .. нафига занимать лишние регистры - я так и не понял.
обещали посмотреть.. прошел год. :)
Кстати, переход на побайтное вычисление времени в обработчике прерывания от таймера - существенно разгружает задачу сохранения регистров в стеке .. что сильно сказывается на качестве его работы ..
Ну ладно, а вообще, задача перенести 1-й и 2-й байты четырехбайтового числа в 0-й и 1-й байты двухбайтового числа соответственно решается только путем записи деления на 256 и больше никак?
А чем >>8 и >>16 не устраивает. Компилятор их вроде правильно сптимизирует в байтовые перемещения.
Можно по указателю на long + 1 (+2) переписать куда надо.
Существенно дольше будет. Компилятор верно обрабатывает >> и << на значения кратные 8, только что проверил.
Версия компилятора? Уже интересно ..
Проверял на ИДЕ 1.0.6. Настройки gcc по умолчанию. Могу еще на ИДЕ 1.6.5 глянуть, но не думаю что отломали.
У меня на 1.6.4 компилятор не обрабатывает верно << и >> при кратности 8. Из-за чего в своем arhat.h и делал реализацию некоторых функций на асм-вставках. Проверял многажды и не нашел способа тогда заяснить ему что надо просто взять 2 иных регистра из результата..
Может я не так их пользовал? Приведите код сдвиговой операции для получения микросекунд в виде long из регистра счетчика таймера + 3 байта от счетчика миллисекунд в wiring, очень интересно получится у вас считать регистр счетчика и вернуть 3 байта из одного места и считанный байт вместо пересылок или явных сдвигов..
А нефиг компилятор учить оптимизировать ;) Работает - не трогай.
Код.
ассемблер
Как при 1 - просто видно сдвиг всех байт, сдвиги на 2-7 - циклы таких сдвигов, сдвиг на 8 (стр.43-53 ассембера) - просто пересылки (!!!) сдвиг на 9 - сново аж 9 циклов, я ожидал не такого тут, сдвиг на 10 - я воще не "распарсил" что оно делает, ну в общем и не важно. Это 1.0.6. Если че и на 1.6.5 повторю.
Вот совсем не то, что Вы накоментировали к этому коду .. если бы "просто пересылки", вопросов не было. А то вот что тут делают первые 3 команды, "знак" размножают .. да от "предыдущего результата", а почему не взять тупо 3 байта от переменной, так как явно прописано в коде?
Сдвиг на 10 точно также "оптимизирован" .. от предыдущего результата.
Мне это напоминает древний анекдот про ИИ (ещё в эпоху БЭСМ-6):
Идет экзамен решения задач Оптимизирующим Исскуственным Интеллектом.
-"Условие: Кухня, на плите стоит пустой, чистый чайник, рядом мойка и кран с холодной водой. Задача: вскипятить воды"
(+3мин) -"О, есть простое решение: берем чайник, снимаем крышку, подставляем в мойку под кран, открываем кран, ждем заполнения, закрываем кран, ставим на плиту, включаем, ждем закипания, выключаем. Всё."
-"2 задача, Условие: Кухня, на плите чайник с водой. Задача таже, вскипятить воды"
(+10мин) -"О, найдена существенная оптимизация: выливаем воду из чайника и тем самым сводим задачу к предыдущей".
В догонку, еще один анекдот того же периода (навеяло):
Идет совещание руководства металопрокатного комплекса на предмет автоматизации круглосуточного непрерывного производства .. звонок от секретарши:
-"К Вам тут срочно изобретатель какой-то просится на совещание. Говорит что у него есть решение проблемы"
-"Срочно!" ..
-"Каковы первоначальные затраты на закупку и внедрение?" -"Ну ... в размере 3 мес. зарплаты троих квалифицированных сотрудников" -"Хм .. заманчиво"
-"Каков начальный период монтажа и настройки?" -"Примерно 1-3 недели" -"Вы, ничего не путаете? Как-то сильно заманчиво"
-"Каковы расходы на поддержание, ремонт?" -"Примерно 3 оклада" .. -"ВСЕГО_О?!?"
-"Каковы плановые простои при обслуживании?" -"таких может не быть вовсе" ...
... -"Молодой человек, да Вам ЦЕНЫ НЕТ, где и что это за система такая?"
-"Человек, сэр." :)
Вот совсем не то, что Вы накоментировали к этому коду .. если бы "просто пересылки", вопросов не было. А то вот что тут делают первые 3 команды, "знак" размножают .. да от "предыдущего результата", а почему не взять тупо 3 байта от переменной, так как явно прописано в коде?
А четвертый чем заполнить? Т.к. переменная знаковая, то и расширяется знаком. И кстати очень неплохо сделано. Была бы беззнаковая то осталась бы очистка старшего байта и пересылка 3-х младших. Сдвигов нет - это факт, о чем я и писал. Отрицать безполезно, полное отсутствие ror очевидно, вместо них mov.
В продолжение: в Паскале существует, минимум, 3 способа осуществить преобразовангие 16.16->8.8 :
В С/С++ пока получается следующее.
код:
т.е. лишние байты не загружаются.
Такое, правда, не получится, если хотя бы одна из переменных объявлениа как register или используется передаваемый в стеке параметр функции.
//лишние байты не загружаются.
А какие могут быть "лишние" и "загружатся" при преобразовании long в short? Из 4 байт в 2. там только отбрасыватся 2 байта могут. Но вот эта тема из 16.16 в 8.8 сама по себе очень плохая. Старший и младший байт теряются, если младший еще ладно (но надо бы округлять его), то потеря старшего, оч серезно. Уж лучше 16.16 в 16.0. И прибавить 0,5 перед преобразованием для округления дробной.
16.16->8.8 лишь пример. Реально скорее всего понадобятся 16.16->16.0, 16.16->8.0, 16.16-16.8, 8.16->16.16 и пр.
И все-таки вопрос остается: как сделать подобное без привлесения адресов, например для переменных register?
long L;
short a=L>>16; //16.16->16.0
bytet a=L>>16; //16.16->8.0
long a=L>>8 ; //16.16->16.8
long a=L ; //8.16->16.16
Типа такого, но надо учитывать знаковые или беззнаковые данные, особенно в последнем, остальные должны верно обработать. Например 8.16->16.16 для беззнаковых просто копирование 3 байт ав старший 0, для знакового не ноль а расширение знаком делать.
Правда я не понимаю, в чем ценность 24-битных представлений, имхо 8.16 и подобные - просто геморой себе. Нету у нас подходящих целых типов.
1. При таком подходе зачем-=то читаются ненужные байты (которые не попадают в финальное число).
2. Целый 24-разрядеый тип для AVR есть, называется __int24.
1. При таком подходе зачем-=то читаются ненужные байты (которые не попадают в финальное число).
2. Целый 24-разрядеый тип для AVR есть, называется __int24.
Logic,
1. Дизассемблированный код приведен в исходном (0-м) сообщении темы.
2. Узнал от ЕП. Проверяется экспериментально.
Logic,
1. Дизассемблированный код приведен в исходном (0-м) сообщении темы.
2. Узнал от ЕП. Проверяется экспериментально.
1. Не катит. Там нет вообще >>.
2. Экспериментально тоже отсутствует.
Код __int24 iii;
Ошибка.
Вобще, если это такая большая проблема, что даже служба поддержки год не может решить, то всегда можна обратится к более тяжелому оружию. Я его обычно использую для перестановки байтов местами. Юнионы
Ну и дизасс.
Как видно пересылки из стр.26-27 идеально записались в 7-8. Ну и математика правильно отрабатывает далее.
ПС. в первом посте, кстати не совсем верно описана идеальная пересылка, для регистрового хранения еще лучше movw.
ППС. подумал что и до movw можна оптимизировать с более хитрым юнионом. Если не догадаетесь сами - завтра напишу.
Ну ладно, а вообще, задача перенести 1-й и 2-й байты четырехбайтового числа в 0-й и 1-й байты двухбайтового числа соответственно решается только путем записи деления на 256 и больше никак?
на ассемблере моv 0,1 и mov 1, 2 ))) ну или как-то так, уже не помню ))
Да. На ассемблере тоже возможно, но на юнионах проще и понятней.
С юнионами, естественно все работает. Но хотелось бы объявлять переменные как обычные целые, а для доступа к байтам использовать переопределение типа на ходу примерно так, как у меня в 13 посте написано на Паскале в строках 3-10.
Т.е. для доступа к байтам на ходу подменить тип. Надеюсь, на С такое возможно?
Что касается Ассемблера, то не знаю я ассамблера AVR. Да и писать на нем целиком большую функцию утомительно, а делать вставку - значит, мешать оптимизатору.
Так делайте приведение к типу. Юнион такой же тип как и все остальные. А все что получится потом в макрос загнать для удобства. А можна еще попробовать перегрузку сделать. В общем варианты есть, но их надо пробовать, на предмет оптимального результата. Т.е. чтоб не получать код длиней. Так, сразу сказать, как повлияет (в плане не добавит ли пару или пару десятков лишних команд), таже перегрузку я не возьмусь. Факт, ваша задача имеет решение, а остальное - подробности;)
Так а что там с __int24? В какой версии ИДЕ оно обнаружилось? И я бы сним был оч. осторожный, неизвестно как там с реализацией даже простых действий. И оптимизации. Нестандарт одним словом.
И снова интересна версия компилятора. У меня локально объявленные юнионы версия gcc 4.8.1 прямиком отправляла в память вместо регистров .. что я делал не так? В общем-то это первое что приходило тогда в голову..
//прямиком отправляла в память вместо регистров
Может ему регистров в конкретно том коде не хватало? Так "карта лягла" у компилятора по его замыслу оптимизации.
"тот код" Вы можете посмотреть или освежить в памяти самостоятельно. Файл arhat.c из моей библиотеки, функция micros().
Arhat109-2, Вам уже много раз говорили куда пойти с рекламой своего барахла. Вам надоело нормальное общение по теме и хочется срача? ОК, получите. Либо конкретный код с дизассом, либо не трындите.
Так делайте приведение к типу.
Собственно, в этом и вопрос.
Проблема в том, что я не настолько хорошо знаю С/С++, чтобы сообразить, как это сделать.
В Паскале аналогичные по виду конструкции ничего не преобразовывают, а просто трактуют байты в указаном месте как другой тип. В С же происходит именно преобразование. Например, Паскале вещественноая единица трактуется как какое-то дикое целое число, тогда как в С происходит именно преобразование в целую единицу.
При попытке преобразования в юнион С не знает, как это сделать, и сообщает о том, что отсутствует подходящий конструктор.
Так а что там с __int24? В какой версии ИДЕ оно обнаружилось? И я бы сним был оч. осторожный, неизвестно как там с реализацией даже простых действий. И оптимизации. Нестандарт одним словом.
У меня в 1.6.5 работает. Ссылка: https://gcc.gnu.org/wiki/avr-gcc
Есть, правда, проблема, что иногда код для инт24 получается длиннее и рабротает дольше, чем для инт32.
Ну и далеко не все это понимают, например, вывексти в последовательный порт так просто не получается.
Меня интересует способ реализации этой функции средствами С, а не ассемблером. Только и всего. Лазить и искать где оно, дабы выдрать из него ассемблерные вставки, а тем более компилять код с С для того чтобы показать что "не проходит" у меня сейчас нет ни доступной возможности, ни времени ни желания. К сожалению. Но интересен Ваш опыт .. думал сможете помочь.
reinterpret_cast
//При попытке преобразования в юнион С не знает, как это сделать, и сообщает о том, что отсутствует подходящий конструктор.Беде должно помочь нахальство ))
reinterpret_cast
Самое нахальное приведение типов. Не портируемо, результат может быть некорректным, никаких проверок не делается. Считается, что вы лучше компилятора знаете как на самом деле обстоят дела, а он тихо подчиняется. Не может быть приведено одно значение к другому значению. Обычно используется, чтобы привести указатель к указателю, указатель к целому, целое к указателю. Умеет также работать со ссылками.
reinterpret_cast<whatever *>(some *)
reinterpret_cast<integer_expression>(some *)
reinterpret_cast<whatever *>(integer_expression)
Чтобы использовать
reinterpret_cast
нужны очень и очень веские причины. Используется, например, при приведении указателей на функции.///У меня в 1.6.5 работает. Ссылка: https://gcc.gnu.org/wiki/avr-gcc
Есть, правда, проблема, что иногда код для инт24 получается длиннее и рабротает дольше, чем для инт32.
Ну и нафиг она тогда такая нужна. ))
Вероятно, я неправильно записываю:
Шото оно не похоже. Скорей так.
Старое
char
b[4];
можна убрать, т.к. не используется, правда и не влияет.Шото оно не похоже. Скорей так.
Старое
char
b[4];
можна убрать, т.к. не используется, правда и не влияет.Как-то оно все равно не то, что хочется.
Что получилось:
транслируется в:
и что имеем:
1. От адресов уйти так и не удалось - если я объявлю переменную как register (а это вполне желательно, т.к. это результат промежуточных вычислений), то все адресные операции пойдут лесом.
2. Ну и по мелочам: когда мы работаем напрямую с адресом, база грузится в регистровую пару R29:R28 и затем байты извлекаются по смещению двухбайтовой операцией, а в случае reinterpret_cast каждый байт извлекается по абсолютному адресу, что приводи к тому, что загрузка каждого байта осуществляется 4-байтовой операцией, которая, вероятнее всего, еще и выполняется вдвое дольше.
В общем, вопрос остается: как средствами C/C++ переписать 2 средних байта четырехбайтовой переменной в двухбайтовую без использования адресов (считая, что 4-байтовая переменная имеет квалификатор register)?
1. От адресов уйти так и не удалось - если я объявлю переменную как register (а это вполне желательно, т.к. это результат промежуточных вычислений), то все адресные операции пойдут лесом.
в случае reinterpret_cast каждый байт извлекается по абсолютному адресу, что приводи к тому, что загрузка каждого байта осуществляется 4-байтовой операцией,
Так Вы именно так хотели. Вот в стартовом сообщении:
в идеале приведенные выше примеры должны выглядеть так: