Dormire tranquilli.

Quando scrivo bene di ChatGPT e dell'intelligenza artificiale puntualmente ricevo commenti secondo i quali i primi a perdere il posto di lavoro saranno proprio quelli come me, perche' ChatGPT puo' gia' programmare e configrare cose. E anche fare design. Non perdero' tempo a discutere quali siano i limiti di un modello di linguaggio, per quanto grande, per cui , complice una chiacchierata casuale, ho deciso di provare.
Ho quindi provato con un compito semplicissimo: una configurazione del packet filter di un BSD qualsiasi, che lasci entrare solo una porta del protocollo IPv6, e blocchi il resto.
Gli ho spiegato il problema e poi gli ho anche detto che intendevo il Packet Filter di OpenBSD.
Guardate che cosa e' successo:

Se conoscete il firewall di OpenBSD , vi sentirete un pochino a disagio. Quello che vi sta consigliando di fare NON e' esattamente quello che gli avevate chiesto. Decisamente no.
E cosi':

Ora, una cosa e' sicura: non usate ChatGPT per configurare firewall.
L'errore e' grave? Quanto grave?
Per i non avvezzi, avevo chiesto a ChatGPT di creare un firewall che lasciasse passare verso l'interno (che intendo difendere), solo una specifica porta. In breve, questo:

Quello che ha fatto e' stato questo:

Quando ho detto che c'era un problema, si e' corretto. E questo significa che puo' essere comodo per evitare di digitare tutto, se avete familiarita' con Pf, ma se siete incompetenti usare chatGPT e fare copia-incolla e' rischiosissimo.
Lo stesso vale per il codice dei programmatori:

Adesso voi direte: ma la soluzione e' corretta. Ni. Nel senso che approssimera' pigreco (in tempi finiti e' ovvio), ma io non ho mai chiesto di limitare le iterazioni a 10000. Sapendo cosa aspettarmi, pero', so che ha anche usato un modo implicito di dichiarare le variabili, e so che si poteva fare molto di meglio.
Con questa routine che usa normali float a 32 bit, e un limite di diecimila, il risultato si ferma cosi':
3.1414926535900345 (10000)
Ma il problema e': se non 10000, qual'e' il numero migliore? E cosa succederebbe se usassi numeri a 64 bit?
Rifacciamo:
package main
import (
"fmt"
"math"
)
func main() {
// n := 10000
// Numero di termini da utilizzare nell'espansione di McLaurin
// Calcola il valore di π utilizzando lo sviluppo di McLaurin dell'arcotangente
var pi float64 = 0.0
var last float64 = 1.0
for i := 0; last-pi != 0; i++ {
last = pi
sign := math.Pow(-1, float64(i))
term := sign / (2*float64(i) + 1)
pi += 4 * term
fmt.Printf("Numero di iterazioni %d , valore %1.30f \n", i, pi)
}
}
Questo codice e' migliore? Beh, se vogliamo tenere il paradigma della serie di Mc Laurin dell'arcotangente, si. Infatti, finisce cosi':
Numero di iterazioni 27830 , valore 3.141628584745683294698892495944
Numero di iterazioni 27831 , valore 3.141556723724897359772967320168
Numero di iterazioni 27832 , valore 3.141628582163772609447960348916
Numero di iterazioni 27833 , valore 3.141556726306621971644972290960
Numero di iterazioni 27834 , valore 3.141628579582233182776462854235
Numero di iterazioni 27835 , valore 3.141556728887975769026752459467
Numero di iterazioni 27836 , valore 3.141628577001064570595190161839
Perche' finisce cosi'? Perche una serie decrescente finisce col convergere ma lo fa solo in R, mentre il computer non usa R, ma solo un insieme discreto. E quindi ha un epsilon di macchina.
Secondo, il numero di interazioni che affronta prima di non poter distinguere due valori successivi (da quel momento andare avanti non servirebbe a nulla) e' di 27836, il che rende arbitrario il precedente limite di 10000 iterazioni, che non ho mai chiesto.
E se osservate con occhio matematico i numeri, notate che l'errore di macchina produce caos, e questo caos ha due attrattori: {3.14155672 , 3.1416285}. Il che significa che l'algoritmo si e' fermato "per quasi caso". Il risultato, cioe', orbita attorno a quei due punti, perche' il troncamento produce caos.
Ora, e' vero che nemmeno il programmatore medio sa che puo' generare caos e attrattori anche per sbaglio, scrivendo codice apparentemente corretto. La cosa non cambia se usiamo la somma dei resti, perche' in questo caso il problema e' il troncamento dovuto all'epsilon di macchina (ove macchina e' il modello di numeri di golang) . Ma adesso possiamo stimare l'errore:
package main
import (
"fmt"
"math"
)
func main() {
// Numero di termini da utilizzare nell'espansione di McLaurin
// n := 10000
// Calcola il valore di π utilizzando lo sviluppo di McLaurin dell'arcotangente e i resti della serie
pi := 0.0
prevPi := 0.1
remainder := 0.0
for i := 0; pi-prevPi != 0; i++ {
sign := math.Pow(-1, float64(i))
term := sign / (2*float64(i) + 1)
prevPi = pi
pi += 4 * term
remainder += math.Abs(pi - prevPi - 4*term)
fmt.Printf("Numero di iterazioni %d , valore %1.30f , errore: %e \n", i, pi, remainder)
}
}
E otterremo anche la precisione:
Numero di iterazioni 24311 , valore 3.141551521638504151923143581371 , errore: 2.699995e-12
Numero di iterazioni 24312 , valore 3.141633783849301142510057616164 , errore: 2.700070e-12
Numero di iterazioni 24313 , valore 3.141551525021900825862530837185 , errore: 2.700169e-12
Numero di iterazioni 24314 , valore 3.141633780466182912505246349610 , errore: 2.700301e-12
Numero di iterazioni 24315 , valore 3.141551528404740611932766114478 , errore: 2.700311e-12
Numero di iterazioni 24316 , valore 3.141633777083621126280377211515 , errore: 2.700392e-12
Numero di iterazioni 24317 , valore 3.141551531787023954223059263313 , errore: 2.700464e-12
Numero di iterazioni 24318 , valore 3.141633773701615783835450201877 , errore: 2.700521e-12
Numero di iterazioni 24319 , valore 3.141551535168751296822620133753 , errore: 2.700608e-12
Numero di iterazioni 24320 , valore 3.141633770320166885170465320698 , errore: 2.700724e-12
Numero di iterazioni 24321 , valore 3.141551538549922639731448725797 , errore: 2.700912e-12
Numero di iterazioni 24322 , valore 3.141633766939273986196212717914 , errore: 2.700990e-12
Come vedete, usando la somma dei resti il valore arriva leggermente prima, il resto della serie sarebbe attorno ai 10 alla meno dodici, ma abbiamo ancora degli effetti caotici.
In realta' spero che non userete mai questo metodo perche' fa schifo per altre ragioni (in condizioni di troncamento il resto raggiungera' un minimo e poi continuera' a crescere anche se lentamente), ma il problema e' che:
- ha aggiunto un requisito mai richiesto (ha "sognato", come dicono gli espertoni di AI), cioe' 10000 iterazioni.
- ha usato una tecnica che genera un effetto caotico con due attrattori.
Si poteva fare di meglio? Si, si poteva usando una produttoria. Potremmo discutere anche del suo concetto di serie di Mc Lauren, a dire il vero, visto che a me il codice ricorda piu' Gregory–Leibniz.
Ma il punto e' che se sapete cosa state facendo, sapete come maneggiare gli errori che fa. Se non lo sapete, un codice "che funziona" ve lo copiaincollate, e se quello che stavate facendo era farvi un pigreco in casa, beh, avete ottenuto un requisito gratuito (che potreste non notare) e degli effetti collaterali che nel calcolo numerico sono abbastanza sgraditi.
E' vero che la media dei programmatori non sa riconoscere i due attrattori , come ho fatto io, perche' non ha mai fatto HPC. Quindi non sa riconoscere nemmeno di aver creato un effetto caos, e nemmeno sa che e' il troncamento a causarlo.
Ma il punto e' che se copiaincollate del codice con effetti collaterali sconosciuti e requisiti caduti dal cielo, potreste scrivere cose MOLTO peggiori di questa.
Ed e' per questo, cari signori, che:
- Non credo che ChatGPT mi togliera' lavoro, ne' lo togliera' ai programmatori bravi.
- ChatGPT, essendo un modello di linguaggio, NON PUO' evitare questi errori.
- Quindi, non puo' nemmeno riprogrammare se' stesso o "migliorarsi". Non sa fare.
Esistono molti motivi per i quali un modello di linguaggio naturale si trovera' a disagio nel programmare o nelle logiche molto formali.
Ma il punto e' molto semplice: potete dormire sonni molto tranquilli.
A perdere il lavoro saranno altri.
Ultimo appunto:

Come potete vedere, conosce una sola serie per calcolare pigreco che lui identifica sia con Mc Lauren sia con Taylor. Il guaio viene se gli faccio questa domanda:

Il codice e' questo:
package main
import (
"fmt"
)
func main() {
// Numero di termini da utilizzare nella serie
n := 10000
// Calcola il valore di π utilizzando la serie approssimante
pi := 0.0
for k := 0; k < n; k++ {
pi += 1.0/(float64(k)*8.0+1.0) - 1.0/(float64(k)*8.0+4.0) - 1.0/(float64(k)*8.0+5.0) - 1.0/(float64(k)*8.0+6.0)
}
pi *= 4.0
// Stampa il valore di π calcolato
fmt.Println("Valore di π calcolato:", pi)
}
Che all'inizio mi ha incuriosito perche' non la conoscevo (vedo che la formula e' del 1995) , e che sarebbe anche interessante.
Se solo quel codice calcolasse pigreco, intendo:
Valore di π calcolato: -2.6657193990759604
Perche' ,a quanto pare, la formula e' diversa dal codice:
oppure
oppure
Adesso scusate, vado a dormire sonni tranquilli.