unsigned to ascii

The place for codemasters or beginners to talk about programming any language for the Spectrum.
Post Reply
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

unsigned to ascii

Post by dfzx »

I'm slowly learning Z88DK C and writing a game using it. I've recently added a score to the game which basically involves keeping a 16 bit value which is decremented on an interrupt. (i.e. the score goes down as you play, meaning the quicker you complete the level the better the score.) This score is printed on screen and is currently updated 10 times a second.

The problem is that the code to convert the value to an ASCII string for printing is slow. Slower than I expected, certainly. Z88DK has a utoa() function in the library ("unsigned to ASCII") which is hand coded assembler, but it takes a radix and uses a subroutine to do the division so it can handle big numbers. It's all a bit heavyweight for what I need.

So I thought I'd write my own, which I did. For those who know C:

Code: Select all

void my_utoa( uint16_t i, uint8_t* next_char )
{
  do
  {
    *next_char = (i % 10) + '0';
    next_char--;
    i = i / 10;
  } while(i);

  return;
}
But that compiles to rather slow and heavyweight assembly too. The modulo operator is a subroutine call, as is the division.

I was wondering if this utoa() operation is just bound to be slow on a Z80, or maybe there's a faster, lighter way to do it in assembly language? The requirement is to convert a number which I'm happy to restrict to the range 0-32767 into a 6 character string (including null terminator byte) with zeroes padding to the left. e.g. input of 320 would result in "00320\0". Speed is more important than code size at this point in time.

What approach would I take to do that operation in assembly language?
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
AndyC
Dynamite Dan
Posts: 1388
Joined: Mon Nov 13, 2017 5:12 am

Re: unsigned to ascii

Post by AndyC »

One common solution is to use BCD to store the number, as it's a lot quicker to convert a BCD number into ASCII because you don't have to deal with any divides (just shifts) and the Z80 can do BCD arithmetic almost as fast as native numbers.
User avatar
R-Tape
Site Admin
Posts: 6353
Joined: Thu Nov 09, 2017 11:46 am

Re: unsigned to ascii

Post by R-Tape »

Is it an option to avoid the conversion altogether by only handling it as a string? It would be much quicker to check when a string is ‘00000’.
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: unsigned to ascii

Post by dfzx »

Thanks for the input guys. I think it's dawned on me why so many games have non-numerical countdown or scoring systems. Numbers are tricky to deal with, as well as dull to look at. Gauges, sliders and half eaten turkeys have been deployed for more than one reason.

The BCD thing rang all sorts of 30 year old bells. :) I've not touched BCD since I moved on from the Speccy back in the 80s. I'll bear that one in mind, but for the game I think a bit more imagination in the scoring will make the problem go away.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
spectron
Drutt
Posts: 25
Joined: Thu Mar 29, 2018 3:27 pm

Re: unsigned to ascii

Post by spectron »

As above, if it's a simple score counting down, just store it in consecutive bytes as ascii chars (48-57 decimal). To decrement the score just decrement the bottom char and if it goes to 47 ('/' char), set it back to 57 then go to the next char and decrement that. That way the score is always in the right format for printing.
User avatar
Metalbrain
Microbot
Posts: 107
Joined: Thu Feb 15, 2018 2:14 pm

Re: unsigned to ascii

Post by Metalbrain »

dfzx wrote: Thu Oct 04, 2018 8:07 pm The requirement is to convert a number which I'm happy to restrict to the range 0-32767 into a 6 character string (including null terminator byte) with zeroes padding to the left. e.g. input of 320 would result in "00320\0". Speed is more important than code size at this point in time.

What approach would I take to do that operation in assembly language?
I'd say the easiest solution would be to subtract powers of 10, something like this (not tested):

Code: Select all

	ld hl,NUMBER
	ld bc,result_location

	ld de,-10000
	ld a,47
loop1:
	inc a
	add hl,de
	jr c,loop1
	sbc hl,de
	ld (bc),a
	inc bc

	ld de,-1000
	ld a,47
loop2:
	inc a
	add hl,de
	jr c,loop2
	sbc hl,de
	ld (bc),a
	inc bc

	ld de,-100
	ld a,47
loop3:
	inc a
	add hl,de
	jr c,loop3
	sbc hl,de
	ld (bc),a
	inc bc

	ld de,-10
	ld a,47
loop4:
	inc a
	add hl,de
	jr c,loop4
	sbc hl,de
	ld (bc),a
	inc bc

	ld a,48
	add a,l
	ld (bc),a
	inc bc

	xor a
	ld (bc),a
Bizzley
Microbot
Posts: 124
Joined: Thu Nov 16, 2017 10:47 am

Re: unsigned to ascii

Post by Bizzley »

Here's another version of the 'powers of 10' method. This one is rolled up with CALLs. Enter with HL holding number to convert.

Code: Select all

conv16bit	ld bc,mess16bit
		ld de,10000
		call conv16bit1
		ld de,1000
		call conv16bit1
		ld de,100
		call conv16bit1
		ld de,10
		call conv16bit1
		ld a,l
		jr conv16bit3
conv16bit1	ld a,-1
conv16bit2	inc a
		sub hl,de
		jr nc,conv16bit2
		add hl,de
conv16bit3	or 48
		ld (bc),a
		inc bc
		ret
mess16bit	ds 5,0
"He made eloquent speeches to an audience consisting of a few depressed daffodil roots, and sometimes the cat from next door."
AndyC
Dynamite Dan
Posts: 1388
Joined: Mon Nov 13, 2017 5:12 am

Re: unsigned to ascii

Post by AndyC »

spectron wrote: Fri Oct 05, 2018 2:13 pm As above, if it's a simple score counting down, just store it in consecutive bytes as ascii chars (48-57 decimal). To decrement the score just decrement the bottom char and if it goes to 47 ('/' char), set it back to 57 then go to the next char and decrement that. That way the score is always in the right format for printing.
Or even just bytes holding 0-9, so you can use the carry flag to spot a number rolling past 0 - it's not like your print routine has to use ASCII codes for 0-9 after all.
User avatar
Metalbrain
Microbot
Posts: 107
Joined: Thu Feb 15, 2018 2:14 pm

Re: unsigned to ascii

Post by Metalbrain »

Bizzley wrote: Fri Oct 05, 2018 4:01 pm

Code: Select all

		sub hl,de
Unfortunately, that one doesn't exist.
Bizzley
Microbot
Posts: 124
Joined: Thu Nov 16, 2017 10:47 am

Re: unsigned to ascii

Post by Bizzley »

Metalbrain wrote: Fri Oct 05, 2018 4:20 pm
Bizzley wrote: Fri Oct 05, 2018 4:01 pm

Code: Select all

		sub hl,de
Unfortunately, that one doesn't exist.
It does if you use the SjASMPLus Assembler. It's one of the "fake" instructions the assembler supports and then internally converts to an equivalent. I'm so used to using them now I forget that other assemblers don't support them so my bad. Replace it with :

or a
sbc hl,de

for compatibilty with other assemblers. Actually you don't really need the 'or a' in this case.
"He made eloquent speeches to an audience consisting of a few depressed daffodil roots, and sometimes the cat from next door."
Alcoholics Anonymous
Microbot
Posts: 194
Joined: Mon Oct 08, 2018 3:36 am

Re: unsigned to ascii

Post by Alcoholics Anonymous »

dfzx wrote: Thu Oct 04, 2018 8:07 pm Z88DK has a utoa() function in the library ("unsigned to ASCII") which is hand coded assembler, but it takes a radix and uses a subroutine to do the division so it can handle big numbers. It's all a bit heavyweight for what I need.
The utoa function actually has optional special cases for bases 2, 8, 10 and 16 which can be enabled in the library configuration. The base 10 special case is enabled by default so you should get a quick base 10 conversion. But it does have a few extras as you say like a paremeter that takes radix and checks that the radix is legal.

You can call the lower level asm library code directly though.

The small implementation of decimal utoa is l_small_utoa and can be called from asm like this:

Code: Select all

EXTERN l_small_utoa
...
call l_small_utoa
You can write your own lightweight c interface to call from c.

The fast implementation is l_fast_utoa and can be called like this:

Code: Select all

EXTERN l_fast_utoa
...
call l_fast_utoa
The difference is it will check if the number is < 256 and do an 8-bit version instead of going through the motions of a full 16-bit conversion.

A nice feature of these functions is that you can ask for leading 0s by setting the carry flag before calling. So "1234" will actually convert to "01234", ie zero padded for 5 decimal digits.
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: unsigned to ascii

Post by dfzx »

Ah, thanks AA, that all makes sense. I saw everything you describe in the Z88DK library sources but either couldn't figure out what it was or couldn't figure out how to use it. :)

I wrote a bunch of test cases over the weekend, timing some of the ideas offered here. I was going to post the results here later today, but now I need to give these things you suggest a try. I don't think anything is going to get near the "store it in a string and rotate the chars" approach for speed, but since that's also the least flexible approach I'll probably end up using something else.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
Alcoholics Anonymous
Microbot
Posts: 194
Joined: Mon Oct 08, 2018 3:36 am

Re: unsigned to ascii

Post by Alcoholics Anonymous »

It also appears I am wrong about utoa - the special decimal case is not enabled by default. It's the ascii to number decimal case that is enabled and not the number to ascii.

The clib options live in config_clib.m4 and you can see I've highlighted the option that controls whether the special decimal code is used. It needs to have bit 2 set to enable it. The library has to be re-built for any changes to take effect.
User avatar
Metalbrain
Microbot
Posts: 107
Joined: Thu Feb 15, 2018 2:14 pm

Re: unsigned to ascii

Post by Metalbrain »

There's a small optimization which saves 1 byte and 3 states for all solutions so far: 10/100 (and -10/-100, and -10+256/-100+256) have the same value for the high byte, so we can load only the lower byte for it. So, "ld e,-10" in my proposal, "ld e,10" in Bizzley's and "ld c,-100+256" in both fast and small z88dk routines.
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: unsigned to ascii

Post by dfzx »

I had a look at some of these options. My Z80 assembly language isn't much above beginner's level so I stuck with Z88DK/C, which works for me because that's what my game is written in. :)

I implemented the unsigned int to ACSII conversion several times and put each one in a loop 9999 times so I could time it using a stopwatch.

First up, here's the sprintf() version:

Code: Select all

/*
 * zcc +zx -vn -startup=0 -clib=sdcc_iy -SO3 --max-allocs-per-node200000 utoa_char_sprintf.c -o utoa_char_sprintf -create-app --list
 *
 * gcc -o utoa_char_sprintf utoa_char_sprintf.c
 */

#include <stdio.h>

unsigned char output_str[5];

int main()
{
  unsigned int i;

  for( i=9999; i>0; i-- ) {
    sprintf( output_str, "%04u", i );

#ifdef __GNUC__
    printf("%s\n", output_str);
#endif

  }

  return 0;
}
On a 48K Spectrum in Fuse this runs in 24 seconds, which is why I asked the original question! Slow... The Z88DK sprintf %u convertor uses the library's utoa() function under the covers, so replacing sprintf with utoa() results in pretty much the same code and timings.

However, as AA advised, there's a library build time switch which pulls in utoa() code optimised for the base 10 case. Using that brings the testcase run time down to 16 seconds, so that's a pretty useful optimisation. Still slow though. :p

Next up is the ASCII digit rolling technique suggested by @R-Tape and @spectron. Like this:

Code: Select all

/*
 * zcc +zx -vn -startup=0 -clib=sdcc_iy -SO3 --max-allocs-per-node200000 utoa_char_digits.c -o utoa_char_digits -create-app --list
 *
 * gcc -o utoc_char_digits utoa_char_digits.c
 */

#include <stdio.h>

unsigned char output_str[5] = "9999";

int main()
{

  while( output_str[0] != '0' || output_str[1] != '0' || output_str[2] != '0' || output_str[3] != '1' ) {
    if( --output_str[3] == 0x2F ) {
      output_str[3] = '9';
      if( --output_str[2] == 0x2F ) {
	output_str[2] = '9';
	if( --output_str[1] == 0x2F ) {
	  output_str[1] = '9';
	  if( --output_str[0] == 0x2F ) {
	    output_str[0] = '9';
	  }
	}
      }
    }

#ifdef __GNUC__
    printf("%s\n", output_str);
#endif

  }

  return 0;
}
This is faster still. Even on the 48K Spectrum this is almost too fast to time. It takes a bit over a second. The code compiles to this:

Code: Select all

 _main:
 l_main_00112:
 	ld	bc,_output_str+0
 	ld	a, (bc)
 	ld	d, a
 	ld	hl,+(_output_str + 0x0003)
 	ld	e, (hl)
 	ld	a, d
 	sub	a,0x30
 	jr	NZ,l_main_00113
 	ld	a,(_output_str + 0x0001)
 	sub	a,0x30
 	jr	NZ,l_main_00113
 	ld	a,(_output_str + 0x0002)
 	sub	a,0x30
 	jr	NZ,l_main_00113
 	ld	a, e
 	sub	a,0x31
 	jr	Z,l_main_00114
 l_main_00113:
 	dec	e
 	ld	hl, +(_output_str + 0x0003)
 	ld	(hl), e
 	ld	a, e
 	sub	a,0x2f
 	jr	NZ,l_main_00112
 	ld	(hl),0x39
 	ld	a,(_output_str + 0x0002)
 	add	a,0xff
 	ld	hl, +(_output_str + 0x0002)
   	ld	(hl), a
 	sub	a,0x2f
 	jr	NZ,l_main_00112
 	ld	(hl),0x39
 	ld	a,(_output_str + 0x0001)
 	add	a,0xff
 	ld	hl, +(_output_str + 0x0001)
 	ld	(hl), a
 	sub	a,0x2f
 	jr	NZ,l_main_00112
 	ld	(hl),0x39
 	ld	a, (bc)
 	add	a,0xff
 	ld	(bc), a
 	sub	a,0x2f
 	jr	NZ,l_main_00112
 	ld	a,0x39
 	ld	(bc), a
 	jr	l_main_00112
 l_main_00114:
 	ld	hl,0x0000
 	ret
I can follow that, just about. I don't quite get why it uses an 'add a,0xff' to do the decrement (surely 'dec a' would be the obvious choice?) but the rest of it makes sense.

The problem with this approach is that it's not very flexible. If I want to decrement the counter by, say, 10, I'd have the run it in a loop that many times. If I want to do any even moderate maths on the value, like add a bonus, then it gets a bit tricky. But it's fast though. :)

Here's the other option I played with, taking the divide-by-powers-of-10 approach advocated by @Metalbrain:

Code: Select all

/*
 * zcc +zx -vn -startup=0 -clib=sdcc_iy -SO3 --max-allocs-per-node200000 utoa_pow10_divs.c -o utoa_pow10_divs -create-app --list
 *
 * gcc -o utoa_pow10_divs utoa_pow10_divs.c
 */

#include <stdio.h>
#include <string.h>

unsigned char output_str[5];

int main()
{
  unsigned int i = 9999;

  while( i > 0 ) {
    unsigned int tmp = i;
    memcpy( output_str, "0000", 4 );

    while( tmp>=1000 ) {
      output_str[0]++;
      tmp -= 1000;
    }

    while( tmp>=100 ) {
      output_str[1]++;
      tmp -= 100;
    }

    while( tmp>=10 ) {
      output_str[2]++;
      tmp -= 10;
    }

    output_str[3]+=tmp;

#ifdef __GNUC__
    printf("%s\n", output_str);
#endif

    i--;
  }

  return 0;
}
This loop counts down in about 5 seconds, so it's still way faster than the sprintf and keeps the value in a scalar so it's easy to manipulate.

The code compiles to this:

Code: Select all

_main:
	ld	bc,0x270f
l_main_00110:
	ld	a, b
	or	a,c
	jr	Z,l_main_00112
	push	bc
	ld	de,_output_str
	ld	bc,0x0004
	ld	hl,___str_0
	ldir
	pop	bc
	ld	e, c
	ld	d, b
l_main_00101:
	ld	a, e
	sub	a,0xe8
	ld	a, d
	sbc	a,0x03
	jr	C,l_main_00119
	ld	a,(_output_str)
	inc	a
	ld	(_output_str),a
	ld	hl,0xfc18
	add	hl,de
	ex	de,hl
	jr	l_main_00101
l_main_00119:
l_main_00104:
	ld	a, e
	sub	a,0x64
	ld	a, d
	sbc	a,0x00
	jr	C,l_main_00121
	ld	hl,_output_str + 1
	inc	(hl)
	ld	hl,0xff9c
	add	hl,de
	ex	de,hl
	jr	l_main_00104
l_main_00121:
l_main_00107:
	ld	a, e
	sub	a,0x0a
	ld	a, d
	sbc	a,0x00
	jr	C,l_main_00109
	ld	hl,_output_str + 2
	inc	(hl)
	ld	hl,0xfff6
	add	hl,de
	ex	de,hl
	jr	l_main_00107
l_main_00109:
	ld	a,(_output_str + 0x0003)
	add	a, e
	ld	((_output_str + 0x0003)),a
	dec	bc
	jr	l_main_00110
l_main_00112:
	ld	hl,0x0000
	ret
I'm a bit out of my depth with this, but I get the idea.

Finally I tried switching to the small_utoa assembly language function described by AA, which is used internally in Z88DK. This does something similar to the tight powers-of-10 dividing code posted by @Bizzley:

Code: Select all

/*
 * zcc +zx -vn -startup=0 -clib=sdcc_iy -SO3 --max-allocs-per-node200000 small_utoa_main.c small_utoa.asm -o small_utoa -create-app --list
 *
 */

#include <stdio.h>
#include <string.h>

unsigned char output_str[5];

char* small_utoa( unsigned int, char* );

int main()
{
  unsigned int i;

  for( i=9999; i>0; i-- ) {
    small_utoa( i, output_str );
  }

  return 0;
}
It needs this (not very efficient because I'm still new to this) interface code to externalise what is normally an internal function:

Code: Select all

; char *small_utoa(unsigned int num, char *buf)

SECTION code_stdlib

PUBLIC _small_utoa

EXTERN l_small_utoa

_small_utoa:

   pop af
   pop hl
   pop de
   pop bc
   
   push bc
   push de
   push hl
   push af
   
   call l_small_utoa

   ex de,hl
   ld (hl),0
   ret
That takes about 3.5 seconds to do what is basically the same as my C code, only with hand coded ASM.

I'd like to try the BCD option but I don't I understand it enough to try. :)

So, bottom line: I knew the sprintf() would be slow which is why I went through this exercise. The divide-by-powers-of-10 approach is much faster, and the hand coded assembly language version internal to Z88DK is significantly faster than my C implementation, so that's the code to use for maximum flexibility. However, the way my game currently works I think I can use the digit rolling code. :)
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
Bizzley
Microbot
Posts: 124
Joined: Thu Nov 16, 2017 10:47 am

Re: unsigned to ascii

Post by Bizzley »

Here's a very simple and small z80 routine to add two five digit numbers held as ASCII strings together using string addition rather than 16bit conversion. It's generalised in that it will add any five digit "number" to another with a resultant less than 99999 but can be optimized by reducing the main loop for powers of ten that aren't used. e.g. if you are only going to be adding numbers between 1 and 999 to the Total then you can set the length of your strings to three and change the value in the ld bc,nn statement to this number. If you are only going to be using a limited set of numbers, such as multiples of 100 up to 9900 with no other digits, then you can do similar by changing the start position of the pointer into the Total string. You could optimise it for speed a little by presetting some of the values used (e.g. ld de,106*256+68) and using sub d and add e instead but I've left it as is so it's a bit clearer.

Code: Select all

;enter with HL pointing to the string holding the value in ASCII.
	ld hl,addstring
	call calcstring
;
calcstring	ld bc,5
	add hl,bc
	ld b,c
	ld ix,totalstring+5
calcstring1	dec ix
	dec hl
	ld a,(hl)
	add (ix+0)
	sub 106
	jr c,calcstring2
	sub 10
	inc (ix-1)
calcstring2	add 58
	ld (ix+0),a
	djnz calcstring1
	ret
;
addstring	dm "000000"
totalstring dm "000000"
"He made eloquent speeches to an audience consisting of a few depressed daffodil roots, and sometimes the cat from next door."
Bizzley
Microbot
Posts: 124
Joined: Thu Nov 16, 2017 10:47 am

Re: unsigned to ascii

Post by Bizzley »

Here's a 5 byte smaller and slightly faster version of the above, done by replacing IX with DE. I've also fixed the bug with the wrong length of the strings.

Code: Select all

calcstring	ld bc,5
	add hl,bc
	ld b,c
	ld de,totalstring+5
	ex de,hl
calcstring1	dec hl
	dec de
	ld a,(de)
	add (hl)
	sub 106
	jr c,calcstring2
	sub 10
	dec hl
	inc (hl)
	inc hl
calcstring2	add 58
	ld (hl),a
	djnz calcstring1
	ret
;
addstring	dm "00000"
totalstring	dm "00000"
"He made eloquent speeches to an audience consisting of a few depressed daffodil roots, and sometimes the cat from next door."
Alcoholics Anonymous
Microbot
Posts: 194
Joined: Mon Oct 08, 2018 3:36 am

Re: unsigned to ascii

Post by Alcoholics Anonymous »

dfzx wrote: Tue Oct 09, 2018 6:06 pm However, as AA advised, there's a library build time switch which pulls in utoa() code optimised for the base 10 case. Using that brings the testcase run time down to 16 seconds, so that's a pretty useful optimisation. Still slow though. :p
sprintf has to deal with a lot of things. Even on the surface sprintf( output_str, "%04u", i ) is being asked to output the integer in a field width of four characters that will have leading 0 padding. Underneath, sprintf is using the printf engine with a string "device driver". The printf engine has to be able to write its output to any device including disk drives, printers, serial devices etc, so it must first generate portions of the output into a buffer (using utoa into a buffer on the stack for %u), then apply formatting as it sends output to the device using a couple of primitives (put character optionally repeated and put buffer). Then the string driver, in the sprintf case, carries out these operations with an ldir equivalent but it must keep track of the current string print position and count how many characters in total are output (the return value of the printf family tells you how many chars where sent in the course of printing). So it's a long road from sprintf to string. sprintf is fantastic for complicated output that mixes all sorts of things into the string but if you want just a 5-digit score in a fixed buffer of 5 chars, it's the slowboat :) That's where it's better to use direct functions like utoa or lower level things either from the library or what you've written.
I can follow that, just about. I don't quite get why it uses an 'add a,0xff' to do the decrement (surely 'dec a' would be the obvious choice?) but the rest of it makes sense.
We can't optimize it out as we currently have no way in the compiler to find out of the carry flag is important in following instructions. At one time I tried to optimize some of these sorts of things out for 16-bit numbers only to find out it broke 32-bit math because the 32-bit math was relying on the carry flag to borrow from the high word.

Code: Select all

; char *small_utoa(unsigned int num, char *buf)
You can change this to fastcall linkage if you also place the buffer at a fixed place in memory like the other examples here are doing:

Code: Select all


; extern void small_utoa(unsigned int num) __z88dk_fastcall;

SECTION code_user

PUBLIC _small_utoa

EXTERN l_small_utoa

_small_utoa:

   ; hl = num
   
   ; destination buffer address
   
   ld de,_score_buffer
   
   ; carry set for leading zeroes
   
   scf
   call l_small_utoa

   ; zero terminate (maybe not needed)
   
   xor a
   ld (de),a
   
   ret


; extern char score_buffer[5];

SECTION bss_user
PUBLIC _score_buffer

_score_buffer:  defs 6
I separated code from vars - it's good practice, even though not necessary for the zx if creating programs that load into ram. The proper assignment of stuff to sections allows the toolchain to generate romable code, which is something that could happen on the zx if you ever decided to make an if2 cartridge, eg.

But yeah the fastest will be adding in ascii as shown in the posts above.
Alcoholics Anonymous
Microbot
Posts: 194
Joined: Mon Oct 08, 2018 3:36 am

Re: unsigned to ascii

Post by Alcoholics Anonymous »

Metalbrain wrote: Tue Oct 09, 2018 11:27 am There's a small optimization which saves 1 byte and 3 states for all solutions so far ... "ld c,-100+256" in both fast and small z88dk routines.
I'll put that into my next commit.

More importantly there is a bug in the 32-bit version - it's missing the hundreds case. LDIRing those constants onto the stack is probably better too.
Post Reply