Ressu 2.0

Kaikki oikeudet pidätetään. Tässä ressun uusimmassa versiossa on uusittu asiakaspuskurin hallintaa, löydetty kellojonosta löytyvien satunnaisbittien “laskentaan” uusi menetelmä.

Asiakaspuskurin hallinnassa aiemmin asiakas toimitti puskurin satunnaisbittigeneraattorin täytettäväksi, ja satunnaislukugeneraattorilla ei ollut “omaa” muistialuetta. Näin jää aina mietittäväksi voisiko edellisestä puskurista laskea seuraavan tai päätellä satunnaisbittigeneraattorin ensimmäisen puskurin. Uudessa versiossa ressulla on oma puskurinsa, josta ressu palauttaa satunnaisia merkkejä asiakaspuskuriin. Näin asiakas ei saa koko puskurin rakennetta, vaan satunnaisen osan siitä.

Kellojonosta löytyvien satunnaisbittien laskentaa on useassa aiemmassa postissa, ja nyt on tästä uusin versio ajattelun alla. Uusi versio perustuu fluctuations eli vaihtelut tulosteeseen. Tulosteesta poistetaan suurimmat ketjut ja jäljelle jäävät lasketaan yhteen. Tästä saadaan arvioidut satunnaisbitit.

Ajatuksena on että nämä suurimmat ketjut ovat se perusmateriaali jota kellomateriaali sisältää ja että ne eivät sisältäisi paljoa satunnaisuutta. Toisaalta nämä pienet ketjut esiintyvät silloin tällöin satunnaisessa paikassa koko kellomateriaalin alueella ja näin niistä voidaan aina laskea jonkin verran satunnaisuutta.

Lisäksi lopussa on kiva lelu satunnaismerkkien hakemiseen.

Edit: korjailtu bugeja ja ohjelman formatointia. Nyt täytyy sanoa, että tämä on mielestäni lähimpänä tuotantoversiota tähän asti. Ei vieläkään ainoana generaattorina vaan lisättynä johonkin/joihinkin muuhun generaattoriin. Jatkan tutkimista..

Edit: muutettu lim lauseen sqrt cbrt:ksi. Kokeilu.

Tässä postissa käyn uusimman ohjelmaversion läpi, ja kommentoin kappaleita. Vaihtelut lasketaan periods tauluun ja clockbytes on kellojonon merkkien lukumäärä. Kuvassa oleva ressu_clockbyte() rutiini on vanha tuttu millisekuntien alimman merkin palauttava rutiini:

unsigned long periods[1024];
unsigned long genbytes=0;
unsigned long clockbytes=0;

#define DEBUG8 2

unsigned char ressu_clockbyte() /* JariK 2013 */
{
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return(tv.tv_usec & 0xff);
}

Seuraava rutiini on ressun varsinainen generaattoripätkä, joka hakee varsinaiset satunnaisluvut. Se lukee kellojonoa ja xor:aa kellojonon merkillä puskurissa olevan merkin. Ensin xora:taan koko jonosta alin bitti, sitten toiseksi alin bitti, kolmanneksi alin ja niin edelleen. Aina kun koko puskurin yksittäinen bitti on xor:attu vaihdetaan puskurissa merkkien paikkoja ja vaihdetaan alinta bittiä. Puhun muuten alimmasta bitistä, mutta todellisuudessa koko puskurin merkki xorataan. Tärkeätä on se, että kellojonon alimmalla bitillä xorataan kaikki puskurin bitit. Alin bitti on se, jossa on eniten vaihtelua. Lisäksi puskurin kaikki bitit kaikkia muitakin kellojonon 8 alinta bittiä.

Seuraavan kappaleen ensimmäinen luuppi (for(d=0)) tekee xor:auksen yhdelle bitille (koko puskurissa). Toinen (for(d=0)) luuppi vaihtaa kaikkien merkkien paikkoja keskenään. Näitä kahta suoritetaan 8 kertaa (for(c=0)), yksi jokaiselle merkkien bitille.

Yhden genbytes_fast rutiinin ajon aikana yksittäinen merkki xor:ataan kahdeksan kellojonon bitin kanssa, ja kellojonon bitti vaihtelee merkin puskuripaikan mukaan. Puskuripaikka vaihtelee satunnaisesti.

Aliohjelmassa on kolme erilaista merkin pyöritysvaihtoehtoa, jotka ovat mahdollisia. Näistä on kuitenkin käytetty vain rotate left 1 vaihtoehtoa. Rotate left 3 kattaisi teoriassa nopeammin koko merkin, mutta sitä ei ole tosiaan käytetty.

void ressu_genbytes_fast(int size, unsigned char *buffer)
{
  int c, d, e, byte;
  static int f = 0, prevbyte = -1, count = 0;

  for(c=0; c<8; c++) {
    for(d=0; d<size; d++) {
      e = buffer[d];
      e = ((e&0x80)>>7) | ((e&0x7f)<<1); // rotate left 1
      //e=((e&0xe0)>>5) | ((e&0x1f)<<3); // rotate left 3
      //e=((e&0xfe)>>1) | ((e&0x1)<<7);  // rotate right 1
      byte = ressu_clockbyte();
      if(prevbyte==-1)
        prevbyte=byte;
      buffer[d] = e^byte;
      if(prevbyte!=byte) {
        periods[count]++;
        clockbytes+=count;
        count=0;
        prevbyte=byte;
      }
      count++;
    }
    for(d=0; d<size; d++) {
      f = (f+buffer[d])%size;
      e = buffer[d];
      buffer[d] = buffer[f];
      buffer[f] = e;
    }
  }
}

Seuraava rutiini on varsinainen asiakkaan kutsuma rutiini, kuten sanottu tässä versiossa pidetään varsinaiset satunnaismerkit ja asiakkaan puskuri erillään. Asiakkaan puskuriin merkit kootaan (for(c=0)) luupilla, ja jos varsinaiset satunnaisbitit loppuvat (if ressut_pos) ehtolauseella haetaan niitä lisää. DEBUG9 ja DEBUG8 ovat debukkailua varten, ja ensimmäisellä yritetään synkronoida kellojonot siten että satunnaisbitit poimitaan samanlaisesta kellojonon kohdasta debukkailua varten, toisella taas raportoidaan debug tiedostoon “newressu.deb” juuri haettu puskuri. Tiedosto esittelee parhaiten sen miten teoreettiset satunnaisbitit lasketaan. Siitä lisää kuvan jälkeen:

Lisäksi rutiinissa on uutena asiana se että, miten paljon satunnaisbittejä tuotetaan ja miten paljon “teoreettisia bittejä” tarvitaan määritellään erikseen. Aiemmissa versioissa tuotettavat ja laskennalliset satunnaisbitit olivat sama asia. Ne määritellään alun #defineissä.

#define RESSUT_SIZE 4096
#define RESSU_BITS_NEEDED 128
#define RESSU_MIN_ROUNDS 2

void ressu_genbytes(int size, unsigned char *buffer) // 6.5.2021 JariK
{
  int c,d,e;
  static int ressut_first=1,
    ressut_pos = 0,
    ressut_size = RESSUT_SIZE,
    ressut_f = 0;
  static unsigned char ressut[RESSUT_SIZE];

  for(c=0; c<size; c++) {
    if(ressut_pos == 0) {
      if(ressut_first) {
        memset(ressut,0,sizeof(ressut));
        ressut_first=0;
      }

      clockbytes=0;
      for(d=0;d<1024;d++)
        periods[d]=0;
#ifdef DEBUG9
      while(ressu_clockbyte()!=0);
      while(ressu_clockbyte()==0);
#endif
      int rndbits=0, rounds=0;

      for(d=0; rndbits<RESSU_BITS_NEEDED ||
          d<RESSU_MIN_ROUNDS; d++) {
        rounds++;
        ressu_genbytes_fast(sizeof(ressut),ressut);
        rndbits=0;
        for(e=0;e<1024;e++) {
          if(periods[e]>0 && periods[e]<sqrt((double)clockbytes)) {
            rndbits+=periods[e];
          }
        }
      }

#ifdef DEBUG8

      FILE *fp1;
      if((fp1=fopen("newressu.deb","a"))!=NULL) {

        for(d=0;d<32;d++)
          fprintf(fp1,"%02x",ressut[d]);

        fprintf(fp1,", clockbytes: %ld",clockbytes);

        unsigned long total=0;
        fprintf(fp1,", fluctuations:");
        for(d=0;d<1024;d++) {
          if(periods[d]!=0) {
            fprintf(fp1," %d:%lu",d,periods[d]);
            total+=(periods[d]*d);
          }
        }
        fprintf(fp1,", total: %lu",total);

        fprintf(fp1,", limit: %lu",(unsigned long)sqrt((double)clockbytes));

        fprintf(fp1,", skipped:");
        for(d=0;d<1024;d++) {
          if(periods[d]>=sqrt((double)clockbytes))
            fprintf(fp1," %d:%lu",d,periods[d]);
        }

        fprintf(fp1,", counted:");
        rndbits=0;
        for(d=0;d<1024;d++) {
          if(periods[d]>0 && periods[d]<sqrt((double)clockbytes)) {
            fprintf(fp1," %d:%lu",d,periods[d]);
            rndbits+=periods[d];
          }
        }
        fprintf(fp1,", rndbits: %d",rndbits);
        fprintf(fp1,", rounds: %d",rounds);

        fprintf(fp1,"\n");

        fclose(fp1);
      } // if((fp1=fopen
#endif
    } // if(ressut_pos == 0)
    ressut_f = (ressut_f + ressut[ressut_pos]) % ressut_size;
    buffer[c] ^= ressut[ressut_f];
    ressut_pos = (ressut_pos+1) % ressut_size;
  } // for(c=0; c<size; c++)

  genbytes+=size;
}

Seuraavassa kappaleessa on esimerkki newressu.deb tiedostoon kerätystä tietueesta: clockbytes kertoo tällä hetkellä käytettyjen kellomerkkien määrän, limit kenttä lasketaan clockbytes kentästä (limit=sqrt(clockbytes)), fluctuations kertoo tässä genbytes_fast ajossa olleet vaihtelut, skipped kertoo laskennassa ohitetut vaihtelut (>=limit), counted kertoo teoreettisiin satunnaisbitteihin lasketut kellomerkit, rndbits on counted kenttien summa, suoritetut kierrokset tällä ajokerralla on 8. ressu_genbytes_fast:ia ajetaan niin kauan kun teoreettisia satunnaisbittejä on tarvittava määrä.

9242aeaa253e187ed893374299d3c40a9ebcc4372f615269533f69fa163345b5, clockbytes: 262136, fluctuations: 1:13 2:7 3:15 4:12 5:13 6:8 7:9 8:9 9:6 10:11 11:10 12:8 13:22 14:9308 15:8720, total: 262136, limit: 511, skipped: 14:9308 15:8720, counted: 1:13 2:7 3:15 4:12 5:13 6:8 7:9 8:9 9:6 10:11 11:10 12:8 13:22, rndbits: 143, rounds: 8

Seuraavassa vanhat tutut aputoiminnot, jotka käyttävät ressu_genbytes funktiota: rutiinit merkin (8 bits), shortin (16 bits), intin (32 bits) ja longin(64 bits) arpomiseksi. _limit-loppuisissa on yläraja halutulle satunnaisnumerolle. Uutena näissä on ressu_gen_limit, joka valitsee parametrina annetun limit:in mukaan sopivan talletuskoon.

int ressu_genbyte()
{
  unsigned char ch;
  ressu_genbytes(sizeof(ch), &ch);
  return(ch);
}

int ressu_genbyte_limit(int limit)
{
  int c;
  while((c=ressu_genbyte()) >= (256/limit)*limit);
  return(c%limit);
}

int ressu_genshort()
{
  return(ressu_genbyte() | ressu_genbyte()<<8);
}

int ressu_genshort_limit(int limit)
{
  int c;
  while((c=ressu_genshort()) >= (65536/limit)*limit);
  return(c%limit);
}

unsigned int ressu_genint()
{
  return(ressu_genshort() | ressu_genshort()<<16);
}

unsigned int ressu_genint_limit(unsigned long limit)
{
  unsigned int c;
  while((c=ressu_genint()) >=
        (((unsigned long)65536*65536)/limit)*limit);
  return(c%limit);
}

unsigned long ressu_genlong()
{
  return(((unsigned long)ressu_genint()) |
         ((unsigned long)ressu_genint())<<32);
}

unsigned long ressu_genlong_limit(unsigned long limit)
{
  unsigned long c;
  while((c=ressu_genlong()) >=
        (((unsigned long)0xffffffffffffffff)/
         limit)*limit);
  return(c%limit);
}

unsigned long ressu_gen_limit(unsigned long limit)
{
  if(limit<=256)
    return(ressu_genbyte_limit(limit));
  else if(limit<=65536)
    return(ressu_genshort_limit(limit));
  else if(limit<=(unsigned long)65536*65536)
    return(ressu_genint_limit(limit));
  else if(limit<=(unsigned long)0xffffffffffffffff)
    return(ressu_genlong_limit(limit));
  else
    return(-1);
}

Loppuosuus postista koskee lelusovellusta, eli ensin lyhyt esittely ja sitten lähdekielinen versio. Näissä esimerkeissä rivin pituus on 72:n sijaan 52, jotta ne mahtuvat koodi-ikkunaan.

Aluksi virheellisistä komennoista tulostetaan käyttöohje:

./newressu -?
./newressu: invalid option -?
print random decimal digits                       newressu -d              
print octal digits                                newressu -o              
print hexadecimal digits                          newressu -x              
print binary digits                               newressu -b              
print decimal digits, with spaces between "words" newressu -d --space      
print binary digits, with spaces between "words"  newressu -b --space      
print decimal digits, 30 lines                    newressu -d -l30         
print decimal digits, 30 lines, no linenumbers    newressu -d -l30 --lineno
print decimal digits, 128 characters per line     newressu -d -c128        
print decimal numbers between 0-9                 newressu -d --lim10      
print hexadecimal numbers with decimal limit      newressu -d --lim10 -x   
print decimal numbers with spaces and wordsize 10 newressu -d -s10 --space 
print alphanumeric characters                     newressu --isalnum       
print alphabetic characters                       newressu --isalpha       
print digits                                      newressu --isdigit       
print printables excluding space                  newressu --isgraph       
print lowercase characters                        newressu --islower       
print printable characters including space        newressu --isprint       
print punctuation characters                      newressu --ispunct       
print uppercase characters                        newressu --isupper       
print hexadecimal digits                          newressu --isxdigit      
print material for passwords                      newressu -1              
print random vowels                               newressu -iaeiouyåäö  
print russian vowels                              newressu -iаэыуояеёюи
flags                                             newressu -s -i󾓥󾓦󾓧󾓨󾓩󾓪󾓫󾓬󾓭󾓮 
print nice labyrinth                              newressu -i "/\"         
print statistics for ressu run                    newressu --stats         
print rand style random number table              newressu --rand -l20000  

Seuraavassa komento ilman parametreja: komento tulostaa kymmenen riviä desimaalisatunnaislukuja.

jarik>newressu
00000 056552593701856488743208064053594455181555491
00001 647836083655640471912091465183887333363812520
00002 735962951043115775872773766654518963151341269
00003 603704844650943255437410070844585966628674315
00004 784156275041999237378412103711914182300000429
00005 780768799139985291313622649468657009981882664
00006 154427558713802757114262129440213749317145956
00007 137569491937026029405363878424500216776474105
00008 464074752787671526189691150232812511113631660
00009 640527175186115479581523659194559928382662503

Seuraavassa desimaalilukuja välilyönneillä: sanojen oletus pituus desimaaliluvuissa on 5.

>newressu --space
00000  61911 65224 24982 56022 53474 17097 92678
00001  91134 70560 25368 03967 41941 61026 68630
00002  87228 04142 57313 98460 67128 78125 17012
00003  59839 48032 77902 41734 64771 08145 89935
00004  68054 22423 12768 13571 47189 03983 59352
00005  78556 93369 17027 81312 98335 46255 01731
00006  04806 08563 34406 64229 09668 04101 23024
00007  55138 96983 11368 53354 74530 79906 56703
00008  20257 80030 54596 50211 81565 96167 57735
00009  46253 46243 60624 95624 85943 36918 62094

Seuraavassa kolmen merkin pituisia desimaalilukuja:

./newressu --space -s3
00000  184 476 605 450 056 680 522 882 987 770 157
00001  687 753 277 923 892 899 457 807 643 724 719
00002  862 773 931 034 740 401 054 207 909 158 466
00003  915 408 471 515 394 293 693 116 611 041 848
00004  824 829 778 991 875 242 931 270 555 233 375
00005  406 202 690 824 543 189 251 940 188 002 029
00006  216 377 415 765 202 231 355 597 366 998 530
00007  417 258 076 161 729 498 955 049 986 685 665
00008  794 585 407 603 007 283 829 118 759 769 789
00009  288 415 404 584 035 638 508 059 272 102 901

Seuraavassa desimaalisia satunnaislukuja välillä 0-11

./newressu --lim12
00000  00 03 02 01 05 04 06 08 09 00 08 00 05 02 06
00001  03 01 09 08 10 00 03 10 11 04 04 01 03 11 00
00002  11 06 07 03 02 01 06 09 05 11 09 10 11 02 01
00003  06 04 02 10 01 04 04 04 00 08 05 05 10 08 07
00004  01 07 04 00 05 00 03 00 06 07 06 10 06 05 08
00005  03 04 06 05 04 01 01 00 06 06 09 02 01 01 05
00006  10 01 10 08 05 07 07 04 05 03 04 00 00 09 07
00007  00 07 11 04 06 00 00 02 01 07 00 11 07 09 01
00008  00 08 05 08 11 06 11 04 06 03 10 04 01 11 01
00009  01 00 10 10 11 04 01 08 04 05 06 06 01 03 02

Edellinen ilman nollia:

./newressu --lim12 --zero
00000  01 02 07 05 07 11 07 05 11 07 08 11 01 11 08
00001  07 01 06 06 06 04 09 08 10 08 06 02 03 10 03
00002  08 10 01 10 07 06 10 01 03 08 08 08 04 07 07
00003  04 05 02 05 04 03 04 09 06 03 09 09 02 07 03
00004  10 01 07 05 05 07 03 07 08 01 02 02 05 10 11
00005  07 10 09 09 09 10 02 11 01 02 04 03 08 08 02
00006  02 11 08 08 05 01 09 04 08 04 06 09 05 09 02
00007  03 01 10 07 06 10 07 06 07 10 02 06 02 06 06
00008  06 04 05 03 05 05 01 04 09 03 04 05 02 05 11
00009  04 06 05 07 04 01 03 11 10 02 04 10 06 06 05

Edellinen ilman rivinumeroita

./newressu --lim12 --zero --lineno
07 06 02 11 03 10 07 09 01 08 10 03 10 07 11 05 07
05 07 04 06 01 07 02 11 07 06 06 10 11 09 07 08 09
01 11 09 05 11 06 03 01 02 01 07 08 03 05 07 10 09
11 02 08 06 04 07 02 04 03 07 09 06 04 02 05 11 07
06 07 05 08 05 08 04 02 04 05 10 01 11 10 08 08 11
01 08 06 11 03 10 06 05 02 10 09 06 04 01 04 04 08
09 03 02 11 11 04 08 01 11 03 08 11 11 08 07 07 01
10 06 02 10 11 08 03 01 09 03 03 02 11 03 03 09 05
09 11 03 04 03 03 06 01 09 03 03 06 09 07 07 07 06
08 10 08 03 03 08 06 01 10 04 09 04 08 02 10 02 07

Seuraavassa heksadesimaalilukuja, näihin voit tietysti käyttää myös muita edellisiä parametreja.

 ./newressu -x
00000 fbcfbd8a4a099ec66469ee09262455592c969430da55
00001 7d1310727a2bcdf31bce90cc12b064159c09c1db6bd0
00002 c60de6a1cc10cbcadc158ed847e7e46e5ff5f7cbc846
00003 bd6f59d206f842f6049fe069859c16466eb06a84b911
00004 dbad7e8ef05e7a601030396239aee557a8b73cfd1fe0
00005 dfaf1bb7931cba2384de4b65be3813d213e0fdb737e2
00006 f31a950b4164660af6b869ba2155e85db025d515a84a
00007 a1d5acd029b4d59bf306c1376ea09d0dcd99677b7689
00008 53c30e8edb0c6eba4da665a2d22c979a84304f49d243
00009 74aa84093433e3421c25aafd55a9b2e9111ed01bff96

Tulostetaan heksalukuja vain 5 riviä:

./newressu -x -l5
00000 cc47a24ad9e2e9b12e9c780304d860b0f2b9b81e08d6
00001 aeeeac102c5759295cc8de62fa3470748ec4b85ffa1c
00002 6b1aa5da4761932b02f396db5a4b0a67c82d1d973a2e
00003 1e42d547a1e3dd475b40ec5b762531ad44f2495cca0d
00004 efbe4cda2f3f434aa5be5bf5bfc7055ef1b3e38e39dd

Seuraavassa potentiaalisia lottonumeroita: (muista tippi jos voitat..)

./newressu --lim41 --zero
00000  03 29 40 16 06 21 19 25 30 02 34 20 35 18 05
00001  15 30 10 24 14 40 11 26 38 37 07 01 01 34 32
00002  09 23 10 26 27 16 14 17 40 04 04 06 21 29 30
00003  35 33 26 30 03 04 25 03 08 39 18 01 02 36 18
00004  05 30 37 10 35 06 01 07 25 02 33 05 27 01 39
00005  38 25 35 08 03 12 26 04 10 12 26 09 11 12 35
00006  21 31 08 11 08 10 13 36 38 05 07 01 18 08 30
00007  15 33 09 16 31 14 13 13 26 35 01 16 15 30 25
00008  03 14 31 20 05 21 16 28 36 09 26 20 19 36 05
00009  04 10 31 08 22 04 24 13 14 39 25 38 19 34 22

Seuraavassa vielä tuloste rand tyylisestä satunnaislukutaulukosta. Tämä ei mahdu kokonaan ikkunaan, loput sarakkeet on scrollattava esiin:

./newressu --rand
00000   82577 42188  38567 42595  40111 51644  43968 18747  74456 07646
00001   10773 43080  85405 81270  17432 97488  78817 56028  85562 72060
00002   06644 40581  38948 13292  56989 49852  50198 22957  44732 54797
00003   18889 86978  93590 17269  79162 84652  54330 67367  76106 75227
00004   77478 81138  14027 44831  73289 58068  70203 63984  98229 81232

00005   73099 63728  41327 51316  74366 74384  95033 42243  11485 90571
00006   08640 61727  17171 35590  46059 81295  20169 66542  31996 07807
00007   17946 54483  37618 77037  64233 74302  28806 68128  39779 09417
00008   39456 17072  01615 32469  88133 14096  34895 95089  47737 25905
00009   71238 89283  94066 61245  18258 93718  77054 79450  99920 86500

Loput rutiinit on lopun lelusovellusta varten. Tämä ensimmäinen laskee utf8 merkkijonossa olevien merkkien määrin:

int utf8len(unsigned char *buf)
{
  int len;
  unsigned char *p;

  p=buf;
  len=0;
  while(*p!='\0') {
    if(*p<0x80 || // ascii char
       *p>0xbf) // first utf8 byte
      len++;
    p++;
  }
  return(len);
}

Seuraavalla voidaan kopioida haluttu merkki tai utf8 merkin merkkijono string jonosta buf jonoon. Merkin numero annetaan n muuttujalla:

#define aDEBUG38 2

void utf8getchar(int size, unsigned char *buf,
    int n, unsigned char *string)
{
  int d;
  unsigned char *p,*q;

  d=0;
  p=string;
  q=buf;

  // find first byte of character

  while(*p!='\0') {
    if(*p<0x80 || // ascii char
       *p>0xbf) { // first utf8 char
      if(d==n)
        break;
      d++;
    }
    p++;
  }

  // copy first byte and rest
  // of character

  if(*p!='\0') {
    *q++=*p; // copy first byte
    if(*p>0xbf) { // if first is utf8 char
      p++;
      for(;;) {
        if(*p>0xbf || // first utf8 char
           *p<0x80 || // ascii char
           *p=='\0')  // end of file
          break;
        *q++=*p++; // copy rest of the bytes
      }
    }
  }
  *q='\0';

#ifdef DEBUG38
  fprintf(stdout,"%s: utf8getchar:",procname);
  fprintf(stdout," string: %s",string);
  fprintf(stdout,", n: %d",n);
  fprintf(stdout,", character: %s",buf);
  fprintf(stdout,"\n");
#endif
}

Seuraava out_word rutiini tulostaa word2 muuttujan luvun buf merkkijonoon. Konversiossa käytetään digits merkkijonoa, jonka pitäisi sisältää halutun lukujärjestelmän luvut 0:sta loppuun asti, esimerkiksi desimaali merkkijono on “0123456789”, heksadesimaali on “0123456789abcdef”, binääri taas “01” ja oktaali “01234567”.

#define aDEBUG45

void out_word(int size, unsigned char *buf, 
    unsigned char *digits, unsigned long word2) // 8.5.2021 JariK                                                                                                             
{
  int c, d, len, digitslen;
  unsigned long word;
  unsigned char string[132], character[32], *p;

  digitslen = utf8len(digits);
  word=word2;
  len=0;
  string[0]='\0';

  if(word==0 || digitslen<2) {
    // zero
    utf8getchar(sizeof(character),character,0,digits);
    if(len+strlen(character)<sizeof(string)) {
      strcat(string,character);
      len+=strlen(character);
    }
  } else {
    // non zero
    while(word>0) {
      utf8getchar(sizeof(character),character,word%digitslen,digits);
      if(len+strlen(character)<sizeof(string)) {
        strcat(string,character);
        len+=strlen(character);
      }
      word/=digitslen;
    }
  }

  // reverse string

  *buf='\0';
  len=0;
  d=utf8len(string);
  for(c=d-1;c>=0;c--) {
    utf8getchar(sizeof(character),character,c,string);
    if(len+strlen(character)<size) {
      strcat(buf,character);
      len+=strlen(character);
    }
  }

#ifdef DEBUG45
  fprintf(stdout,"]\n%s: out_word: ",procname);
  fprintf(stdout," reverse string: %s",string);
  fprintf(stdout,", digits: %s",digits);
  fprintf(stdout,", int: %lu",word2);
  fprintf(stdout,"\n[");
#endif
}

Seuraava rutiini tekee tulostuksen toisinpäin, eli word-muuttujaan tulee buf kentästä saatavan merkkijonon arvo. Konversiossa käytetään taas digits merkkijonossa olevaa lukusarjan numeroiden luetteloa:

#define DEBUG58 2

void in_word(unsigned long *word, unsigned char *digits,
    unsigned char *buf)
{
  int c,d,e,f,ok;
  unsigned char character[32], character2[32], *p, *q;

  *word=0;
  p=buf;
  d=utf8len(buf);
  f=utf8len(digits);

  for(c=0;c<d;c++) {
    utf8getchar(sizeof(character2),character2,c,buf);
    ok=0;
    for(e=0;e<f;e++) {
      utf8getchar(sizeof(character),character,e,digits);
      if(!strcmp(character,character2)) {
        ok=1;
        break;
      }
    }
    if(ok) {
      *word=((*word)*f)+e;
    } else {
      fprintf(stdout,"%s: in_word:",procname);
      fprintf(stdout," illegal digit '%s'\n",character2);
      help=1;
    }
  }
#ifdef DEBUG58
  fprintf(stdout,"%s: in_word:",procname);
  fprintf(stdout," word: %lu",*word);
  fprintf(stdout,", digits: %s",digits);
  fprintf(stdout,", string: %s",buf);
  fprintf(stdout,"\n");
#endif
}

Lopuksi satunnaislukulelun pääohjelman valittuja palasia. Pääohjelma on postin lopussa kokonaisuudessaan. size muuttujassa on “sanan” eli numeron pituus merkkeinä. Esimerkiksi 10000:n pituus on 5 merkkiä. sspace kertoo tulostetaanko numeroiden väliin välilyönnit. (0=ei tulosteta,1=tulostetaan). chars kertoo, kuinka monta merkkiä mahtuu riville. pchars kertoo kuinka monta merkkiä riville on tällähetkellä tulostettu. words kertoo kuinka monta sanaa mahtuu riville. pwords kertoo kuinka monta sanaa on jo tulostettu. lines on tulostettavien rivien lukumäärä. plines on tulostettujen rivien lukumäärä. quiet on hiljainen tulostus, zero kertoo tulostetaanko nollat vai ei. Edelliset ovat oletusarvoja, komentorivin kytkimillä niille voidaan antaa uusia arvoja.

#define aDEBUG86 2
#define aDEBUG87 2
#define aDEBUG88 2
#define aDEBUG89 2

void main(int argc, char *argv[])
{
  int c, d, bytes=1024, size=5, sspace=0,
    chars=72, pchars=0, words=0, pwords=0,
    lines=10, plines=0, plinesdigits=5, slines=1,
    stats=0, quiet=0, zero=1;
  unsigned char *p, buffer[32], *digits="0123456789",
    digitstemp[256], character[32];
  unsigned long limit;

  procname=argv[0];

Seuraavat debukit kannattaa ehkä jättää päälle (86,87,88,89). 86 debukki tulostaa merkkijonon, jota käytetään konversioissa:

#ifdef DEBUG86

    // ./newressu: digits string used: "0123456789", length: 10
     
    fprintf(stderr,"%s:", procname);
    fprintf(stderr," digits string used: \"%s\"", digits);
    fprintf(stderr,", length: %d", d);
    fprintf(stderr,"\n");
#endif

87 debukki tulostaa merkin numeron ja merkin. Merkit voivat olla ascii merkkejä tai utf8 merkkejä, joissa sisäinen muoto koostuu kahdesta tai useammasta merkistä:

#ifdef DEBUG87

    // ./newressu: digits has characters 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9

    fprintf(stderr,"%s:", procname);
    fprintf(stderr," digits has characters");
    for(c=0;c<d;c++) {
      utf8getchar(sizeof(character),character,c,digits);
      fprintf(stderr," %d:%s",c,character);
    }
    fprintf(stderr,"\n");
#endif

Tämä 88 debukki näyttää digits merkkijonon merkkien järjestysnumeron ja tulostusmerkkijonon heksana.

#ifdef DEBUG88

    //./newressu: characters have codes 0:30 1:31 2:32 3:33 4:34 5:35 6:36 7:37 8:38 9:39

    fprintf(stderr,"%s:", procname);
    fprintf(stderr," characters have codes");
    for(c=0;c<d;c++) {
      utf8getchar(sizeof(character),character,c,digits);
      p=character;
      fprintf(stderr," %d:%02x",c,*p); // first character
      p++;
      while(*p!='\0') {
        if(*p>0xbf || // first utf8 char
           *p<0x80 || // ascii char
           *p=='\0')  // end of file
          break;
        fprintf(stderr,"%02x",*p); // rest of the characters
        p++;
      }
    }
    fprintf(stderr,"\n");
#endif

  } // if(digits!=NULL)

89 debukki tulostaa rivin vain jos merkkijonossa on utf8 merkkejä. Tämä näyttää merkin utf8 koodin kun taas edellinen näytti tulostusmerkkijonon.

#ifdef DEBUG89

    // ./newressu: characters have utf8-codes 0:030 1:031 2:032 3:033 4:034 5:035 6:036 7:037 8:038 9:039 

    // 0c0 ÀÁÂÃÄÅÆÇ ÈÉÊËÌÍÎÏ
    // 0d0 ÐÑÒÓÔÕÖ× ØÙÚÛÜÝÞß
    // 0e0 àáâãäåæç èéêëìíîï
    // 0f0 ðñòóôõö÷ øùúûüýþÿ

    int utffound=0;
    for(c=0;c<d;c++) {
      utf8getchar(sizeof(character),character,c,digits);
      if(strlen(character)>1) {
        utffound=1;
        break;
      }
    }
    if(utffound) {
      fprintf(stderr,"%s:", procname);
      fprintf(stderr," characters have utf8-codes");
      for(c=0;c<d;c++) {
        unsigned long code;
        utf8getchar(sizeof(character),character,c,digits);
        p=character;

        // first byte

        if(*p>=0xfc) code=*p&0x1;       // 1111110 x 10 xxxxxx 10 xxxxxx 10 xxxxxx 10 xxxxxx 10 xxxxxx 7fffffff
        else if(*p>=0xf8) code=*p&0x3;  // 111110 xx 10 xxxxxx 10 xxxxxx 10 xxxxxx 10 xxxxxx
             if(*p>=0xf0) code=*p&0x7;  // 11110 xxx 10 xxxxxx 10 xxxxxx 10 xxxxxx
             if(*p>=0xe0) code=*p&0xf;  // 1110 xxxx 10 xxxxxx 10 xxxxxx
             if(*p>=0xc0) code=*p&0x1f; // 110 xxxxx 10 xxxxxx
             code=*p&0x7f; // ascii
                                                                
        while(*p!='\0') {
          if(*p>0xbf || // first utf8 byte
             *p<0x80 || // ascii char
             *p=='\0')  // end of string
            break;
          code=code<<6|*p&0x3f;
          p++;
        }
        fprintf(stderr," %d:0%lx",c,code);
      }
      fprintf(stderr,"\n");
    } // if(utffound) {
#endif

Ohjelmanpätkä hakee rivinumeron pituuden merkkeinä: Aluksi lines muuttujasta vähennetään yksi, muutetaan se merkkijonoksi out_word toiminnolla. Otetaan edellisen vastauksen pituus merkkeinä. Jos vastaus on lyhyempi kuin 5 merkkiä vastaus on viisi merkkiä.

  // get linenumber length in digits

  out_word(sizeof(wordbuf),wordbuf,"0123456789",lines-1);
  plinesdigits=strlen(wordbuf);
  if(plinesdigits<5)
    plinesdigits=5;

limit muuttujaan määritetään arvo –lim kytkimellä. Menetelmä on sama kun edellisessä kappaleessa ilman viiden minimiä ja edellisessä tulostetaan aina desimaalilukuja.

  // get data length in digits

  if(limit!=0) {
    unsigned char wordbuf[128];
    out_word(sizeof(wordbuf),wordbuf,digits,limit-1);
    size=utf8len(wordbuf);
  }

Tämä rutiini tulostaa sanan, jos sille on määritelty yläraja (limitti). poistetaan sanojen joukosta nollat, tulostetaan ja täytetään etunollat.

if(limit!=0) {
      word=0;

      if(zero) {
        word=ressu_gen_limit(limit); // include zeroes
      } else if(limit>=1) {
        while((word=ressu_gen_limit(limit))==0); // skip zeroes
      }

      out_word(sizeof(wordbuf3),wordbuf3,digits,word);

      // fill leading zeroes

      wordbuf[0]='\0';
      utf8getchar(sizeof(character),character,0,digits);
      for(c=size-utf8len(wordbuf3);c>0;c--) {
        strcat(wordbuf,character);
      }

      // rest of the number

      strcat(wordbuf,wordbuf3);
  }

Tulostus jos ei ole määritelty ylärajaa. Tällöin käytetään merkkien rajoitteena digits merkkijonoa.

  if(digits!=NULL) {
      int digitslen;

      wordbuf[0]='\0';
      digitslen=utf8len(digits);

      // fill whole word digit by digit

      for(c=0;c<size;c++) {
        if(digits[0]=='0' && !zero)
          d=ressu_gen_limit(digitslen-1)+1;
        else
          d=ressu_gen_limit(digitslen);
        utf8getchar(sizeof(wordbuf3),wordbuf3,d,digits);
        strcat(wordbuf,wordbuf3);
      }
    }

Tässä rutiinissa katsotaan tarvitaanko rivinvaihto ennen seuraavaa sanaa, eli jos tulostetun rivin pituus ja utf8 merkkijonon pituus ylittää rivin pituuden, rivin pituus ja ja sanan pituus ylittää rivin pituuden tai tulostettujen sanojen lukumäärä ylittää sanojen lukumäärän tulostetaan rivinvaihto. Lisäksi tässä on käsittely rand tyyppisille riveille (sspace==3). Rand tyylisessä taulussa tulostetaan viiden rivin jälkeen ylimääräinen välilyönti, eli tulostetaan tyhjä rivi. Tyhjä rivi tulostetaan vain jos seuraavalla rivillä on tulostettavaa (plines<lines).

// line full?

    if(((chars > 0 && pchars+utf8len(wordbuf)>=chars) ||
       (chars > 0 && pchars+size>=chars) ||
        (words > 0 && pwords>=words))
       &&pwords > 0) { // atleast one word printed
      pchars=0;
      pwords=0;
      if(!quiet)
        fprintf(stdout,"\n");
      plines++;
      if(sspace==3 && plines<lines && plines%5==0)
        fprintf(stdout,"\n");
      fflush(stdout);
    }

Jos tulostetun rivin numero sivuaa haluttua rivien määrää poistutaan for(;;) luupista.

// all needed lines printed?

    if(plines >= lines)
      break;

Jos olemme rivin alussa (pchars == 0) ja rivinumeron tulostusta ei ole estetty tulostetaan se.

    // in beginning of line, print line number

    if(pchars == 0) {
      sprintf(wordbuf2,"%0*d",plinesdigits,plines);
      if(!quiet && slines) {
        fprintf(stdout,"%s",wordbuf2);
        pchars +=strlen(wordbuf2);
        fprintf(stdout," ");
        pchars++;
      }
    }

Tulostetaanko välilyönti sanojen väliin. Tässä myös rand käsittelyä (sspace==3). Rand tyylisessä taulukossa sanojen väliin tulostetaan välilyönti ja kahden sanan välein tulostetaan toinen välilyönti. Rivinumeron ja ensimmäisen sanan väliin tulee kolme välilyöntiä. Eli sanojen välilyönnit ovat 3 1 2 1 2 1 2 1 2 1.

// want to print spaces between "words"?

    if(sspace && pchars>0) {
      if(!quiet) {
        if(sspace==3 && pwords%2==0)
          fprintf(stdout," ");
        fprintf(stdout," ");
      }
      pchars++;
    }

Ja viimeinen rutiini, varsinaisen sanan tulostus:

// print word

    if(size!=0) {
      if(!quiet)
        fprintf(stdout,"%*s",size,wordbuf);
      pchars += size;
      pwords++;
    } else {
      if(!quiet)
        fprintf(stdout,"%s",wordbuf);
      pchars += strlen(wordbuf);
      pwords++;
    }

Seuraavassa leluohjelma kokonaisuudessaan:

#define aDEBUG75 2
#define aDEBUG76 2

#define aDEBUG86 2
#define aDEBUG87 2
#define aDEBUG88 2
#define aDEBUG89 2

static char *programname = "newressu version 2.0 ©";
static unsigned char *copyright = "Copyright (c) 2013-2021 Jari Kuivaniemi, Helsinki, Finland. Kaikki oikeudet pidätetään!";

void main(int argc, char *argv[])
{
  int c, d, bytes=1024, size=5, sspace=0,
    chars=72, pchars=0, words=0, pwords=0,
    lines=10, plines=0, plinesdigits=5, slines=1,
    stats=0, quiet=0, zero=1;
  unsigned char *p, buffer[32], *digits="0123456789",
    digitstemp[256], character[32];
  unsigned long limit;

  procname=argv[0];

  limit=0;

  // look thru command line parameters

  for(c=1;c<argc;c++) {
    if(!strncmp("-",argv[c],1)) {
      if(!strcmp("--lineno",argv[c])) {
        slines=!slines;

      } else if(!strcmp("--quiet",argv[c])) {
        quiet=!quiet;

      } else if(!strcmp("--stats",argv[c]) ||
        !strcmp("--stat",argv[c])) {
        stats=!stats;

      } else if(!strcmp("--space",argv[c])) {
        sspace=!sspace;

      } else if(!strcmp("--zero",argv[c])) {
        zero=!zero;

      } else if(!strcmp("--copyright",argv[c]) ||
         !strcmp("--version",argv[c])) {
        fprintf(stderr,"%s",programname);
        fprintf(stderr,", %s",copyright);
        fprintf(stderr,"\n\n");
        help=1;

      } else if(!strncmp("--lim",argv[c],5)) {
        if(*(argv[c]+5)!='\0') {
          in_word(&limit, digits, argv[c]+5);
        } else if(c+1<argc) {
          in_word(&limit, digits, argv[c+1]);
          c++;
        }
        sspace=1;

      } else if(!strncmp("-s",argv[c],2)) {
        if(*(argv[c]+2)!='\0') {
          size=atoi(argv[c]+2);
        } else if(c+1<argc) {
          size=atoi(argv[c+1]);
          c++;
        }
      } else if(!strncmp("-w",argv[c],2)) { // words per line
        if(*(argv[c]+2)!='\0') {
          words=atoi(argv[c]+2);
        } else if(c+1<argc) {
          words=atoi(argv[c+1]);
          c++;
        }
        if(words<1)
          words=1;

        chars = 0;

      } else if(!strncmp("-c",argv[c],2)) {  // characters per line
        if(*(argv[c]+2)!='\0') {
          chars=atoi(argv[c]+2);
        } else if(c+1<argc) {
          chars=atoi(argv[c+1]);
          c++;
        }
        words=0;

      } else if(!strncmp("-l",argv[c],2)) { // lines
        if(*(argv[c]+2)!='\0') {
          lines=atoi(argv[c]+2);
        } else if(c+1<argc) {
          lines=atoi(argv[c+1]);
          c++;
        }

      } else if(!strcmp("-x",argv[c])) {
        digits = "0123456789abcdef";
        size=4;

      } else if(!strcmp("-d",argv[c])) {
        digits = "0123456789";
        size=5;

      } else if(!strcmp("-o",argv[c])) {
        digits = "01234567";
        size=3;

      } else if(!strcmp("-b",argv[c])) {
        digits = "01";
        size=8;

      } else if(!strcmp("-1",argv[c])) {
        digits=
          "0123456789" \
          "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
          "abcdefghijklmnopqrstuvwxyz";
        size=8;

      } else if(!strcmp("-2",argv[c])) {
        digits=
          "0123456789" \
          "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
          "abcdefghijklmnopqrstuvwxyz" \
          "_-";
        size=8;

      } else if(!strcmp("-3",argv[c])) { // 9.5.2021 JariK
        digits=
          "!\"#$%&'()*+,-./0123456789:;<=>?@" \
          "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" \
          "abcdefghijklmnopqrstuvwxyz{|}~";
        size=8;

      } else if(!strcmp("-4",argv[c])) {
        digits=
          "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ" \
          "абвгдеёжзийклмнопрстуфхцчшщъыьэюя";
        size=8;

      } else if(!strcmp("--isalnum",argv[c]) || // 9.5.2021 JariK
                !strcmp("--isalpha",argv[c]) ||
                !strcmp("--isdigit",argv[c]) ||
                !strcmp("--isgraph",argv[c]) ||
                !strcmp("--islower",argv[c]) ||
                !strcmp("--isprint",argv[c]) ||
                !strcmp("--ispunct",argv[c]) ||
                !strcmp("--isupper",argv[c]) ||
                !strcmp("--isxdigit",argv[c])) {
        unsigned char *p=digitstemp;
        for(d=0;d<256;d++) {
          if((!strcmp("--isalnum",argv[c]) && isalnum(d)) ||
             (!strcmp("--isalpha",argv[c]) && isalpha(d)) ||
             (!strcmp("--isdigit",argv[c]) && isdigit(d)) ||
             (!strcmp("--isgraph",argv[c]) && isgraph(d)) ||
             (!strcmp("--islower",argv[c]) && islower(d)) ||
             (!strcmp("--isprint",argv[c]) && isprint(d)) ||
             (!strcmp("--ispunct",argv[c]) && ispunct(d)) ||
             (!strcmp("--isupper",argv[c]) && isupper(d)) ||
             (!strcmp("--isxdigit",argv[c]) && isxdigit(d))) {
            *p++=d;
          }
        }
        *p='\0';
        digits=digitstemp;
        size=8;

      } else if(!strcmp("--rand",argv[c])) {
        digits = "0123456789";
        sspace=3;
        slines=1;
        words=10;
        limit=100000;
        //size=5;
        //lines=20000;

      } else if(!strncmp("-i",argv[c],2)) {
        digits=NULL;
        if(*(argv[c]+2)!='\0') {
          digits=argv[c]+2;
        } else if(c+1 < argc) {
          digits=argv[c+1];
          c++;
        }
        if(digits==NULL || utf8len(digits)<2) {
          fprintf(stderr,"%s: not enough digits \"%s\"\n",procname,argv[c]);
          help = 1;
        }
        size=1;

      } else {
        fprintf(stderr,"%s: invalid option %s\n",procname,argv[c]);
        help = 1;
      }
    } else {
      help = 1;
    } // if(!strncmp
  } // for(c=0

  if(digits!=NULL) {
    d = utf8len(digits);

#ifdef DEBUG86

    // ./newressu: digits string used: "0123456789", length: 10

    fprintf(stderr,"%s:", procname);
    fprintf(stderr," digits string used: \"%s\"", digits);
    fprintf(stderr,", length: %d", d);
    fprintf(stderr,"\n");
#endif

#ifdef DEBUG87

    // ./newressu: digits has characters 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9

    fprintf(stderr,"%s:", procname);
    fprintf(stderr," digits has characters");
    for(c=0;c<d;c++) {
      utf8getchar(sizeof(character),character,c,digits);
      fprintf(stderr," %d:%s",c,character);
    }
    fprintf(stderr,"\n");
#endif

#ifdef DEBUG88

    //./newressu: characters have codes 0:30 1:31 2:32 3:33 4:34 5:35 6:36 7:37 8:38 9:39

    fprintf(stderr,"%s:", procname);
    fprintf(stderr," characters have codes");
    for(c=0;c<d;c++) {
      utf8getchar(sizeof(character),character,c,digits);
      p=character;
      fprintf(stderr," %d:%02x",c,*p); // first character
      p++;
      while(*p!='\0') {
        if(*p>0xbf || // first utf8 char
           *p<0x80 || // ascii char
           *p=='\0')  // end of file
          break;
        fprintf(stderr,"%02x",*p); // rest of the characters
        p++;
      }
    }
    fprintf(stderr,"\n");
#endif

#ifdef DEBUG89

    // ./newressu: characters have utf8-codes 0:030 1:031 2:032 3:033 4:034 5:035 6:036 7:037 8:038 9:039

    // 0c0 ÀÁÂÃÄÅÆÇ ÈÉÊËÌÍÎÏ
    // 0d0 ÐÑÒÓÔÕÖ× ØÙÚÛÜÝÞß
    // 0e0 àáâãäåæç èéêëìíîï
    // 0f0 ðñòóôõö÷ øùúûüýþÿ

    int utffound=0;
    for(c=0;c<d;c++) {
      utf8getchar(sizeof(character),character,c,digits);
      if(strlen(character)>1) {
        utffound=1;
        break;
      }
    }
    if(utffound) {
      fprintf(stderr,"%s:", procname);
      fprintf(stderr," characters have utf8-codes");
      for(c=0;c<d;c++) {
        unsigned long code;
        utf8getchar(sizeof(character),character,c,digits);
        p=character;

        // first byte

        if(*p>=0xfc) code=*p&0x1;       // 1111110 x 10 xxxxxx 10 xxxxxx 10 xxxxxx 10 xxxxxx 10 xxxxxx 7fffffff
        else if(*p>=0xf8) code=*p&0x3;  // 111110 xx 10 xxxxxx 10 xxxxxx 10 xxxxxx 10 xxxxxx
        else if(*p>=0xf0) code=*p&0x7;  // 11110 xxx 10 xxxxxx 10 xxxxxx 10 xxxxxx
        else if(*p>=0xe0) code=*p&0xf;  // 1110 xxxx 10 xxxxxx 10 xxxxxx
        else if(*p>=0xc0) code=*p&0x1f; // 110 xxxxx 10 xxxxxx
        else code=*p&0x7f; // ascii
        p++;
        while(*p!='\0') {
          if(*p>0xbf || // first utf8 byte
             *p<0x80 || // ascii char
             *p=='\0')  // end of string
            break;
          code=code<<6|*p&0x3f;
          p++;
        }
        fprintf(stderr," %d:0%lx",c,code);
      }
      fprintf(stderr,"\n");
    } // if(utffound) {
#endif
  } // if(digits!=NULL)

  if(help) {
    struct helpline {
      char *command;
      char *text;
    } helplines[] = {
      "newressu -d", "print random decimal digits",
      "newressu -o", "print octal digits",
      "newressu -x", "print hexadecimal digits",
      "newressu -b", "print binary digits",
      "newressu -d --space", "print decimal digits, with spaces between \"words\"",
      "newressu -b --space", "print binary digits, with spaces between \"words\"",
      "newressu -d -l30", "print decimal digits, 30 lines",
      "newressu -d -l30 --lineno", "print decimal digits, 30 lines, no linenumbers",
      "newressu -d -c128", "print decimal digits, 128 characters per line",
      "newressu -d --lim10", "print decimal numbers between 0-9",
      "newressu -d --lim10 -x","print hexadecimal numbers with decimal limit",
      "newressu -d -s10 --space","print decimal numbers with spaces and wordsize 10",
      "newressu --isalnum", "print alphanumeric characters",
      "newressu --isalpha", "print alphabetic characters",
      "newressu --isdigit", "print digits",
      "newressu --isgraph", "print printables excluding space",
      "newressu --islower", "print lowercase characters",
      "newressu --isprint", "print printable characters including space",
      "newressu --ispunct", "print punctuation characters",
      "newressu --isupper", "print uppercase characters",
      "newressu --isxdigit", "print hexadecimal digits",
      "newressu -1", "print material for passwords",
      "newressu -iaeiouyåäö", "print random vowels",
      "newressu -iаэыуояеёюи", "print russian vowels",
      "newressu -s -i󾓥󾓦󾓧󾓨󾓩󾓪󾓫󾓬󾓭󾓮 ","flags",
      "newressu -i \"/\\\"", "print nice labyrinth",
      "newressu --stats", "print statistics for ressu run",
      "newressu --rand -l20000", "print rand style random number table",
    };

    for(c=0;c<sizeof(helplines)/sizeof(helplines[0]);c++) {
      fprintf(stderr,"%-50s",helplines[c].text);
      fprintf(stderr,"%-25s",helplines[c].command);
      fprintf(stderr,"\n");
    }
    for(c=0;c<chars;c++)
      fprintf(stdout,"*");
    fprintf(stdout,"\n");
    lines=1; // print one line below help as a sample
  }

  unsigned long word;
  unsigned char wordbuf[1024];
  unsigned char wordbuf2[1024];
  unsigned char wordbuf3[1024];

  // get linenumber length in digits

  out_word(sizeof(wordbuf),wordbuf,"0123456789",lines-1);
  plinesdigits=strlen(wordbuf);
  if(plinesdigits<5)
    plinesdigits=5;

  // get data length in digits

  if(limit!=0) {
    unsigned char wordbuf[128];
    out_word(sizeof(wordbuf),wordbuf,digits,limit-1);
    size=utf8len(wordbuf);
  }

  // init statistics

  for(;;) {

    if(limit!=0) {
      word=0;

      if(zero) {
        word=ressu_gen_limit(limit); // include zeroes
      } else if(limit>=1) {
        while((word=ressu_gen_limit(limit))==0); // skip zeroes
      }

      out_word(sizeof(wordbuf3),wordbuf3,digits,word);

      // fill leading zeroes

      wordbuf[0]='\0';
      utf8getchar(sizeof(character),character,0,digits);
      for(c=size-utf8len(wordbuf3);c>0;c--) {
        strcat(wordbuf,character);
      }

      // rest of the number

      strcat(wordbuf,wordbuf3);

    } else if(digits!=NULL) {
      int digitslen;

      wordbuf[0]='\0';
      digitslen=utf8len(digits);

      // fill whole word digit by digit

      for(c=0;c<size;c++) {
        if(digits[0]=='0' && !zero)
          d=ressu_gen_limit(digitslen-1)+1;
        else
          d=ressu_gen_limit(digitslen);
        utf8getchar(sizeof(wordbuf3),wordbuf3,d,digits);
        strcat(wordbuf,wordbuf3);
      }
    }

    // line full?

    if(((chars > 0 && pchars+utf8len(wordbuf)>=chars) ||
       (chars > 0 && pchars+size>=chars) ||
        (words > 0 && pwords>=words))
       &&pwords > 0) { // atleast one word printed
      pchars=0;
      pwords=0;
      if(!quiet)
        fprintf(stdout,"\n");
      plines++;
      if(sspace==3 && plines<lines && plines%5==0)
        fprintf(stdout,"\n");
      fflush(stdout);
    }

    // all needed lines printed?

    if(plines >= lines)
      break;

    // all needed lines printed?

    if(plines >= lines)
      break;

    // in beginning of line, print line number

    if(pchars == 0) {
      sprintf(wordbuf2,"%0*d",plinesdigits,plines);
      if(!quiet && slines) {
        fprintf(stdout,"%s",wordbuf2);
        pchars +=strlen(wordbuf2);
        fprintf(stdout," ");
        pchars++;
      }
    }

    // want to print spaces between "words"?

    if(sspace && pchars>0) {
      if(!quiet) {
        if(sspace==3 && pwords%2==0)
          fprintf(stdout," ");
        fprintf(stdout," ");
      }
      pchars++;
    }

    // print word

    if(size!=0) {
      if(!quiet)
        fprintf(stdout,"%*s",size,wordbuf);
      pchars += size;
      pwords++;
    } else {
      if(!quiet)
        fprintf(stdout,"%s",wordbuf);
      pchars += strlen(wordbuf);
      pwords++;
    }
  } // for(;;)
}