Seed7

Altri tipi

Vediamo ora, si tratta solo di un accenno in attesa di approfondimenti, altri tipi di dati che ci saranno utili nel prosieguo.

Iniziamo parlando dei bigInteger, si tratta di interi con segno di grandezza illimitata. Formalmente invece abbiamo una sequenza di cifre seguita dal carattere _ (underscore). Se si usa una base diversa dal consueto 10 la rappresentazione adatta per il tipo bigInteger è dato dalla base seguita dal # e poi dalle cifre seguito dal solito underscore. Quindi per esempio:

12_ in base 10
16#AB_ in base esadecimale.

Per utilizzare i bigInteger è necessario includere la libreria bigint. Per capire rapidamente la differenza rispetto agli interi normali considerate il seguente esempio:

  Esempio 6.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ include "seed7_05.s7i";
include "bigint.s7i";

const proc: main is func
local
var integer: n1 is 0;
var bigInteger: n is 0_;

begin
for n range 0_ to 16_ do
writeln(n <& "! = " <& !n);
end for;
for n1 range 0 to 16 do
writeln(n1 <& "! = " <& !n1);
end for;

end func;

La riga 2 mette in evidenza una particolarità: quando si chiamano più librerie si usa comunque una sola volta il simbolo $ con cui inizia la riga 1.
La riga 7 ci presenta il bigInteger n.
La riga 10 adopera detto bigInteger. Da notare il carattere underscore.
Se eseguirete l'esempio 6.1 noterete come il primo for va a buon fine, in quanto bigInteger è capiente a sufficienza, il secondo for, quello che usa il tipo integer, invece va in crash a causa di un overflow.

La differenza di notazione non è solo formale ma anche effettiva. Interi e bigInteger sono profondamente diversi e non è possibile riversare uno nell'altro in via diretta. Ovvero scrivere:

var integer: x1 is 4;
var bigInteger: x2 is 0_;
.....
x2 := x1;

non va bene. Per fare questo tipo di conversione è necessario ricorrere ad una funzione interna, ovvero conv. Vediamo un esempio:

  Esempio 6.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ include "seed7_05.s7i";
include "bigint.s7i";

const proc: main is func
local
var integer: n1 is 0;
var bigInteger: n2 is 0_;

begin
n1 := n1 + 1;
n2 := bigInteger conv(n1);
writeln(n2 + 1_);

end func;

L'istruzione alla riga 11 compie l'operazione in modo corretto.
Per fare il discorso inverso è possibile invece ricorrere alla funzione ord. Questa funzione in maniera sicura sollevando una eccezione nel momento in cui il bigInteger riversato sia troppo grosso per essere contenuto nel range degli interi. Ecco un frammento che presenta il semplice procedimento:

var bigInteger: bi1 is 1_;
var integer: s1 is 0;
........
s1 := ord(bi1);


Analogamente è necessario ricorrere alla funzione parse per gestire un input in formato stringa; il seguente frammento prevede che s1 sia di tipo stringa e bi1 sia il bigInteger:

bi1 := bigInteger parse(s1);

Le operazioni di base riportano a quanto già visto per il tipo intero. La libreria contiene molte altre funzioni che potete trovare sul sito ufficiale.

I razionali sono invece costituiti da una coppia formata da un numeratore e un denominatore entrambi di tipo intero. Tenendo presente che è necessario includere la libreria rational.s7i, formalmente la cosa è molto semplice:

var rational: nomevar is numeratore / denominatore;

questo che segue è un esempio il cui output è interessante  e ci offre lo spunto per una osservazione pratica:

  Esempio 6.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ include "seed7_05.s7i";
include "rational.s7i";

const proc: main is func
local
var rational: r1 is 2/3;
var rational: r2 is 3/4;
begin
writeln(r1);
writeln(r1 + r2);
writeln(r1 - r2);
writeln(r1 * r2);
writeln(r1 / r2);

end func;

che ci restituisce:

2/3
17/12
-1/12
1/2
8/9


Numeratore e denominatore devono essere di tipo intero. In questo senso se x1 e x2 sono due variabili intere allora un razionale può essere espresso come
x1/x2
Da un punto di vista pratico è evidente dall'esempio che si fa molto uso del minimo comune multiplo per quanto riguarda i denominatori. Questo può abbastanza facilmente portare a degli overflow sul tipo intero e questo avviene in maniera silente (non bello, lo so, ma è così). Si consiglia pertanto in linea di massima di preferire l'uso dei bigRational che vedremo tra poco.
Per passare da un razionale all'intero corrispondente si possono usare 4 funzioni precotte:
  • floor - tronca verso il basso, quindi: floor(1.8) = 1
  • ceil - tronca verso l'alto, quindi: ceil(1.1) = 2
  • trunc - tronca verso 0
  • round - arrotonda (da .5 in su verso l'alto da .4 in giù verso il basso)
la funzione rat invece trasforma da intero in razionale. Stessa trasformazione è possibile eseguirla tramite rationat conv(intero). Vediamo un esempio che illustra l'uso di alcune di queste funzioni:

  Esempio 6.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ include "seed7_05.s7i";
include "rational.s7i";

const proc: main is func
local
var rational: r1 is 2/3;
var rational: r2 is 3/4;
var integer: x1 is 0;
begin
writeln(r1);
writeln(r1 + r2);
writeln(r1 - r2);
writeln(r1 * r2);
writeln(r1 / r2);
r2 := rat(3);
writeln(r2);
x1 := floor(r1);
writeln(x1);

end func;

Può anche essere interessante capire come trovare il valore vero di un razionale, quindi non espresso in frazione. Un sistema può essere il seguente:

  Esempio 6.5
1
2
3
4
5
6
7
8
9
10
11
12
13
$ include "seed7_05.s7i";
include "rational.s7i";
include "float.s7i";

const proc: main is func
local
var rational: r1 is 2/3;
var float: f1 is 0.0;
begin
f1 := (flt(r1.numerator) / flt(r1.denominator));
writeln(f1);

end func;

usando gli interessanti numerator e denominator che estraggono, appunto, numeratore e denominatore come interi, di qui la necessità di usare flt.
Se invece vi servisse di effettuare il parsing da una stringa per costruire un rational allora dovrete usare rational parse(stringa).

Come accennato più sicuri sono i bigRational. Con essi è molto più difficile avere dei problemi di overflow. Diversamente dai razionali un bigRational è composto tramite 2 bigInteger:

numerator / denominator

dove entrambi i componenti sono, appunto, bigInteger. Ad esempio:

var bigRational: br1 is 1_ / 2_

Per poter lavorare con i bigRational bisogna includere la libreria bigrat.s7i. Le regole sono le medesime, da un punto di vista logico, viste per i rational, con la differenza, da non dimenticare, che i partner sono i bigInteger. Ad esempio la funzione rat(A) converte A, bigInteger, in bigRational. Vediamo un esempio che ci mostra come passare al valore reale, float, operazione che si presenta appena più complicata rispetto a quanto visto nell'esempio 6.5.

  Esempio 6.6
1
2
3
4
5
6
7
8
9
10
11
12
13
$ include "seed7_05.s7i";
include "bigrat.s7i";
include "float.s7i";

const proc: main is func
local
var bigRational: br1 is 3_/7_;
var float: f1 is 0.0;
begin
writeln(br1);
f1 := flt(ord(br1.numerator)) / flt(ord(br1.denominator));
writeln(f1);
end func;

La "complicazione" è dovuta al fatto di dover usare la funzione ord, in aggiunta. Tutto qui.

In questo sede do solo un accenno ai numeri complessi. Il tipo relativo si chiama complex e per poter essere utilizzato deve essere inclusa la libreria complex.s7i.
Un numero complesso è costituito da una parte reale (re) ed una immaginaria (im) entrambe di tipo float.
Per ora non useremo questa tipologia di dati.