Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 21:58 21 Oct 2025 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : CSUB for string reversal

Author Message
Nimue

Guru

Joined: 06/08/2020
Location: United Kingdom
Posts: 425
Posted: 01:43pm 12 Oct 2025
Copy link to clipboard 
Print this post

As a sanity exercise, I play with numbers (things like primes etc).  Usually this happens in Python due to being able to code at work in lunch etc.

Recently I've been busting out a 2040 PicoMite to code in MMBasic as an attemt to unwind after work.

Currently playing with palindromic numbers and was scanning though 10,000 plus to find palindromic "cyclops" numbers "1230321"  with a zero in the middle digit.  Compound that to find palindromic, cyclops primes and the calculations sprial.

This led me to testing string reversal in MMBASIC and its "slow" - not a moan given what's going on, but it was a bottle neck.  

So I thought I'd have a go at writing a CSUB.

My C is not great and it took a fair few attempts and Google to get it working.   To compile to ARM I asked ChatGPT (and especially for the "endian" and 32bit words etc).

But it works...

So here's the CSUB and a benchmark program.  C code at the end.

'=========================================================
' Reverse benchmark: BASIC vs CSUB
' - Assumes you already defined:  CSUB REVERSE output$, input$
' - Measures 10000 reversals on the same input string
'=========================================================

OPTION EXPLICIT
CONST NITER = 10000  ' Number of iterations.  My case testing 10000

' ---------- Pure BASIC reverse (sub writes into dest$) ----------
SUB ReverseBasic dest$, src$
 LOCAL i%
 dest$ = SPACE$(LEN(src$))           ' pre-allocate for speed
 FOR i% = LEN(src$) TO 1 STEP -1
   MID$(dest$, LEN(src$)-i%+1, 1) = MID$(src$, i%, 1)
 NEXT i%
END SUB

' ---------- Benchmark helper ----------
SUB BenchReverse method$, elapsed_ms%
 LOCAL i%, s$, r$, t0%
 s$ = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#"
 r$ = ""                             ' output buffer

 ' one warm-up to stabilize any first-use effects - added for consistency
 IF method$ = "BASIC" THEN
   ReverseBasic r$, s$
 ELSEIF method$ = "CSUB" THEN
   ON ERROR SKIP 1 : REVERSE r$, s$ : IF MM.ERRNO <> 0 THEN
     PRINT "CSUB REVERSE not available. Skipping CSUB test."
     elapsed_ms% = -1
     EXIT SUB
   ENDIF
 ENDIF

 TIMER = 0                           ' reset millisecond timer
 IF method$ = "BASIC" THEN
   FOR i% = 1 TO NITER
     ReverseBasic r$, s$
   NEXT i%
 ELSE
   FOR i% = 1 TO NITER
     REVERSE r$, s$
   NEXT i%
 ENDIF
 elapsed_ms% = TIMER                 ' elapsed ms since reset
END SUB

' ---------- Run both tests ----------
DIM t_basic%, t_csub%
BenchReverse "BASIC", t_basic%
BenchReverse "CSUB",  t_csub%

' ---------- Report ----------
PRINT "Iterations: "; NITER
IF t_basic% >= 0 THEN PRINT "BASIC total: "; t_basic%; " ms   (~"; INT(NITER * 1000 / t_basic%); " ops/s)"
IF t_csub%  >= 0 THEN PRINT "CSUB  total: "; t_csub%;  " ms   (~"; INT(NITER * 1000 / t_csub% ); " ops/s)"

IF t_basic% > 0 AND t_csub% > 0 THEN
 PRINT "Speed-up (CSUB / BASIC): x"; (t_basic% / t_csub% * 100)
ELSEIF t_csub% = -1 THEN
 PRINT "Note: CSUB timing skipped (REVERSE command not found)."
ENDIF




CSUB REVERSE string, string
'useage REVERSE output$, input$
00000000
780CB530 190B1C42 D1014299 BD307004 3B01781D 32017015 0000E7F6
END CSUB


This gives 500ms for the CSUB to reverse 10,000 strings compared to 65s for pure MMBasic.  x12,000% speedup if my math works.


I am sure my C can be improved:

// REVERSE dest$, src$     (RP2040 / Cortex-M0+; length at index 0)
// Offset added to get string length, capped to n=255
void REVERSE(char *dest, const char *src) {
   unsigned int n = ((const unsigned char*)src)[0];   // length at offset 0
   if (n > 255) n = 255;

   // reverse: src[1 .. n]  -> dest[1 .. n]
   for (unsigned int i = 0; i < n; i++) {
       dest[1 + i] = src[1 + (n - 1 - i)];
   }

   // tell MMBasic how many chars are valid
   ((unsigned char*)dest)[0] = (unsigned char)n;

}


Note - no error trapping (other than basic string length) inside the C.

Now for me, the other bottle neck is probably converting integers to strings in the first place and I'll have a go at doing something specific for ReverseInteger (where it takes an integer, converets inside the C to a string and spits out the integer) - but that's my fringe case use.

This string reverse CSUB itself might be useful elsewhere.

My C is rudimentary and still learning, so any insight greatly recieved.

Happy Sunday.
Entropy is not what it used to be
 
Nimue

Guru

Joined: 06/08/2020
Location: United Kingdom
Posts: 425
Posted: 04:36pm 12 Oct 2025
Copy link to clipboard 
Print this post

Likewise for directly taking an integer and checking for palindrome without text conversion first:

Useage:  ISPALINDROM boolean_out%, integer%
Need to Dim boolean_out% first ;-)


CSUB ISPALINDROM integer, integer
00000000 680BB5F0 9303B085 684E2300 001A0007 DB0C429E 20000034 9D032100
4322002A 9B03D109 4043404E 42594333 603B414B B005607A 220ABDF0 F0002300
220AF833 91019000 00210028 F0002300 0005F807 9800000C 18809901 E7DF4159
D1152B00 D1132A00 DB062900 2800DC01 2000D006 084143C0 2180E002 20000609
4802B407 1840A101 BD039002 000000B5 4668B403 9802B501 F834F000 469E9B01
BC0CB002 46C04770 46CEB5F0 0C034699 0413469C 0C1B4647 000E001D 04044661
B5800C24 0C100007 4365434B 43604341 18C00C2C 468C1824 D90342A3 025B2380
44C44698 43794649 0C234356 0C2D042D 44631989 19600424 BCC018C9 46B046B9
46C0BDF0 46CEB5F8 B5804647 46984691 000D0004 F816F000 000E0007 000B0002
46414648 FFC0F7FF 418D1A24 00389B08 601C0031 BCC0605D 46B046B9 46C0BDF8
46C04770 4645B5F0 465746DE B5E0464E 9200B083 000D9301 9A019900 2D000004
0006DB61 2A00002F 9C00DB0C 42BD9D01 2000D91A B0032100 46BBBCF0 46A946B2
BDF046A0 424C2500 42BD4195 D100D8F1 2301E0AE 4699425B 9A00E00B 25009B01
419D4254 D8E442BD D10042BD 2300E09C 00294699 F0000020 4680F8C3 00300039
F8BEF000 1A1B4643 3B204698 E080D500 40990021 000B469A 46400021 000A4081
D82D42BB 4651D02A 419F1AB6 DA002900 2100E09B 24012000 91019000 408C4651
24019401 40AC4645 46449400 D11E2C00 99019800 2B00464B 0003D0AD 2100000C
41A14258 2700E7A7 41AF4266 DBB42A00 9D019C00 D89C42BD E059D1AB D9D242B1
21002000 90004644 2C009101 07DCD0E0 466146A4 46C40854 085D430C 42BDE014
42B4D101 0032D812 1B12003B 260141AB 415B1892 18B62700 2301415F 469B425B
466344DC D00A2B00 D9E842BD 425B2301 44DC469B 19B64663 2B00417F 9A00D1F4
46519B01 417B1992 DB252900 003D003C 464140CC 465140CD DB2D2900 408D0025
46450029 002040AC 418B1A12 93019200 4642E79E 469A0028 00212320 1A9B4090
000340D9 E777430B D90042B4 E75EE741 D80042B4 E73CE74D 21204640 00381A09
00344088 46400001 003D40C4 4641430C 465140CD DAD12900 21204640 40850026
40CE1A09 43310029 4641E7CB 25012420 20001A64 40E52100 91019000 E7609501
2900B510 F000D103 3020F807 0008E002 F802F000 46C0BD10 2301211C 4298041B
0C00D301 0A1B3910 D3014298 39080A00 4298091B 0900D301 A2023904 18405C10
46C04770 02020304 01010101 00000000 00000000 7FFFFDB0 00000001
END CSUB


Works out about x9 - x10 quicker than native.

Fun activity learning how to make CSUBs and use in MMBASIC.
Entropy is not what it used to be
 
Plasmamac

Guru

Joined: 31/01/2019
Location: Germany
Posts: 594
Posted: 06:48am 13 Oct 2025
Copy link to clipboard 
Print this post

Cool , like it
Plasma
 
JohnS
Guru

Joined: 18/11/2011
Location: United Kingdom
Posts: 4096
Posted: 11:49am 13 Oct 2025
Copy link to clipboard 
Print this post

  Nimue said  I am sure my C can be improved:

The C compiler's optimiser probably did it anyway - depending on what level of optimisation you had.

  Nimue said  
// REVERSE dest$, src$     (RP2040 / Cortex-M0+; length at index 0)
// Offset added to get string length, capped to n=255
void REVERSE(char *dest, const char *src) {
   unsigned int n = ((const unsigned char*)src)[0];   // length at offset 0
   if (n > 255) n = 255;

   // reverse: src[1 .. n]  -> dest[1 .. n]
   for (unsigned int i = 0; i < n; i++) {
      dest[1 + i] = src[1 + (n - 1 - i)];
   }

   // tell MMBasic how many chars are valid
   ((unsigned char*)dest)[0] = (unsigned char)n;

}


Happy Sunday.


I suppose you could use something like:
for (unsigned int i = 1; i <= n; i++) {
      dest[i] = src[(n - i + 1)];
  }


Or use pointers, but fair chance any fairly modern C compiler in effect already does.

You could get the ASM from the tool chain and look to see :)

You may find the ASM easier to read than you expect - but would be tougher to write.

John
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5357
Posted: 01:29pm 13 Oct 2025
Copy link to clipboard 
Print this post

Hi Nimue,

I think I am missing the point. I looked at your "ispalindrome" list of hex numbers. And these are not palindromes in decimal nor hex notation.
As for cyclops palindromes, why not simply generate them in the notation you envision ?
'cyclops palindromes decimal ASCII
For a=48 To 57
For b=48 To 57
For c=48 To 57
Print Chr$(a);Chr$(b);Chr$(c);"0";Chr$(c);Chr$(b);Chr$(a);" ";
Next :Next :Next


Volhout
Edited 2025-10-13 23:30 by Volhout
PicomiteVGA PETSCII ROBOTS
 
Bleep
Guru

Joined: 09/01/2022
Location: United Kingdom
Posts: 666
Posted: 05:18pm 13 Oct 2025
Copy link to clipboard 
Print this post

Hi Harm,
I think the 'list of hex numbers' you refer to is the compiled Csub, for you to put into some of your own basic code and try out? :-(
Regards.
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 2782
Posted: 08:47pm 13 Oct 2025
Copy link to clipboard 
Print this post

Or at the command line.
> dim n%,P%
> for n%=1 to 10000 :ISPALINDROM P%,n% :if P% then :? n%, :endif :next
1       2       3       4       5       6       7       8       9       11      22      33      44      55      66      77      88    99      101     111     121     131     141     151     161     171     181     191     202     212     222     232     242     252   262     272     282     292     303     313     323     333     343     353     363     373     383     393     404     414     424   434     444     454     464     474     484     494     505     515     525     535     545     555     565     575     585     595   606     616     626     636     646     656     666     676     686     696     707     717     727     737     747     757     767   777     787     797     808     818     828     838     848     858     868     878     888     898     909     919     929     939   949     959     969     979     989     999     1001    1111    1221    1331    1441    1551    1661    1771    1881    1991    2002  2112    2222    2332    2442    2552    2662    2772    2882    2992    3003    3113    3223    3333    3443    3553    3663    3773  3883    3993    4004    4114    4224    4334    4444    4554    4664    4774    4884    4994    5005    5115    5225    5335    5445  5555    5665    5775    5885    5995    6006    6116    6226    6336    6446    6556    6666    6776    6886    6996    7007    7117  7227    7337    7447    7557    7667    7777    7887    7997    8008    8118    8228    8338    8448    8558    8668    8778    8888  8998    9009    9119    9229    9339    9449    9559    9669    9779    9889    9999
>
Don't even need to disturb the existing program on the Pico. Just add the CSub to the end of it in the editor.

Using Volhout's method to create it as a number.
p% = val(Chr$(a)+Chr$(b)+Chr$(c)+"0"+Chr$(c)+Chr$(b)+Chr$(a)) :? p%

Edited 2025-10-14 15:41 by phil99
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2025