[Algorytm] Algorytm konwersji liczb na postać słowną

Projekty użytkowników forum zarówno sprzętowe, jak i związane z programowaniem w dowolnym języku.
Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

[Algorytm] Algorytm konwersji liczb na postać słowną

Postautor: gaweł » czwartek 25 sty 2018, 01:28

Algorytm konwersji liczb na postać słowną

Ciszę w pracach wypełniała muza:https://www.youtube.com/watch?v=HrXCW11Wifc

Płacąc rachunki czasami zdarza mi się uważniej przyjrzeć blankietowi na przelew. Taki dokument zawiera kwotę do zapłaty (zawsze uważam, że za dużą, ale to mój punkt widzenia i z pewnością „strona przeciwna” nie podziela tej opinii :) ). A poważnie, to … by zminimalizować lub uniemożliwić jakiekolwiek manipulacje, kwota powinna być wyrażona w postaci słownej. Jednak na wydrukowanym blankiecie przelewu nie znajduję wydrukowanej kwoty słownie tylko wydrukowane jedynie cyfry i to w dodatku w formie skróconej.
Tak zastanawiam się nad przyczyną takiego stanu rzeczy. Czy to wynika z lenistwa twórców oprogramowania? Sam kiedyś tworząc przeróżne oprogramowanie natknąłem się na podobną problematykę. Wygrzebałem więc z signumowego archiwum fragment oprogramowania, którego zadaniem jest wyrażenie kwoty w formie słownej. Algorytm pochodzi z okresu połowy lat dziewięćdziesiątych ubiegłego wieku. Zadałem sobie tylko trochę trudu by wyrazić go w języku C (w oryginale jest napisany w języku MODULA 2) i umieścić go w realiach mikrokontrolera AVR. Może komuś się przyda, więc będzie jak znalazł (adaptacja do realiów maszyn w architekturze innej niż Harvard wymaga kosmetycznych zmian).
Algorytm poprawnie, w sensie języka polskiego, generuje końcówki wyrazów. Jakby dokładniej się przyjrzeć gramatyce polskiej, to można dostrzec, że jest podobne jak „Kali liczyć: jeden, dwa, dużo”. Jest podobnie, tylko występuje szerszy zakres (a dokładniej, granice są w innych miejscach). Na przykładzie złotych, to poprawnie brzmi:
  • jeden złoty,
  • dwa złote,
  • trzy złote,
  • cztery złote,
  • pięć złotych.
Występują trzy różne formy „jednostki”: dla 1 → złoty, dla 2, 3 i 4 → złote , dla pozostałych (5 .. 9) → są złote. W przypadku nastu (11 .. 19) → złotych. W przypadku liczb większych istotna jest ostatnia cyfra i tu obowiązuje taka sama forma jak dla zakresu 1 .. 9. Liczby w istocie są wyrażane w formie trzycyfrowej z podaniem słownego mnożnika (sto, tysiąc, milion, miliard …). I tu można dostrzec fraktalne podobieństwo:
  • jeden milion,
  • dwa, trzy, cztery miliony,
  • pięć, sześć, siedem, osiem, dziewięć milionów.
Występują identyczne zasady gramatyczne jak w przypadku jednostek. W gruncie rzeczy algorytm musi przetwarzać jedynie liczby trzycyfrowe i do tej liczby dodać właściwą „jednostkę”. Tą funkcjonalność realizuje funkcja Liczba3CyfrowaNaNapis, która zwraca informację o wymaganej końcówce jako jedną z następujących możliwych:

Kod: Zaznacz cały

#define Zero                            0
#define Jeden                           1
#define Kilka                           2
#define Duzo                            3

  • Zero – oznacza, że nie wystąpiła żadna aktywna konwersja i służy do eliminacji takich przypadków jak: zero milionów zero tysięcy … (brzmi to trochę pokracznie),
  • Jeden – oznacza konieczność dodania „mnożnika” w wariancie jeden (przykładowo jeden milion),
  • Kilka – oznacza konieczność dodania „mnożnika” w wariancie kilka (dokładnie w zakresie 2..4: przykładowo 3 miliony),
  • Duzo – oznacza koniecznośc dodania „Mnożnika” w wariancie dużo (dokładnie w zakresie 5..9: przykładowo 6 milionów).

Kod: Zaznacz cały

static uint8_t Liczba3CyfrowaNaNapis ( uint8_t * Napis ,
                                       uint16_t  Liczba )
{
  uint16_t Cyfra100 ;   /* cyfra setek */
  uint16_t Cyfra10 ;    /* cyfra dziesiated */
  uint16_t Cyfra1 ;     /* cyfra jednosci */
  /*-------------------------------------------------------------------------*/
  * Napis = 0 ;
  if ( Liczba == 0 )
  {
    return ( Zero ) ;
  } /* if */ ;
  Cyfra100 = Liczba / 100 ;
  Liczba = Liczba % 100 ;
  Cyfra10 = Liczba / 10 ;
  Cyfra1 = Liczba % 10 ;
  if ( Cyfra100 != 0 )
  {
    StrCpyFlash ( Napis , ( uint16_t ) Setki [ Cyfra100 ] ) ;
  } /* if */ ;
  if ( ( Cyfra10 == 1 ) & ( Cyfra1 != 0 ) )
  {
    AppendFlash ( Napis , ( uint16_t ) Nascie [ Cyfra1 ] ) ;
    return ( Duzo ) ;
  } /* if */ ;
  if ( Cyfra10 != 0 )
  {
    AppendFlash ( Napis , ( uint16_t ) Dziesiatki [ Cyfra10 ] ) ;
  } /* if */ ;
  if ( Cyfra1 != 0 )
  {
    AppendFlash ( Napis , ( uint16_t ) Jednostki [ Cyfra1 ] ) ;
  } /* if */ ;
  switch ( Cyfra1 )
  {
    case 0      :
      return ( Duzo ) ;
    case 1      :
      if ( ( Cyfra100 == 0 ) & ( Cyfra10 == 0 ) )
        return ( Jeden ) ;
      else
        return ( Duzo ) ;
    case 2      :
    case 3      :
    case 4      :
      if ( Cyfra10 != 1 )
        return ( Kilka ) ;
      else
        return ( Duzo ) ;
    case 5      :
    case 6      :
    case 7      :
    case 8      :
    case 9      :
      return ( Duzo ) ;
    default     :
      return ( Zero ) ;
  } /* switch */ ;
} /* Liczba3CyfrowaNaNapis */
Operując liczbami określającymi kwoty (wyrażonych w groszach) z zakresu miliardów, konieczne jest posiłkowanie się arytmetyką 64-bitową. W przypadku rezygnacji z miliardów (nie widzę powodów, by z tego rezygnować), wystarczy arytmetyka 2-bitowa.
Główna funkcja konwersji wydziela z kwoty grosze (posiłkuje się stałą C02 → 102), najmłodsza 3-cyfrowa część liczby (posiłkuje się stałą C05 → 105), kolejna 3-cyfrowa część jest wydzielana za pomocą stałej C08 → 108 oraz najbardziej znacząca część liczby wydzielona za pomocą stałej C1011 → 1011.
Funkcja ma następująca postać:

Kod: Zaznacz cały

void Slownie ( uint64_t  Kwota ,
               uint8_t * Napis )
{
  uint16_t CzescLiczby ;
  uint8_t LokalnyNapis [ 63 ] ;
  uint8_t Zakres ;
  uint8_t OstatniZakres ;
  /*-------------------------------------------------------------------------*/
  * Napis = 0 ;
  OstatniZakres = 0xFF ;
/* miliardy */
  CzescLiczby = ( uint16_t ) ( Kwota / C1011 ) ;  /* 10 ** 11 */
  Kwota = Kwota % C1011 ;                         /* 10 ** 11 */
  Zakres = Liczba3CyfrowaNaNapis ( Napis , CzescLiczby ) ;
  if ( Zakres != Zero )
  {
    OstatniZakres = Duzo ;
    switch ( Zakres )
    {
      case Jeden :
        AppendFlash ( Napis, ( uint16_t ) Miliard ) ;
        break ;
      case Kilka :
        AppendFlash ( Napis, ( uint16_t ) Miliardy ) ;
        break ;
      case Duzo  :
        AppendFlash ( Napis, ( uint16_t ) Miliardow ) ;
    } /* switch */ ;
  } /* if */ ;
/* miliony */
  CzescLiczby = ( uint16_t ) ( Kwota  / C108 ) ;  /* 10 ** 8 */
  Kwota = Kwota % C108 ;                          /* 10 ** 8 */
  Zakres = Liczba3CyfrowaNaNapis ( LokalnyNapis , CzescLiczby ) ;
  if ( Zakres != Zero )
  {
    OstatniZakres = Duzo ;
    Append ( Napis , LokalnyNapis ) ;
    switch ( Zakres )
    {
      case Jeden :
        AppendFlash ( Napis, ( uint16_t ) Milion ) ;
        break ;
      case Kilka :
        AppendFlash ( Napis, ( uint16_t ) Miliony ) ;
        break ;
      case Duzo  :
        AppendFlash ( Napis, ( uint16_t ) Milionow ) ;
    } /* switch */ ;
  } /* if */ ;
/* tysiace */
  CzescLiczby = ( uint16_t ) ( Kwota / C105 ) ;   /* 10 ** 5 */
  Kwota = Kwota % C105 ;                          /* 10 ** 5 */
  Zakres = Liczba3CyfrowaNaNapis ( LokalnyNapis , CzescLiczby ) ;
  if ( Zakres != Zero )
  {
    OstatniZakres = Duzo ;
    Append ( Napis , LokalnyNapis ) ;
    switch ( Zakres )
    {
      case Jeden :
        AppendFlash ( Napis, ( uint16_t ) Tysiac ) ;
        break ;
      case Kilka :
        AppendFlash ( Napis, ( uint16_t ) Tysiace ) ;
        break ;
      case Duzo  :
        AppendFlash ( Napis, ( uint16_t ) Tysiecy ) ;
    } /* switch */ ;
  } /* if */ ;
/* zlote */
  CzescLiczby = ( uint16_t ) ( Kwota / C102 ) ;   /* 10 ** 2 */
  Kwota = Kwota % C102 ;                          /* 10 ** 2 */
  Zakres = Liczba3CyfrowaNaNapis ( LokalnyNapis , CzescLiczby ) ;
  if ( Zakres != Zero )
  {
    Append ( Napis , LokalnyNapis ) ;
    OstatniZakres = Zakres ;
  } /* if */ ;
  if ( OstatniZakres != 0xFF )
  {
    switch ( OstatniZakres )
    {
      case Jeden :
        AppendFlash ( Napis, ( uint16_t ) Zloty ) ;
        break ;
      case Kilka :
        AppendFlash ( Napis, ( uint16_t ) Zlote ) ;
        break ;
      case Duzo  :
        AppendFlash ( Napis, ( uint16_t ) Zlotych ) ;
    } /* switch */ ;
  } /* if */ ;
/* grosze */
  CzescLiczby = ( uint16_t ) Kwota ;
  Zakres = Liczba3CyfrowaNaNapis ( LokalnyNapis , CzescLiczby ) ;
  Append ( Napis , LokalnyNapis ) ;
  switch ( Zakres )
  {
    case Zero  :
      AppendFlash ( Napis, ( uint16_t ) Jednostki [ 0 ] ) ;
      AppendFlash ( Napis, ( uint16_t ) Groszy ) ;
      break ;
    case Jeden :
      AppendFlash ( Napis, ( uint16_t ) Grosz ) ;
      break ;
    case Kilka :
      AppendFlash ( Napis, ( uint16_t ) Grosze ) ;
      break ;
    case Duzo  :
      AppendFlash ( Napis, ( uint16_t ) Groszy ) ;
  } /* switch */ ;
} /* Slownie */
Kompletny program prezentujący algorytm jest przeznaczony dla mikrokopntrolera ATMEGA32, w którym używany jest jedynie kanał transmisji szeregowej.
Program ten pozwala poprzez łącze szeregowe wprowadzić znakowo liczbę, która zostaje przetworzona na postać słowną i wysłana w kanał szeregowy.
slo_ilu00.jpg

Łącząc się z programem poprzez dowolny emulator terminala mamy:
Czesc koles z http://www.microgeek.eu:
Wprowadz jakas liczbe (w groszach) i walnij na koniec Enter


0
zero groszy

1
jeden grosz

2
dwa grosze

3
trzy grosze

4
cztery grosze

5
piec groszy

6
szesc groszy

7
siedem groszy

8
osiem groszy

9
dziewiec groszy

10
dziesiec groszy

11
jedenascie groszy

12
dwanascie groszy

20
dwadziescia groszy

21
dwadziescia jeden groszy

22
dwadziescia dwa grosze

23
dwadziescia trzy grosze

50
piecdziesiat groszy

99
dziewiecdziesiat dziewiec groszy

100
jeden zloty zero groszy

101
jeden zloty jeden grosz

123
jeden zloty dwadziescia trzy grosze

1234
dwanascie zlotych trzydziesci cztery grosze

12345
sto dwadziescia trzy zlote czterdziesci piec groszy

123456
jeden tysiac dwiescie trzydziesci cztery zlote piecdziesiat szesc groszy

1234567
dwanascie tysiecy trzysta czterdziesci piec zlotych szescdziesiat siedem groszy

12345678
sto dwadziescia trzy tysiace czterysta piecdziesiat szesc zlotych siedemdziesiat osiem groszy

123456789
jeden milion dwiescie trzydziesci cztery tysiace piecset szescdziesiat siedem zlotych osiemdziesiat dziewiec groszy

1234567890
dwanascie milionow trzysta czterdziesci piec tysiecy szescset siedemdziesiat osiem zlotych dziewiecdziesiat groszy

12345678901
sto dwadziescia trzy miliony czterysta piecdziesiat szesc tysiecy siedemset osiemdziesiat dziewiec zlotych jeden grosz

123456789012
jeden miliard dwiescie trzydziesci cztery miliony piecset szescdziesiat siedem tysiecy osiemset dziewiecdziesiat zlotych dwanascie groszy

1234567890123
dwanascie miliardow trzysta czterdziesci piec milionow szescset siedemdziesiat osiem tysiecy dziewiecset jeden zlotych dwadziescia trzy grosze

12345678901234
sto dwadziescia trzy miliardy czterysta piecdziesiat szesc milionow siedemset osiemdziesiat dziewiec tysiecy dwanascie zlotych trzydziesci cztery grosze


Załącznik, przykładowy program:
slownie.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
tasza
Geek
Geek
Posty: 1082
Rejestracja: czwartek 12 sty 2017, 10:24
Kontaktowanie:

Re: [Algorytm] Algorytm konwersji liczb na postać słowną

Postautor: tasza » czwartek 25 sty 2018, 10:21

gaweł pisze: Jednak na wydrukowanym blankiecie przelewu nie znajduję wydrukowanej kwoty słownie tylko wydrukowane jedynie cyfry i to w dodatku w formie skróconej.
Tak zastanawiam się nad przyczyną takiego stanu rzeczy. Czy to wynika z lenistwa twórców oprogramowania?


No to nie wynika z lenistwa niczyjego ani nic, tylko ze względów czysto praktycznych – zapisana skrótami kwota jest kompaktna i nie ma ryzyka, że nie zmieści w rubryczkach blankietu. Sensem zapisu słownie/skrótami jest powstanie redundancji informacji, jeżeli cokolwiek się nie zgadza, przelew nie powinien być przyjęty do wpłaty, a ręcznie korygowany – nie może być podstawą do roszczeń.

Aha, zapis skrócony jest dopuszczalny ale nie jest zalecany, bo jednak jest podatniejszy na przerabianie niż w pełni gramatyczny zapis liczebników , przykład:
1234.56 -> *jeden*dwa*trzy*cztery*zł*pięć*sześć*gr*
91234.56 -> *dziewięć *jeden*dwa*trzy*cztery*zł*pięć*sześć*gr* - naiwne zmanipulowanie kwoty

Pewnym uszczelnieniem tego mechanizmu jest mocne zalecenie aby w przypadku pojedynczych tysięcy, milionów na początku zapisu dawać liczebnik jeden, wtedy trudniej coś na początku dopisać, bez wzbudzania podejrzeń.
______________________________________________ ____ ___ __ _ _ _ _
Kończysz tworzyć dopiero, gdy umierasz. (Marina Abramović)

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [Algorytm] Algorytm konwersji liczb na postać słowną

Postautor: gaweł » czwartek 25 sty 2018, 11:09

tasza pisze:Pewnym uszczelnieniem tego mechanizmu jest ...

W signumowych programach w takich przypadkach wydruki liczbowe były zawsze poprzedzone bezpośrednio znakiem gwiazdki. Jednak w niektórych przypadkach był wymóg zapisu słownego, dlatego został wykumany powyższy algorytm.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse


Wróć do „DIY”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 1 gość