Преобразования типа чисел с фиксированной точкой.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Число с фиксированной точкой - почти такое же, как целое, но отмасштабированное на число кратное 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

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Мне в свое время не нашлось способа уговорить компилятор так делать .. ещё один момент, который отписывал разрабам gcc, повторю тут:

long арифметика компиляется так: загрузка всех операндов в 8 регистров (или 4 если с константой), словная операция с 2-я младшими (если такая есть) и побайтная со старшими байтами. В то время как поочередное выполнение с каждым байтом .. освобождает 3(6) регистро РОН вообще как класс и соответственно улучшает перераспределение регистров особенно в случае получения/передачи long в параметрах/результатах .. и вообще, компилятор слабо учитывает что Меги - 8-и разрядные микроконтроллеры и все числа большей разрядности обрабатываются побайтно .. нафига занимать лишние регистры - я так и не понял.

обещали посмотреть.. прошел год. :)

Кстати, переход на побайтное вычисление времени в обработчике прерывания от таймера - существенно разгружает задачу сохранения регистров в стеке .. что сильно сказывается на качестве его работы ..

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Ну ладно, а вообще, задача перенести 1-й и 2-й байты четырехбайтового числа в 0-й и 1-й байты двухбайтового числа соответственно решается только путем записи деления на 256 и больше никак?

Logik
Offline
Зарегистрирован: 05.08.2014

А чем >>8 и >>16 не устраивает. Компилятор их вроде правильно сптимизирует в байтовые перемещения.

__Alexander
Offline
Зарегистрирован: 24.10.2012

Можно по указателю на long + 1 (+2) переписать куда надо.

Logik
Offline
Зарегистрирован: 05.08.2014

Существенно дольше будет. Компилятор верно обрабатывает >> и << на значения кратные 8, только что проверил.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Версия компилятора? Уже интересно ..

Logik
Offline
Зарегистрирован: 05.08.2014

Проверял на ИДЕ  1.0.6. Настройки gcc по умолчанию. Могу еще на ИДЕ 1.6.5 глянуть, но не думаю что отломали. 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

У меня на 1.6.4 компилятор не обрабатывает верно << и >> при кратности 8. Из-за чего в своем arhat.h и делал реализацию некоторых функций на асм-вставках. Проверял многажды и не нашел способа тогда заяснить ему что надо просто взять 2 иных регистра из результата..

Может я не так их пользовал? Приведите код сдвиговой операции для получения микросекунд в виде long из регистра счетчика таймера + 3 байта от счетчика миллисекунд в wiring, очень интересно получится у вас считать регистр счетчика и вернуть 3 байта из одного места и считанный байт вместо пересылок или явных сдвигов..

Logik
Offline
Зарегистрирован: 05.08.2014

А нефиг компилятор учить оптимизировать ;) Работает - не трогай.

Код.

long a=millis();

Serial.println(a>>1);
Serial.println(a>>2);
Serial.println(a>>7);
Serial.println(a>>8);
Serial.println(a>>9);
Serial.println(a>>10);

ассемблер

     ef6:	0e 94 f4 0a 	call	0x15e8	; 0x15e8 <millis>
     efa:	7b 01       	movw	r14, r22
     efc:	8c 01       	movw	r16, r24
     efe:	b8 01       	movw	r22, r16
     f00:	a7 01       	movw	r20, r14
     f02:	75 95       	asr	r23
     f04:	67 95       	ror	r22
     f06:	57 95       	ror	r21
     f08:	47 95       	ror	r20
     f0a:	88 ef       	ldi	r24, 0xF8	; 248
     f0c:	92 e0       	ldi	r25, 0x02	; 2
     f0e:	2a e0       	ldi	r18, 0x0A	; 10
     f10:	30 e0       	ldi	r19, 0x00	; 0
     f12:	0e 94 89 0f 	call	0x1f12	; 0x1f12 <_ZN5Print7printlnEli>
     f16:	b8 01       	movw	r22, r16
     f18:	a7 01       	movw	r20, r14
     f1a:	f2 e0       	ldi	r31, 0x02	; 2
     f1c:	75 95       	asr	r23
     f1e:	67 95       	ror	r22
     f20:	57 95       	ror	r21
     f22:	47 95       	ror	r20
     f24:	fa 95       	dec	r31
     f26:	d1 f7       	brne	.-12     	; 0xf1c <setup+0xa2>
     f28:	88 ef       	ldi	r24, 0xF8	; 248
     f2a:	92 e0       	ldi	r25, 0x02	; 2
     f2c:	2a e0       	ldi	r18, 0x0A	; 10
     f2e:	30 e0       	ldi	r19, 0x00	; 0
     f30:	0e 94 89 0f 	call	0x1f12	; 0x1f12 <_ZN5Print7printlnEli>
     f34:	b8 01       	movw	r22, r16
     f36:	a7 01       	movw	r20, r14
     f38:	e7 e0       	ldi	r30, 0x07	; 7
     f3a:	75 95       	asr	r23
     f3c:	67 95       	ror	r22
     f3e:	57 95       	ror	r21
     f40:	47 95       	ror	r20
     f42:	ea 95       	dec	r30
     f44:	d1 f7       	brne	.-12     	; 0xf3a <setup+0xc0>
     f46:	88 ef       	ldi	r24, 0xF8	; 248
     f48:	92 e0       	ldi	r25, 0x02	; 2
     f4a:	2a e0       	ldi	r18, 0x0A	; 10
     f4c:	30 e0       	ldi	r19, 0x00	; 0
     f4e:	0e 94 89 0f 	call	0x1f12	; 0x1f12 <_ZN5Print7printlnEli>
     f52:	77 27       	eor	r23, r23
     f54:	17 fd       	sbrc	r17, 7
     f56:	7a 95       	dec	r23
     f58:	61 2f       	mov	r22, r17
     f5a:	50 2f       	mov	r21, r16
     f5c:	4f 2d       	mov	r20, r15
     f5e:	88 ef       	ldi	r24, 0xF8	; 248
     f60:	92 e0       	ldi	r25, 0x02	; 2
     f62:	2a e0       	ldi	r18, 0x0A	; 10
     f64:	30 e0       	ldi	r19, 0x00	; 0
     f66:	0e 94 89 0f 	call	0x1f12	; 0x1f12 <_ZN5Print7printlnEli>
     f6a:	59 e0       	ldi	r21, 0x09	; 9
     f6c:	15 95       	asr	r17
     f6e:	07 95       	ror	r16
     f70:	f7 94       	ror	r15
     f72:	e7 94       	ror	r14
     f74:	5a 95       	dec	r21
     f76:	d1 f7       	brne	.-12     	; 0xf6c <setup+0xf2>
     f78:	88 ef       	ldi	r24, 0xF8	; 248
     f7a:	92 e0       	ldi	r25, 0x02	; 2
     f7c:	b8 01       	movw	r22, r16
     f7e:	a7 01       	movw	r20, r14
     f80:	2a e0       	ldi	r18, 0x0A	; 10
     f82:	30 e0       	ldi	r19, 0x00	; 0
     f84:	0e 94 89 0f 	call	0x1f12	; 0x1f12 <_ZN5Print7printlnEli>
     f88:	15 95       	asr	r17
     f8a:	07 95       	ror	r16
     f8c:	f7 94       	ror	r15
     f8e:	e7 94       	ror	r14
     f90:	88 ef       	ldi	r24, 0xF8	; 248
     f92:	92 e0       	ldi	r25, 0x02	; 2
     f94:	b8 01       	movw	r22, r16
     f96:	a7 01       	movw	r20, r14
     f98:	2a e0       	ldi	r18, 0x0A	; 10
     f9a:	30 e0       	ldi	r19, 0x00	; 0
     f9c:	0e 94 89 0f 	call	0x1f12	; 0x1f12 <_ZN5Print7printlnEli>

Как при 1 - просто видно сдвиг всех байт, сдвиги на 2-7 - циклы таких сдвигов, сдвиг на 8 (стр.43-53 ассембера) - просто пересылки (!!!) сдвиг на 9 - сново аж 9 циклов, я ожидал не такого тут, сдвиг на 10 - я воще не "распарсил" что оно делает, ну в общем и не важно. Это 1.0.6. Если че и на 1.6.5 повторю.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015
f52:	77 27       	eor	r23, r23
f54:	17 fd       	sbrc	r17, 7
f56:	7a 95       	dec	r23
f58:	61 2f       	mov	r22, r17
f5a:	50 2f       	mov	r21, r16
f5c:	4f 2d       	mov	r20, r15

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

Сдвиг на 10 точно также "оптимизирован" .. от предыдущего результата.

Мне это напоминает древний анекдот про ИИ (ещё в эпоху БЭСМ-6):

Идет экзамен решения задач Оптимизирующим Исскуственным Интеллектом.

-"Условие: Кухня, на плите стоит пустой, чистый чайник, рядом мойка и кран с холодной водой. Задача: вскипятить воды"

(+3мин) -"О, есть простое решение: берем чайник, снимаем крышку, подставляем в мойку под кран, открываем кран, ждем заполнения, закрываем кран, ставим на плиту, включаем, ждем закипания, выключаем. Всё."

-"2 задача, Условие: Кухня, на плите чайник с водой. Задача таже, вскипятить воды"

(+10мин) -"О, найдена существенная оптимизация: выливаем воду из чайника и тем самым сводим задачу к предыдущей".

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

В догонку, еще один анекдот того же периода (навеяло):

Идет совещание руководства металопрокатного комплекса на предмет автоматизации круглосуточного непрерывного производства .. звонок от секретарши:

-"К Вам тут срочно изобретатель какой-то просится на совещание. Говорит что у него есть решение проблемы"

-"Срочно!" ..

-"Каковы первоначальные затраты на закупку и внедрение?" -"Ну ... в размере 3 мес. зарплаты троих квалифицированных сотрудников" -"Хм .. заманчиво"

-"Каков начальный период монтажа и настройки?" -"Примерно 1-3 недели" -"Вы, ничего не путаете? Как-то сильно заманчиво"

-"Каковы расходы на поддержание, ремонт?" -"Примерно 3 оклада" .. -"ВСЕГО_О?!?"

-"Каковы плановые простои при обслуживании?" -"таких может не быть вовсе" ...

... -"Молодой человек, да Вам ЦЕНЫ НЕТ, где и что это за система такая?"

-"Человек, сэр." :)

Logik
Offline
Зарегистрирован: 05.08.2014

Arhat109-2 пишет:

f52:	77 27       	eor	r23, r23
f54:	17 fd       	sbrc	r17, 7
f56:	7a 95       	dec	r23
f58:	61 2f       	mov	r22, r17
f5a:	50 2f       	mov	r21, r16
f5c:	4f 2d       	mov	r20, r15

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

А четвертый чем заполнить? Т.к. переменная знаковая, то и расширяется знаком. И кстати очень неплохо сделано. Была бы беззнаковая то осталась бы очистка старшего байта и пересылка 3-х младших. Сдвигов нет - это факт, о чем я и писал. Отрицать безполезно, полное отсутствие ror очевидно, вместо них mov.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

В продолжение: в Паскале существует, минимум, 3 способа осуществить преобразовангие 16.16->8.8 :

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_a(d : longint) : smallint;
begin
  (tb2(result)).b[0] := (tb4(d)).b[1];
  (tb2(result)).b[1] := (tb4(d)).b[2];
end;

function p32_16r8_b(d : longint) : smallint;
var
  b : array[0..3]of byte absolute d;
  i : array[0..1]of byte absolute result;
begin
  i[0] := b[1];
  i[1] := b[2];
end;

function p32_16r8_c(d : longint) : smallint;
begin
  memw[dword(@result)] := memw[dword(@d)+1];
end;

var
  i : smallint;
  d : longint;
begin
  d := $12345678;
  i := p32_16r8_a(d);
  writeln(hex(d));
  writeln(hex(i));
  i := p32_16r8_b(d);
  writeln(hex(i));
  i := p32_16r8_c(d);
  writeln(hex(i));
end.

В С/С++ пока получается следующее.

unsigned long nn[3] = {0x01020304, 0x05060708, 0x090a0b0c};
#define MEM_R_1(argument) (*((unsigned short*)((char*)(& argument) + 1)))

void loop() {
    unsigned short m0 = *((unsigned short*)(&nn[1] + 1));
    Serial.println(m0);
    unsigned short m1 = *((unsigned short*)((char*)(&nn[1]) + 1));
    Serial.println(m1);
    unsigned short m2 = MEM_R_1(nn[1]);
    Serial.println(m2);
}

код:

0000016e <loop>:
 16e:	cf 93       	push	r28
 170:	df 93       	push	r29
 172:	c0 e0       	ldi	r28, 0x00	; 0
 174:	d2 e0       	ldi	r29, 0x02	; 2
 176:	68 85       	ldd	r22, Y+8	; 0x08
 178:	79 85       	ldd	r23, Y+9	; 0x09
 17a:	4a e0       	ldi	r20, 0x0A	; 10
 17c:	50 e0       	ldi	r21, 0x00	; 0
 17e:	82 e3       	ldi	r24, 0x32	; 50
 180:	92 e0       	ldi	r25, 0x02	; 2
 182:	d9 d2       	rcall	.+1458   	; 0x736 <_ZN5Print7printlnEji>
 184:	6d 81       	ldd	r22, Y+5	; 0x05
 186:	7e 81       	ldd	r23, Y+6	; 0x06
 188:	4a e0       	ldi	r20, 0x0A	; 10
 18a:	50 e0       	ldi	r21, 0x00	; 0
 18c:	82 e3       	ldi	r24, 0x32	; 50
 18e:	92 e0       	ldi	r25, 0x02	; 2
 190:	d2 d2       	rcall	.+1444   	; 0x736 <_ZN5Print7printlnEji>
 192:	6d 81       	ldd	r22, Y+5	; 0x05
 194:	7e 81       	ldd	r23, Y+6	; 0x06
 196:	4a e0       	ldi	r20, 0x0A	; 10
 198:	50 e0       	ldi	r21, 0x00	; 0
 19a:	82 e3       	ldi	r24, 0x32	; 50
 19c:	92 e0       	ldi	r25, 0x02	; 2
 19e:	df 91       	pop	r29
 1a0:	cf 91       	pop	r28
 1a2:	c9 c2       	rjmp	.+1426   	; 0x736 <_ZN5Print7printlnEji>

т.е. лишние байты не загружаются.

Такое, правда, не получится, если хотя бы одна из переменных объявлениа как register или используется передаваемый в стеке параметр функции.

Logik
Offline
Зарегистрирован: 05.08.2014

//лишние байты не загружаются.

А какие могут быть "лишние" и "загружатся" при преобразовании long в short? Из 4 байт в 2. там только отбрасыватся 2 байта могут. Но вот эта тема из 16.16 в 8.8 сама по себе очень плохая. Старший и младший байт теряются, если младший еще ладно (но надо бы округлять его), то потеря старшего, оч серезно. Уж лучше 16.16 в 16.0. И прибавить 0,5 перед преобразованием для округления дробной. 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

16.16->8.8 лишь пример. Реально скорее всего понадобятся 16.16->16.0, 16.16->8.0, 16.16-16.8, 8.16->16.16 и пр.

И все-таки вопрос остается: как сделать подобное без привлесения адресов, например для переменных register?

Logik
Offline
Зарегистрирован: 05.08.2014

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 и подобные - просто геморой себе. Нету у нас подходящих целых типов.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

1. При таком подходе зачем-=то читаются ненужные байты (которые не попадают в финальное число).

2. Целый 24-разрядеый тип для AVR есть, называется __int24.

Logik
Offline
Зарегистрирован: 05.08.2014

andriano пишет:

1. При таком подходе зачем-=то читаются ненужные байты (которые не попадают в финальное число).

Дизассемблер в студию.

andriano пишет:

2. Целый 24-разрядеый тип для AVR есть, называется __int24.

поиск по всем *.h из arduino-1.6.5-r5\hardware не находит такового. Где Вы его видите?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Logic

1. Дизассемблированный код приведен в исходном (0-м) сообщении темы.

2. Узнал от ЕП. Проверяется экспериментально.

Logik
Offline
Зарегистрирован: 05.08.2014

andriano пишет:

Logic

1. Дизассемблированный код приведен в исходном (0-м) сообщении темы.

2. Узнал от ЕП. Проверяется экспериментально.

1. Не катит. Там нет вообще >>.

2. Экспериментально тоже отсутствует.

Код  __int24 iii;

Ошибка.

DU_Terminal.ino: In function 'void setup()':
DU_Terminal:107: error: '__int24' was not declared in this scope
DU_Terminal:107: error: expected `;' before 'iii'
Logik
Offline
Зарегистрирован: 05.08.2014

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

union jj
{
   unsigned long L;
   
   char b[4];
   };

union js
{
   unsigned short s;
   
   char b[2];
   };
   
void setup(void)
{

  
  jj k;
  js d;
 
  k.L=Serial.read();

  Serial.println(k.L);

  d.b[0]=k.b[1];
  d.b[1]=k.b[2];
  
  Serial.println(d.s);
  d.s++;
  Serial.println(d.s);

Ну и дизасс.

     42c:	c5 01       	movw	r24, r10
     42e:	b7 01       	movw	r22, r14
     430:	a6 01       	movw	r20, r12
     432:	2a e0       	ldi	r18, 0x0A	; 10
     434:	30 e0       	ldi	r19, 0x00	; 0
     436:	0e 94 54 05 	call	0xaa8	; 0xaa8 <_ZN5Print7printlnEmi>
     43a:	0d 2d       	mov	r16, r13
     43c:	1e 2d       	mov	r17, r14
     43e:	c5 01       	movw	r24, r10
     440:	b8 01       	movw	r22, r16
     442:	4a e0       	ldi	r20, 0x0A	; 10
     444:	50 e0       	ldi	r21, 0x00	; 0
     446:	0e 94 68 05 	call	0xad0	; 0xad0 <_ZN5Print7printlnEji>
     44a:	0f 5f       	subi	r16, 0xFF	; 255
     44c:	1f 4f       	sbci	r17, 0xFF	; 255
     44e:	c5 01       	movw	r24, r10
     450:	b8 01       	movw	r22, r16
     452:	4a e0       	ldi	r20, 0x0A	; 10
     454:	50 e0       	ldi	r21, 0x00	; 0
     456:	0e 94 68 05 	call	0xad0	; 0xad0 <_ZN5Print7printlnEji>

Как видно пересылки из стр.26-27 идеально записались в 7-8. Ну и математика правильно отрабатывает далее. 

ПС. в первом посте, кстати не совсем верно описана идеальная пересылка, для регистрового хранения еще лучше movw.

ППС. подумал что и до movw можна оптимизировать с более хитрым юнионом. Если не догадаетесь сами - завтра напишу.

 
ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

andriano пишет:

Ну ладно, а вообще, задача перенести 1-й и 2-й байты четырехбайтового числа в 0-й и 1-й байты двухбайтового числа соответственно решается только путем записи деления на 256 и больше никак?

на ассемблере моv 0,1 и mov 1, 2 ))) ну или как-то так, уже не помню ))

Logik
Offline
Зарегистрирован: 05.08.2014

Да. На ассемблере тоже возможно, но на юнионах проще и понятней.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

С юнионами, естественно все работает. Но хотелось бы объявлять переменные как обычные целые, а для доступа к байтам использовать переопределение типа на ходу примерно так, как у меня в 13 посте написано на Паскале в строках 3-10.

Т.е. для доступа к байтам на ходу подменить тип. Надеюсь, на С такое возможно?

Что касается Ассемблера, то не знаю я ассамблера AVR. Да и писать на нем целиком большую функцию утомительно, а делать вставку - значит, мешать оптимизатору.

Logik
Offline
Зарегистрирован: 05.08.2014

Так делайте приведение к типу. Юнион такой же тип как и все остальные. А все что получится потом в макрос загнать для удобства. А можна еще попробовать перегрузку сделать. В общем варианты есть, но их надо пробовать, на предмет оптимального результата. Т.е. чтоб не получать код длиней. Так, сразу сказать, как  повлияет (в плане не добавит ли пару или пару десятков лишних команд),  таже перегрузку я не возьмусь. Факт, ваша задача имеет решение, а остальное - подробности;) 

Так а что там с  __int24? В какой версии ИДЕ оно обнаружилось? И я бы сним был оч. осторожный, неизвестно как там с реализацией даже простых действий. И оптимизации. Нестандарт одним словом.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

И снова интересна версия компилятора. У меня локально объявленные юнионы версия gcc 4.8.1 прямиком отправляла в память вместо регистров .. что я делал не так? В общем-то это первое что приходило тогда в голову..

Logik
Offline
Зарегистрирован: 05.08.2014

//прямиком отправляла в память вместо регистров 

Может ему регистров в конкретно том коде не хватало? Так "карта лягла" у компилятора по его замыслу оптимизации.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

"тот код" Вы можете посмотреть или освежить в памяти самостоятельно. Файл arhat.c из моей библиотеки, функция micros().

Logik
Offline
Зарегистрирован: 05.08.2014

Arhat109-2, Вам уже много раз говорили куда пойти с рекламой своего барахла. Вам надоело нормальное общение по теме и хочется срача? ОК, получите. Либо конкретный код с дизассом, либо не трындите.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Logik пишет:

Так делайте приведение к типу.

Собственно, в этом и вопрос.

Проблема в том, что я не настолько хорошо знаю С/С++, чтобы сообразить, как это сделать.

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

При попытке преобразования в юнион С не знает, как это сделать, и сообщает о том, что отсутствует подходящий конструктор.

Цитата:

Так а что там с  __int24? В какой версии ИДЕ оно обнаружилось? И я бы сним был оч. осторожный, неизвестно как там с реализацией даже простых действий. И оптимизации. Нестандарт одним словом.

У меня в 1.6.5 работает. Ссылка: https://gcc.gnu.org/wiki/avr-gcc

Есть, правда, проблема, что иногда код для инт24 получается длиннее и рабротает дольше, чем для инт32.

Ну и далеко не все это понимают, например, вывексти в последовательный порт так просто не получается.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

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

Logik
Offline
Зарегистрирован: 05.08.2014

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.

Ну и нафиг она тогда такая нужна. ))

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Вероятно, я неправильно записываю:

typedef struct b4 { byte b[4]; };

void setup() {
  Serial.begin(115200);
}

unsigned long nn[3] = {0x01020304, 0x05060708, 0x090a0b0c};

void loop() {
    unsigned short m3 = reinterpret_cast<short>((reinterpret_cast<b4>(nn[1])).b[1]);
    Serial.println(m3);
}



Arduino: 1.6.5 (Windows XP), Board: "Arduino Mega or Mega 2560, ATmega2560 (Mega 2560)"

D:\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 -ID:\Arduino\arduino-1.6.5-r2\hardware\arduino\avr\cores\arduino -ID:\Arduino\arduino-1.6.5-r2\hardware\arduino\avr\variants\mega c:\Tmp\build4054785896209395205.tmp\byte_pr.cpp -o c:\Tmp\build4054785896209395205.tmp\byte_pr.cpp.o 
byte_pr.ino: In function 'void loop()':
byte_pr.ino:101:76: error: invalid cast from type 'long unsigned int' to type 'b4'
invalid cast from type 'long unsigned int' to type 'b4'

 

Logik
Offline
Зарегистрирован: 05.08.2014

Шото оно не похоже. Скорей так.

union jj
{
   unsigned long L;
   
   char b[4];
   struct {
     byte b1;
     unsigned short ss;
   };

   };


 
#define Get1and2Byte(L) (reinterpret_cast<jj*>(&L))->ss

..........
  unsigned short r1= Get1and2Byte(L1);

Старое char b[4]; можна убрать, т.к. не используется, правда и не влияет.

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Logik пишет:

Шото оно не похоже. Скорей так.

union jj
{
   unsigned long L;
   
   char b[4];
   struct {
     byte b1;
     unsigned short ss;
   };

   };


 
#define Get1and2Byte(L) (reinterpret_cast<jj*>(&L))->ss

..........
  unsigned short r1= Get1and2Byte(L1);

Старое char b[4]; можна убрать, т.к. не используется, правда и не влияет.

 

Как-то оно все равно не то, что хочется.

Что получилось:

unsigned long nn[3] = {0x01020304, 0x05060708, 0x090a0b0c};
#define MEM_R_1(argument) (*((unsigned short*)((char*)(& argument) + 1)))

void loop() {
    unsigned short m0 = *((unsigned short*)(&nn[1] + 1));
    Serial.println(m0);
    unsigned short m1 = *((unsigned short*)((char*)(&nn[1]) + 1));
    Serial.println(m1);
    unsigned short m2 = MEM_R_1(nn[1]);
    Serial.println(m2);
    unsigned short m3 = reinterpret_cast<U1*>(&nn[1])->i;
    Serial.println(m3);
}

транслируется в:

0000016e <loop>:
 16e:	cf 93       	push	r28
 170:	df 93       	push	r29
 172:	c0 e0       	ldi	r28, 0x00	; 0
 174:	d2 e0       	ldi	r29, 0x02	; 2
 176:	68 85       	ldd	r22, Y+8	; 0x08
 178:	79 85       	ldd	r23, Y+9	; 0x09
 17a:	4a e0       	ldi	r20, 0x0A	; 10
 17c:	50 e0       	ldi	r21, 0x00	; 0
 17e:	82 e3       	ldi	r24, 0x32	; 50
 180:	92 e0       	ldi	r25, 0x02	; 2
 182:	e2 d2       	rcall	.+1476   	; 0x748 <_ZN5Print7printlnEji>
 184:	6d 81       	ldd	r22, Y+5	; 0x05
 186:	7e 81       	ldd	r23, Y+6	; 0x06
 188:	4a e0       	ldi	r20, 0x0A	; 10
 18a:	50 e0       	ldi	r21, 0x00	; 0
 18c:	82 e3       	ldi	r24, 0x32	; 50
 18e:	92 e0       	ldi	r25, 0x02	; 2
 190:	db d2       	rcall	.+1462   	; 0x748 <_ZN5Print7printlnEji>
 192:	6d 81       	ldd	r22, Y+5	; 0x05
 194:	7e 81       	ldd	r23, Y+6	; 0x06
 196:	4a e0       	ldi	r20, 0x0A	; 10
 198:	50 e0       	ldi	r21, 0x00	; 0
 19a:	82 e3       	ldi	r24, 0x32	; 50
 19c:	92 e0       	ldi	r25, 0x02	; 2
 19e:	d4 d2       	rcall	.+1448   	; 0x748 <_ZN5Print7printlnEji>
 1a0:	60 91 05 02 	lds	r22, 0x0205
 1a4:	70 91 06 02 	lds	r23, 0x0206
 1a8:	4a e0       	ldi	r20, 0x0A	; 10
 1aa:	50 e0       	ldi	r21, 0x00	; 0
 1ac:	82 e3       	ldi	r24, 0x32	; 50
 1ae:	92 e0       	ldi	r25, 0x02	; 2
 1b0:	df 91       	pop	r29
 1b2:	cf 91       	pop	r28
 1b4:	c9 c2       	rjmp	.+1426   	; 0x748 <_ZN5Print7printlnEji>

и что имеем:

1. От адресов уйти так и не удалось - если я объявлю переменную как register (а это вполне желательно, т.к. это результат промежуточных вычислений), то все адресные операции пойдут лесом.

2. Ну и по мелочам: когда мы работаем напрямую с адресом, база грузится в регистровую пару R29:R28 и затем байты извлекаются по смещению двухбайтовой операцией, а в случае reinterpret_cast каждый байт извлекается по абсолютному адресу, что приводи к тому, что загрузка каждого байта осуществляется 4-байтовой операцией, которая, вероятнее всего, еще и выполняется вдвое дольше.

 

В общем, вопрос остается: как средствами C/C++ переписать 2 средних байта четырехбайтовой переменной в двухбайтовую без использования адресов (считая, что 4-байтовая переменная имеет квалификатор register)?