Menu

#3077 _ultoa, etc.

open
nobody
None
other
5
2020-07-27
2020-07-03
No

SDCC has 4 functions in stdlib.h to convert integers into string: _uitoa, _itoa, _ultoa, _ltoa.

Since making sprintf as efficient as these will take some time, we still need them for now.

However, there are some problems with them that should be fixed:

  • Their names are not in the namespace reserved for implementation-specific extensions and could collide with names used by users. They should be changed to start with two underscores.
  • They are not tested in regression tests.
  • They are not available for all ports.

Discussion

1 2 > >> (Page 1 of 2)
  • Philipp Klaus Krause

    • Description has changed:

    Diff:

    --- old
    +++ new
    @@ -4,7 +4,7 @@
    
     However, there are some problems with them that should be fixed:
    
    -* Their names are no in the namespacew reserved for implementation-specific extensions and could collide with names used by users. They should be chnaged to start with two underscores.
    +* Their names are no in the namespace reserved for implementation-specific extensions and could collide with names used by users. They should be chnaged to start with two underscores.
    
     * They are not tested in regression tests.
     * They are not available for all ports.
    
     
  • Maarten Brock

    Maarten Brock - 2020-07-06
    • Description has changed:

    Diff:

    --- old
    +++ new
    @@ -1,10 +1,10 @@
    -SDCC has 4 functions in stdlib.h to convert integers into string: \_uitoa, \itoa, \_ultoa, \_ltoa.
    +SDCC has 4 functions in stdlib.h to convert integers into string: \_uitoa, \_itoa, \_ultoa, \_ltoa.
    
     Since making sprintf as efficient as these will take some time, we still need them for now.
    
     However, there are some problems with them that should be fixed:
    
    -* Their names are no in the namespace reserved for implementation-specific extensions and could collide with names used by users. They should be chnaged to start with two underscores.
    +* Their names are not in the namespace reserved for implementation-specific extensions and could collide with names used by users. They should be changed to start with two underscores.
    
     * They are not tested in regression tests.
     * They are not available for all ports.
    
     
  • Sergey Belyashov

    Can return value be a pointer to null char at end of result (to simplify compute len = res - str)?

     

    Last edit: Sergey Belyashov 2020-07-07
  • Sergey Belyashov

    I have added regression tests for _itoa, _uitoa, _ltoa and _ultoa functions. These tests compiles to huge binaries. If compile as is (without partitioning) z80 port fails to compile with segmentation fault. I think, it is because of memory overflow.
    But now stm8-large port has failed (u)itoa farm tests with segmentation fault. On my local desktop it pass all tests.

     
    • Philipp Klaus Krause

      The ususal thing to do in such a case (new regression test fails for some ports), is to open a bug report for this newly discoved issue, and disable the test (using #define 0 with a comment referencing the bug number).
      In this case, I have already opened the bug [#3082] for it.

      Also, IMO, it would be good, if we can do some testing even on the smaller targets. Even if for those targets only one or to tests are done per function.

       

      Related

      Bugs: #3082

  • Sergey Belyashov

    I think, these functions can be implemented on z80/z180/z80n/ez80 using bin2bcd convertion. It faster than div/mod algorithm.

     
    • Philipp Klaus Krause

      Yes. I've used this approach on the ColecoVision before (see attachment), and it would make sense to use it here (I never got around to doing it, since in the long term, I intend to do a big printf-implementation-overhaul, but to be realistic, it makes more sense to just use the bcd stuff now, so we get an improvement quick with the existing functions).

       
      • Sergey Belyashov

        Have some other SDCC ports instructions which simplifies BCD arithmetics?

         

        Last edit: Sergey Belyashov 2020-07-12
        • Maarten Brock

          Maarten Brock - 2020-07-13

          Mcs51 has DA which is the only instruction I'm pretty sure I have never used.

          Operation: DA
          Function: Decimal Adjust Accumulator
          Syntax: DA A
          OpCode: 0xD4
          Flags: C
          Description: DA adjusts the contents of the Accumulator to correspond to a BCD (Binary Coded Decimal) number after two BCD numbers have been added by the ADD or ADDC instruction. If the carry bit is set or if the value of bits 0-3 exceed 9, 0x06 is added to the accumulator. If the carry bit was set when the instruction began, or if 0x06 was added to the accumulator in the first step, 0x60 is added to the accumulator.

          The Carry bit (C) is set if the resulting value is greater than 0x99, otherwise it is cleared.

           
        • Philipp Klaus Krause

          The Padauk have a half-carry flag. But they lack a decimal-adjust instruction, so the flag isn't really useful. One could still try something like

          daa:
              t0sn    f, c
              goto    c
          
          nc:
              t0sn    f, ac
              goto    ncac
              ceqsn   a, #0x09
              nop
              t1sn    f, ac
          ncac:
              add a, #0x06
          ncnac:
              ceqsn   a, #0x90
              nop
              t1sn    f, c
              goto    cnac
              set0    f, c
              ret
          
          c:
              t0sn    f, ac
              goto    cac
              ceqsn   a, #0x09
              nop
              t1sn    f, ac
          cac:
              add a, #0x06
          cnac:
              add a, #0x60
              set1    f, c
              ret
          
          
          
          daa_singledigitperbyte:
              ceqsn   a, #0x0a
              t1sn    f, c
              goto    nc
          c:
              add a, #0x06
              and a, #0x0f
              set1    f, c
              ret
          nc:
              set0    f, c
              ret
          

          However, if you want to use BCD, I'd suggest doing it port-by-port with individual patches, not one big patch that changes all ports at the same time.

           
          • Sergey Belyashov

            My idea: implement uitoa with radix 10 using port internal functions void __uitobcd(unsigned int v, unsigned char r[3]), void __ultobcd(unsigned long v, unsigned char r[5]), and, possible, __ulltobcd(unsigned long long v, unsigned char r[10]). So result function will looks like:

            void _uitoa(unsigned int value, char* string, unsigned char radix)
            {
              unsigned char shift;
              switch (radix) {
              case 2:
                shift = 1;
                break;
              case 4:
                shift = 2;
                break;
              case 8:
                shift = 3;
                break;
              case 16:
                shift = 4;
                break;
              case 32:
                shift = 5;
                break;
            #if defined(__SDCC_z80) ...
              case 10:
                uitoa_10 (value, string);
                return;
            #endif
              default:
                goto generic;
              }
            //fast implementation for radix = 2^n, where n = 1...5
            ...
            //generic implementation
            generic:
             }
            
             

            Last edit: Sergey Belyashov 2020-07-13
  • Sergey Belyashov

    There are implementation of uitobcd and ultobcd for Z80 (mostly all Z80-like, except Rabbits):

        .area   _CODE
    
        .globl ___ultobcd
    ;
    ; void __ultobcd (unsigned long v, unsigned char bcd[5])
    ; __ultobcd converts v to BCD representation to the bcd.
    ; bcd[] will contain BCD value in BigEndian order (MSB first).
    ;
    ___ultobcd:
        push    ix
        ld  ix, #0
        add ix, sp
        ld  bc, #0x2000
    ;
    ;--- begin speed optimization
    ;
        ld  l, 4 (ix)
        ld  h, 5 (ix)
        ld  e, 6 (ix)
        ld  d, 7 (ix)
        ld  a, e
        or  a, d
        jr  NZ, 101$
    ;high 2 bytes are zero
        ld  b, #0x10
        ex  de, hl
    101$:
        ld  a, d
        or  a, a
        jr  NZ, 102$
    ;high byte is zero
        ld  d, e
        ld  e, h
        ld  h, l
        ld  a, #-8
        add a, b
        ld  b, a
    102$:
        ld  4 (ix), l
        ld  5 (ix), h
        ld  6 (ix), e
        ld  7 (ix), d
    ;
    ;--- end speed optimization
    ;
        ld  hl, #0x0000
        ld  e, l
        ld  d, h
    ; (ix+0)..(ix+3) - binary value
    ; CDEHL - future BCD value
    ; B - bits count (32)
    103$:
        sla 4 (ix)
        rl  5 (ix)
        rl  6 (ix)
        rl  7 (ix)
        ld  a, l
        adc a, a
        daa
        ld  l, a
        ld  a, h
        adc a, a
        daa
        ld  h, a
        ld  a, e
        adc a, a
        daa
        ld  e, a
        ld  a, d
        adc a, a
        daa
        ld  d, a
        ld  a, c
        adc a, a
        daa
        ld  c, a
        djnz    103$
    ;
        ld  b, l
        ld  a, h
        ld  l, 8 (ix)
        ld  h, 9 (ix)
        ld  (hl), c
        inc hl
        ld  (hl), d
        inc hl
        ld  (hl), e
        inc hl
        ld  (hl), a
        inc hl
        ld  (hl), b
    ;
        pop ix
        ret
    
        .area   _CODE
    
        .globl ___uitobcd
    ;
    ; void __uitobcd (unsigned int v, unsigned char bcd[3])
    ; __uitobcd converts v to BCD representation to the bcd.
    ; bcd[] will contain BCD value in BigEndian order (MSB first).
    ;
    ___uitobcd:
        push    ix
        ld  ix, #0
        add ix, sp
    ;
        ld  bc, #0x1000
        ld  d, c
        ld  e, c
        ld  l, 4 (ix)
        ld  h, 5 (ix)
    ;
    ;--- begin speed optimization
    ;
        ld  a, h
        or  a, a
        jr  NZ, 100$
    ;
        ld  h, l
        srl b
    ;
    ;--- end speed optimization
    ;
    ; HL - binary value
    ; CDE - future BCD value
    ; B - bits count (16)
    100$:
        add hl, hl
        ld  a, e
        adc a, a
        daa
        ld  e, a
        ld  a, d
        adc a, a
        daa
        ld  d, a
        ld  a, c
        adc a, a
        daa
        ld  c, a
        djnz    100$
    ;
        ld  l, 6 (ix)
        ld  h, 7 (ix)
        ld  (hl), c
        inc hl
        ld  (hl), d
        inc hl
        ld  (hl), e
    ;
        pop ix
        ret
    

    I have tests for them. So if they OK, I may commit as:

    sdcc/device/lib/z80/Makefile.in
    sdcc/device/lib/z80/__uitobcd.s
    sdcc/device/lib/z80/__ultobcd.s
    sdcc/support/regression/tests/uitobcd.c
    sdcc/support/regression/tests/ultobcd.c
    
     

    Last edit: Sergey Belyashov 2020-07-13
  • Philipp Klaus Krause

    Anyway, I think it is better to fix the names first before adding port-specicif implementations (so we're not adding a lot of files that would have to be renamed later - though since we use svn,. not git, a rename doesn't harm the history).

    I'd suggest to go for simply renaming _ultoa to __ultoa, etc unless there is a good reason to choose a different name.

     
    • Maarten Brock

      Maarten Brock - 2020-07-16

      Anyway, I think it is better to fix the names first before adding port-specicif implementations
      I'd suggest to go for simply renaming _ultoa to __ultoa, etc unless there is a good reason to choose a different name.

      This microsoft page seems to indicate that only ultoa is non-compliant and _ultoa is ok:
      https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/itoa-itow?view=vs-2019

      The POSIX names itoa, ltoa, and ultoa exist as aliases for the _itoa, _ltoa, and _ultoa functions. The POSIX names are deprecated because they do not follow the implementation-specific global function name conventions of ISO C.

      Are you sure that the name with a single underscore is non-compliant?

       
      • Philipp Klaus Krause

        Sure is a very strong word, and reserved identifiers are not my speciality. There is a reason a WG14 document on this is titled "What we think we reserve" (N2493).
        And looking at the details again we apparently should be okay with the old name:

        Clearly, identifiers beginning with two underscores or an underscore followed by a capital letter are reserved. Traditionally, the former is mostly used for implementation extensions, while the latter is mostly used for introducing new standard fetures.

        In the current C2X draft we also have: "All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces." (and I assume the wording is the same in earlier standards).

        So we could indeed continue to use _ultoa as name for a function in our library, as we use it for a file-scope identifier in the ordinary namespace. Still it is cleaner to use the __ prefix for all implementation extensions, even though the _ prefix is allowed in this corner case.

         
  • Sergey Belyashov

    Implemented for Z80 in [r11726].

    Convertion is done with 3 algorithms:

    1. via BCD for radix=10
    2. fast binary for radix=2^n (n=1..7)
    3. optimized generic for any other radix (3,5,6,7,9...255)
      If number is equal to 0, then string "0" is returned.
      If radix is 0 or 1 then empty string is returned (not covered by test).
      Exported both symbols like __itoa and ___itoa. So renaming can be done later.
     
  • Philipp Klaus Krause

    Rename done in [r11731]. Tests had been added already.
    I think we can close this bug report (unless someone objects). BCD optimization would be a feature, not a bugfix.

     
    • Sergey Belyashov

      BCD is already implemented for pure Z80 port. For itoa it faster about 2 times than generic algorithm used for custom radix. For ltoa it faster about 10-20%.

      I suggest to review __uitobcd, __ultobcd, and __strreverse new functions too. If all is OK, then I port them for Z180, EZ80, Z80N targets.

       
  • Sergey Belyashov

    Implemented for eZ80, Z180 and Z80N in [r11753].

     
  • Free-PDK

    Free-PDK - 2020-07-26

    Can we also have __ltoa and __ultoa in pdk ports?

    Here is the patch:

    $ svn diff

    Index: device/lib/pdk13/Makefile.in
    ===================================================================
    --- device/lib/pdk13/Makefile.in    (Revision 11773)
    +++ device/lib/pdk13/Makefile.in    (Working Copy)
    @@ -46,6 +46,7 @@
    
     PDK13_SDCC = $(COMMON_SDCC) \
       __itoa.c \
    
    +  __ltoa.c \
       _startup.c \
       _strcmp.c \
       _strcpy.c \
    Index: device/lib/pdk14/Makefile.in
    ===================================================================
    --- device/lib/pdk14/Makefile.in    (Revision 11773)
    +++ device/lib/pdk14/Makefile.in    (Working Copy)
    @@ -46,6 +46,7 @@
    
     PDK14_SDCC = $(COMMON_SDCC) \
       __itoa.c \
    
    +  __ltoa.c \
       _startup.c \
       _strcmp.c \
       _strcpy.c \
    Index: device/lib/pdk15/Makefile.in
    ===================================================================
    --- device/lib/pdk15/Makefile.in    (Revision 11773)
    +++ device/lib/pdk15/Makefile.in    (Working Copy)
    @@ -46,6 +46,7 @@
    
     PDK15_SDCC = $(COMMON_SDCC) \
       __itoa.c \
    
    +  __ltoa.c \
       _startup.c \
       _strcmp.c \
       _strcpy.c \
    Index: device/lib/pdk15-stack-auto/Makefile.in
    ===================================================================
    --- device/lib/pdk15-stack-auto/Makefile.in (Revision 11773)
    +++ device/lib/pdk15-stack-auto/Makefile.in (Working Copy)
    @@ -46,6 +46,7 @@
    
     PDK15_SDCC = $(COMMON_SDCC) \
       __itoa.c \
    
    +  __ltoa.c \
       _startup.c \
       _strcmp.c \
       _strcpy.c \
    
     
    • Sergey Belyashov

      Too little target. Theoretically it is possible but unusefull.

       
      • Free-PDK

        Free-PDK - 2020-07-26

        Sorry, I don't understand your comment.

        The patch above enables the generic __ltoa and __ultoa C implementations to be included in the PDK libraries. No more work required.

        _ltoaand _ultoa including __modulong and __divulong sums up to 302 instructions (PDK usually has 1024-3096 instructions in code memory available).

        Currently I'm working on a tiny printf which could use this functions and for sure others might need them as well.

         
1 2 > >> (Page 1 of 2)

Log in to post a comment.

MongoDB Logo MongoDB