Частный случай конвертера IEEE754 в строку.

Частный случай — потому что вывод немного нестандартный. Вместо классического 1.23E5 эта процедура отдаёт числа вида 123456.789, соответственно диапазон сжат до (скажем так) употребимых значений. Сверху вывод ограничен числом 16777215, а снизу числом 0.0001.
Как это реализовано? Достаточно просто — float конвертируется в Int, методом денормализации. Отбрасываемые в ходе денормализации биты не теряются, а накапливаются в качестве дробной части числа. Для получения четырёх (а зачем больше?) знаков после запятой, дробная часть умножается на 10000.
Оба получившихся целых числа конвертируются в строки, строки объединяются и форматируются. Всё.

Подлежащее конверсии число передаётся в процедуру внутри первого аккумулятора.
Целочисленная математическая библиотека использует два аргумента — аргумент var1 хранится в регистрах r2~r9 и имеет размер до 64х бит. Аргумент var2 хранится в регистрах r10~r13 и имеет размер до 32х бит.
Эта асимметрия имеет естественную природу, поскольку умножение 32-битных чисел даёт в итоге 64 бита, и размеры аргументов позволяют провести операцию вида var1=var1*var2, для восьми-, шестнадцати-. двадцатичетырёх-, и 32-битных чисел, и обеспечить место для результата.
Два аргумента с плавающей точкой хранятся там же, только в некотором роде «наоборот». Первый аккумулятор занимает var2, а второй — var1, сейчас поясню почему именно так. Каждый из аккумуляторов имеет размер 32 бита, и соответственно занимает 4 регистра; число, хранящееся в этих регистрах имеет стандартный формат ieee754. Когда с числом производятся какие-либо действия, оно распаковывается — младшие 24 бита занимает нормализованная мантисса, старшие 8 бит занимает экспонента в дополнительном коде, а знак временно хранится в регистре T. Псевдонимы регистров, хранящих мантиссу — mant1l, mant1m, mant1h — младший, средний и старший байты соответственно. в exp1 находится экспонента. После математических действий число опять упаковывается в формат ieee или теряется, если не требуется.
И если хранить первый аккумулятор там же, где хранится первый аргумент для целочисленных процедур — то станет невозможным использование для работы с float уже готовых процедур целочисленного умножения — расширяясь в ходе умножения, первый аргумент затрёт содержимое экспоненты первого float. А второй float и так типа жертвенный, после операции он не потребуется, пусть его ломает, не жалко. Вот как-то так.

Источником вдохновения послужил распостранённый в сети текст, содержащий строку
“;**** FP Math Routines: ****
; ** Very similar to _ecvt() **”
Именно оттуда была взята заготовка для конвертера float=>int, опилена напильником, дополнена сохранением дробной части и выводилками.
Как всегда, текст можно целиком скопировать в новый проект студии, и погонять под отладчиком, глядя на результат.
Обратите внимание — при формировании строки она создаётся с хвостом из ведомых нулей, которые потом отбрасываются вставлением AZ терминатора. При этом символы нулей физически остаются в памяти, но никому не мешают…
Ещё одна особенность — ошибка в один отсчёт на экстремально малых значениях, например вместо 0.001 выводится 0.0009, но это связано взаимодействием двух факторов — во первых, сам по себе float дыряв как дуршлаг на голове пастафарианца, и физически не содержит числа 0,001. Вместо него используется число 0.0010000000475, и когда при формировании дробной части отсекаются биты, это число превращается в 0.000999994575977, затем выводилка отбрасывает пятый и последующие знаки после десятичной точки — и мы имеем что имеем.
Поэтому добрый дядя Питер Нортон в своём руководстве по программированию и предупреждал нас — если вам дороги жизнь и рассудок, никогда не гуляйте по Гримпенской пустоши если вы не хотите наступить на очень неприятные артефакты, никогда не считайте деньги во float.

; Первый аргумент
.def var10=r2
.def var11=r3
.def var12=r4
.def var13=r5
.def var14=r6
.def var15=r7
.def var16=r8
.def var17=r9
; второй аргумент
.def var20=r10
.def var21=r11
.def var22=r12
.def var23=r13
.def var24=r14
.def var25=r15

.def temp=r16

; Первый аккумулятор соответствует VAR2, чтобы сохранить компактность и избежать повреждения порядка при умножении
.def	mant1l	=	r10
.def	mant1m	=	r11
.def	mant1h	=	r12
.def	exp1	=	r13
; второй аккумулятор соответствует VAR1
.def	mant2l	=	r2
.def	mant2m	=	r3
.def	mant2h	=	r4
.def	exp_2	=	r5




.dseg
strbuf:	.byte 8

.cseg
; стек
		ldi	r16, low(RAMEND)
		out	spl, r16
		ldi	r16, high(RAMEND)
		out	sph, r16
; указатель на буфер для вывода строк
		ldi	xh, high(strbuf)
		ldi	xl, low (strbuf)
; указатель на массив тестовых чисел 
		ldi	zh, high(TestData<<1)
		ldi	zl, low (TestData<<1)
; девять раз будем повторять  для 9ти разных чисел.		
		ldi	r17, 9
Convertloop:		
		rcall	Fldz1		; загрузили float
		rcall	F2str		; выгрузили ASCIIZ string (смотри дамп памяти)
		dec	r17
		brne	Convertloop


		rjmp	pc	; Halt. Поставьте сюда брейкпоинт при отладке


TestData:	.dd	0xc9cccccc	; -1677721.5
		.dd	0x4b7fffff	; 16777215
		.dd	0x449a4005	; 1234.0006
		.dd	0x47f12040	; 123456.5
		.dd	0x4641cd80	; 12403.375
		.dd	0x43b92000	; 370.25
		.dd	0x3c000000	; 0.0078125
		.dd	0x3a83126f	; 0.001
		.dd	0x399d4952	; 0.0003



;****************************************************************************
; Конверсия Float из первого аккумулятора в строку.
; 360-911 тактов
; На входе - iee754 single в первом аккумуляторе.
; X pointer - указатель на буфер для текстовой строки.
; Результат работы - ASCIIZ строка типа 123.45 в буфере. 
; 4 знака после точки, нули в хвосте отброшены.
; слишком малые числа представляет как "0.0" слишком большие как "tolrg"
; Портит первый и второй аккумуляторы, то есть var20~var25 и var10~var17 
; также портит r16 и перемещает указатель X на конец строки
;****************************************************************************
F2str:		bst	exp1, 7		; знак отправляем в T
		brtc	pc+3
		ldi	temp, '-'	; если знак отрицательный
		st	x+, temp	; начинаем строку с минуса
;---------------------------------------		
		ldi	temp,0x80
		lsl	mant1h		; старший бит из мантиссы 
		rol	exp1		; переносится в экспоненту
		eor	exp1, temp	
		sec			; единицу
		ror	mant1h		; задвигаем в старший бит мантиссы
		cp	exp1, temp	; проверка на ноль
		brne	pc+2		
		rjmp	ftstrzres	; Ответ: 0,0
; Проверка на результат больше 16777215
		ldi	temp, 22
		sub	temp, exp1
		brpl	pc+2
		rjmp	ftstrmaxres	; Ответ: tolrg
; Проверка на слишком малое число
		cpi	temp, 36	
		brlo	pc+2
		rjmp	ftstrzres	; Ответ: 0,0
 		clr	var24
		clr	var25
 ; Быстрый сдвиг на 8 порядков.
		cpi	temp,8	; use fast byte-move, if possible
		brlo	pc+8
		mov	var24, var25		; выдвигаем дробную часть в var24:25
		mov	var25, mant1l
		mov	mant1l, mant1m
		mov	mant1m, mant1h
		clr	mant1h
		subi	temp,8
		rjmp	pc-8
; После быстрых сдвигов порядок не нулевой ?
		tst	temp
		breq	pc+8	; не делаем сдвигов, если не надо
; Делаем сдвиги по одному порядку за раз
		lsr	mant1h
		ror	mant1m
		ror	mant1l
		ror	var25
		ror	var24
		dec	temp
		brne	pc-6
; и вот тут в mant1 находится целая часть
; а в var24:25 дробная.
		mov	var10, mant1l
		mov	var11, mant1m
		mov	var12, mant1h
		rcall	Bin2str24a
		ldi	temp, '.'	; десятичная точка
		st	x+, r16
; дробную часть множим на 10k
; кастомная процедура var10 = (var24*10000)/65536 временные var12 r16
		ldi	temp, low (10000)
		mul	var25, temp	; bx
		mov	var12, r0
		mov	var10, r1
		mul	var24, temp	; by
		add	var12, r1
		adc	var10, var16
		adc	var11, var16
		ldi	temp, high(10000)
		mul	var24, temp	; ay
		add	var12, r0
		adc	var10, r1
		adc	var11, var16
		mul	var25, temp	; ax
		add	var10, r0
		adc	var11, r1
; конец хитрого умножения на 10000
; Теперь в var10:11 у нас число 0~9999, отражающее дробную часть
; Преобразуем его в строку. Алгоритм, формирующий обратную строку тут будет полезен
; возможностью быстро дополнить строку ведущими нулями
		rcall	Bin2str16b
		ldi	temp, 0x04		
; Дополнение строки ведущими нулями		
		mov	var10, temp
		ldi	temp, 0x30	; "0"
		cp	var17, var10	; == strlen ?
		brcc	pc+4	
		st	x+, temp	; укладываем "ноль" в строку 
		inc	var17		; до достижения строкой нужной длины
		rjmp	pc-4
; Переворот обратной строки - загрузка
		ld	var10, -x
		ld	var11, -x
		ld	var12, -x
		ld	var13, -x
; и сохранение в другом порядке
		st	x+, var10
		st	x+, var11
		st	x+, var12
		st	x+, var13
; Удаление завершающих нулей (если есть)
		ld	r16, -x		; извлекаем последний символ из строки
		cpi	r16, 0x30	; "ноль" ?
		brne	pc+3		
		dec	var17		; повторяем только до ограничителя, 
		brne	pc-4		; чтобы в любом случае оставить один ноль после точки
		adiw	xl, 1
		st	x+, var16	; ZS terminator 
		ret
; Результат: tolrg
ftstrmaxres:	ldi	r16, 't'
		st	x+, r16
		ldi	r16, 'o'
		st	x+, r16
		ldi	r16, 'l'
		st	x+, r16
		ldi	r16, 'r'
		st	x+, r16
		ldi	r16, 'g'
		st	x+, r16
		ldi	r16, 0
		st	x+, r16
		ret
; Результат: 0.0
ftstrzres:	ldi	r16, 0x30
		st	x+, r16
		ldi	r16, 0x2e
		st	x+, r16
		ldi	r16, 0x30
		st	x+, r16
		ldi	r16, 0
		st	x+, r16
		ret



		
;****************************************************************************
; Загрузка первого аккумулятора из памяти программ по указателю Z
;****************************************************************************
Fldz1:		lpm	mant1l, z+
		lpm	mant1m, z+
		lpm	mant1h, z+
		lpm	exp1, z+
		ret


; Дальши пошли процедуры конвертеров число-строка. 
; Отдельно для 24х бит, отдельно для 16-ти. 
; впрочем, можно использовать только одну - немного упадёт быстродействие, и надо будет изменить процедуру 
; добавления ведущих нулей и удаления ведомых








;****************************************************************************
; Преобразование 16бит числа в строку, используя быстрое деление на 10
; авторство кода http://forum.easyelectronics.ru/viewtopic.php?p=240893&sid=8d8b529647d46d09ac70820dbbf982e8#p240893
; назначение регистров var10:11 - число, на выходе строка
; Временные регистры: var13 - temporary lsb; var14:15 = var10/0x0A  var16 = zero
; На выходе - "обратная строка" по указателю в X,  lsb впереди, msb сзади.
; var17 = счётчик глубины строки
; 48 тактов на числе 5 (наилучшее время) 
; 208 тактов на числе 12345 (наихудшее время)
;****************************************************************************
Bin2str16b:     clr     var17           ; strlen count init
; начало алгоритма быстрого деления на 10
                clr     var16           ; zero
bin16tobcdloop:
                ldi     r16, 0xcd
                mul     r16, var10
                mov     var13, r1       
                ldi     r16, 0xcc
                mul     r16, var11
                mov     var14, r0
                mov     var15, r1
                mul     r16, var10
                add     var13, r0
                adc     var14, r1
                adc     var15, var16    ; +zero
                ldi     r16, 0xcd
                mul     r16, var11
                add     var13, r0
                adc     var14, r1
                adc     var15, var16    ; +zero
    // quotient >>= 3
                lsr     var15
                ror     var14
                lsr     var15
                ror     var14
                lsr     var15
                ror     var14
 ; Конец алгоритма быстрого деления на 10. до этого момента - 28 тактов.  
    // multiple quotient back (*10)
                ldi     r16, 10
                mul     var14, r16      ; Нас интересует только младший разряд, остальные не участвуют в формировании остатка
                sub     var10, r0               
; В этот момент - в var14:15 число, делённое на 10, в var10 - остаток 
                ldi     r16, 0x30
                add     r16, var10      ; bcd 2 ascii
                st	x+, r16         ; store it
                inc     var17           ; count++
; результат деления опять помещаем в делимое и повторяем при необходимости
                mov     var10, var14
                mov     var11, var15
                or      var15, var14    ; zero?
                brne    bin16tobcdloop
		ret

;****************************************************************************
; Преобразование 24 бит беззнакового числа в строку
; по следам публикаций http://we.easyelectronics.ru/Soft/preobrazuem-v-stroku-chast-2-chisla-s-fiksirovannoy-i-plavayuschey-tochkoy.html
; и http://forum.easyelectronics.ru/viewtopic.php?p=240893&sid=8d8b529647d46d09ac70820dbbf982e8#p240893
; низкооптимизированная версия, заточенная на минимизацию использованных регистров и естественное представление строки
; назначение регистров var10:11:12 - число, var10 lsb
; На выходе строка по указателю в X, естественное представление, готова для вывода
; портит r17, r16, var1x 
;****************************************************************************
Bin2str24a:     clr     var17           ; str count clear
                clr     var16           ;  0 
; Цикл - последовательное деление на 10
bin24tostrloop:
; Начало алгоритма быстрого деления 24 бит на константу 10                
		clr     var15           
; var11~15 = var1x * 0xcccccd           (abc* xyz)
                ldi     r16, 0xCC
                mul     r16, var12      ; a*x | a*y
                mov     var13, r0
                mov     var14, r0
                add     var14, r1
                adc     var15, r1
		inc	r16         
                mul     r16, var12      ; a*z
                add     var12, r0       
                adc     var13, r1
                adc     var14, var16    ; +zero
                adc     var15, var16    ; +zero
		dec	r16
                mul     r16, var11      ; b*x | b*y
                mov     var12, r0
                add     var13, r0
                adc     var14, r1
                adc     var15, var16
                add     var13, r1
                adc     var14, var16    ; +zero
                adc     var15, var16    ; +zero
		inc	r16

                mul     r16, var11      ; b*z
                mov     var11, r0
                add     var12, r1
                adc     var13, var16    ; +zero
                adc     var14, var16    ; +zero
                adc     var15, var16    ; +zero
		dec	r16

                mul     r16, var10      ; c*x | c*y
                add     var12, r0
                adc     var13, r1
                adc     var14, var16    ; +zero
                adc     var15, var16    ; +zero

                add     var11, r0
                adc     var12, r1
                adc     var13, var16    ; +zero
                adc     var14, var16    ; +zero
                adc     var15, var16    ; +zero

		inc	r16
                mul     r16, var10      ; c*z
                add     var11, r1
                adc     var12, var16    ; +zero
                adc     var13, var16    ; +zero
                adc     var14, var16    ; +zero
                adc     var15, var16    ; +zero
; сдвиг результата вправо на 3 бита
                lsr     var15
                ror     var14
                ror     var13
;-----------------------------
                lsr     var15
                ror     var14
                ror     var13
;-----------------------------
                lsr     var15
                ror     var14
                ror     var13
; Конец алгоритма быстрого деления на 10  
;----------------------------- 
; multiple quotient back (*10)
; математика в этой вселенной устроена таким образом, что если разделить число на 10, округлить и умножить на 10 снова
; то все разряды, кроме последнего не будут отличаться! Вы, может быть удивитесь, но это так. 
; И при поразрядном умножении, только младший разряд множителя участвуют в формировании младшего разряда результата. Я проверял, это всегда так!
; Поэтому, чтобы найти остаток деления, не обязательно множить и вычитать всё число. Достаточно проделать этот трюк только с последним разрядом.
; можете меня не благодарить, ваш КЭП.
                ldi     r16, 10
                mul     var13, r16      ; Нас интересует только младший разряд, остальные не участвуют в формировании остатка 
                sub     var10, r0       ; вычитаем из оригинала делённое- умноженное - округлённое.     
                ldi     r16, 0x30
                add     r16, var10      ; bcd 2 ascii
		push	r16
                inc     var17           ; count++
; результат деления опять помещаем в делимое и повторяем процесс при необходимости
                mov     var10, var13
                mov     var11, var14
                mov     var12, var15
                or      var13, var15    ; zero?
                or      var13, var14
                breq    pc+2
                rjmp    bin24tostrloop
; извлечение строки из стека и укладка в буфер
		pop	r16
		st	x+, r16
		dec	var17
		brne	pc-3
                ret             

  • +5
  • 23 ноября 2020, 13:43
  • Gornist
  • 1
Файлы в топике: fl2str.zip

Комментарии (0)

RSS свернуть / развернуть
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.