/* * dec_print.c — компактная десятичная печать, портировано из solid-c * (third_party/solid-c/SRC/CLIB/STDLIB.ASM, модули dec8/dec16/dec32). * * void dec8 (uint8_t v) — без ведущих нулей, "0" .. "255" * void dec16(uint16_t v) — без ведущих нулей, "0" .. "65535" * void dec32(uint32_t v) — без ведущих нулей, "0" .. "4294967295" * * Алгоритм: вычитание степени десяти со счётчиком, цифра печатается, * если ведущие нули уже кончились. 32-битные значения живут в паре * BC/DE/HL плюс теневой набор (exx) для старшей половины. * * Раскладка: dec32 — главная рутина; dec8/dec16 — крошечные обёртки, * готовящие состояние и прыгающие на внутренние входы (__dec_entry3 — * «последние 3 цифры», __dec_entry5 — «последние 5») — как в solid-c, * per-digit код общий. Поэтому файл сознательно НЕ разрезан на три * модуля: dec8 всё равно притянул бы тело dec32 * (см. docs/libc-split-asm-cases.md, случай 1). * * ESTEX PUTCHAR ($5B) сохраняет IX (проверено эмпирически), так что * push/pop ix вокруг RST не нужен. */ #include #include /* Флаг подавления ведущих нулей: 0 — «всё ещё пропускаем нули», * не-0 — «первая значащая цифра встречена, дальше печатаем всё * (включая последующие нули)». */ static uint8_t dec_flag; void dec8(uint8_t v) __naked { (void)v; __asm ;; A = v. ld l, a ld h, #0 ; HL = значение xor a, a ld (_dec_flag), a ; сбросить флаг подавления нулей jp __dec_entry3 __endasm; } void dec16(uint16_t v) __naked { (void)v; __asm ;; HL = v. exx ld hl, #0 ; HL-альт = 0 (старшие 16 составного) exx xor a, a ld (_dec_flag), a jp __dec_entry5 __endasm; } void dec32(uint32_t v) __naked { (void)v; __asm ;; На входе HL = high16, DE = low16 (SDCC HLDE). ;; Старшие 16 — в HL-альт (теневой набор), младшие — в HL. push hl exx pop hl ; HL-альт = high16 exx ex de, hl ; HL = low16 xor a, a ld (_dec_flag), a ;; ---- 5 старших декад (1e9..1e5) ---- ld de, #0xCA00 exx ld de, #0x3B9A ; 0x3B9ACA00 = 1,000,000,000 exx call _dec_get_d32 ld de, #0xE100 exx ld de, #0x05F5 ; 0x05F5E100 = 100,000,000 exx call _dec_get_d32 ld de, #0x9680 exx ld de, #0x0098 ; 0x00989680 = 10,000,000 exx call _dec_get_d32 ld de, #0x4240 exx ld de, #0x000F ; 0x000F4240 = 1,000,000 exx call _dec_get_d32 ld de, #0x86A0 exx ld de, #0x0001 ; 0x000186A0 = 100,000 exx call _dec_get_d32 __dec_entry5:: ; вход из dec16 ld de, #10000 exx ld de, #0 exx call _dec_get_d32 ld de, #1000 call _dec_get_d16 __dec_entry3:: ; вход из dec8 ld de, #100 call _dec_get_d16 ld de, #10 call _dec_get_d16 ;; Цифра единиц — печатается всегда (dec*(0) выводит "0"). ld a, l add a, #0x30 ld c, #0x5B rst #0x10 ret ;; ---- 32 бита: сколько раз DE+DE-альт укладывается в HL+HL-альт ---- _dec_get_d32: ld a, #0x2F ; 0x2F = символ перед 0 (пред-декремент) and a, a ; CF = 0 _dec_get_d32_loop: inc a sbc hl, de ; младшая половина exx sbc hl, de ; старшая половина (с цепочкой заёма) exx jp nc, _dec_get_d32_loop ;; Перебрали — вернуть последнее корректное значение. add hl, de exx adc hl, de exx jr _dec_emit_or_skip ;; ---- 16 бит: сколько раз DE укладывается в HL ---- _dec_get_d16: ld a, #0x2F and a, a _dec_get_d16_loop: inc a sbc hl, de jp nc, _dec_get_d16_loop add hl, de ;; Fall-through в emit/skip. _dec_emit_or_skip: ;; A = символ цифры 0x30..0x39. Не-ноль защёлкивает флаг. ;; Печатаем только при взведённом флаге. ld b, a ; спасти цифру на время проверки флага cp a, #0x30 jr z, _dec_check_flag ld (_dec_flag), a ; встречена значащая цифра _dec_check_flag: ld a, (_dec_flag) or a, a ld a, b ; вернуть цифру (ld флаги не трогает) ret z ; ведущий ноль — печать пропустить ;; ESTEX PUTCHAR ($5B) сохраняет основной набор (BC, DE, HL, ;; IX), но КЛОББЕРИТ теневой (BC/DE/HL-альт). Цикл вычитания ;; степени десяти в _dec_get_d32 держит старшие 16 бит остатка ;; в HL-альт, поэтому HL-альт сохраняем вокруг RST. Основной ;; HL (младшие 16 остатка) переживает вызов сам, спасать не ;; нужно. См. memory/estex_putchar_abi.md. exx push hl exx ld c, #0x5B rst #0x10 exx pop hl exx ret __endasm; }