Formula di Luhn

Beniamino Sartini

2023-01-21

La Formula di Luhn

La formula di Luhn, anche conosciuta come Modulo 10, è un semplice algoritmo che consente di generare e verificare la validità di vari numeri identificativi. Venne ideata nel 1954 dall’informatico dell’IBM Hans Peter Luhn e brevettata nel 1960. Ora di pubblico dominio ha molteplici applicazioni, ad esempio per i numeri delle carte di credito. Per esempio ogni carta di credito ha un suo numero di carta (es. 0000-1234-5678-9123) dove la prima parte identifica il circuito internazionale (Visa, American Express, Mastercard…) mentre il resto la banca emittente ed il cliente.

Descrizione dell’algoritmo per il calcolo della cifra di Luhn

La cifra di controllo di tipo Luhn viene calcolata in modo semplice. Si sommano tutte le cifre in posizione pari al doppio della somma di quelle in posizione dispari. Si considera quindi il modulo rispetto a 10 (ovvero il resto della divisione per 10) del valore così ottenuto; si determina quindi la cifra di Luhn come segue:

  • se il modulo è 0 (la somma è divisibile per 10) la cifra di controllo sarà 0. Ad esempio se la somma = 60 che diviso per 10 dà resto 0, la cifra di Luhn sarà 0
  • altrimenti la cifra di Luhn sarà la differenza tra 10 ed il modulo. Ad esempio se la somma = 61 che diviso per 10 dà resto 1, la cifra di Luhn sarà 9 (10-1)

Verifica della validità

Il controllo di un numero contenente la cifra di Luhn si basa su tre passi:

  1. Partendo da destra e spostandosi verso sinistra, moltiplicare per 2 ogni cifra posta in posizione pari
  2. Laddove la moltiplicazione ha dato un risultato a due cifre, sommare le due cifre per ottenerne una sola (es. 18 = 1+8)
  3. Sommare tutte le cifre, sia quelle che si trovano in posizione pari, sia quelle che si trovano in posizione dispari

e la somma complessiva è divisibile per 10 (la divisione non ha resto) la carta è valida.

Ad esempio, supponendo di avere il seguente numero di carta: 4716-4359-1733-0099 (quindi 9900367291386278)

  1. 9+9+0+0+3+6+7+2+9+1+3+8+6+2+7+8=80
  2. 80/10 = 8 = risultato intero → carta valida

La formula di Luhn viene utilizzata in Canada dal Social Insurance Number per l’identificazione dei suoi clienti; tuttavia con la seguente formula non verifica ulteriori informazioni, come il numero delle cifre e la validità della data di scadenza. Del resto la formula è stata studiata per rilevare errori di digitazione, non è adatta a rilevare falsificazioni volontarie.

# verifica se il numero in input è valido secondo il criterio di Luhn.
# Input: "x" è una stringa numerica 
# Output: un vettore contenente: 
# - come primo elemento: 1 se il numero è valido altrimenti 0.
# - come secondo elemento: la cifra di luhn. 

verificaNumeroLuhn <- function(x, quiet = FALSE){
  
  # Controllo: solo character 
  if(!inherits(x, "character")) {
    stop('x must be a character.')
  }
  
  # Tranformo in vettore numerico
  y <- strsplit(x, "")[[1]]
  y <- as.integer(y)
  
  # Inverto l'ordine 
  y_reverse <- y[c(seq(length(y),1,-1))]
  
  # Ciclo per il controllo
  for(i in 1:length(y_reverse)) {
    
    if(i %% 2 == 0){
      # indice pari
      y_reverse[i] <- 2*y_reverse[i]
    
      # somma maggiore di 10
      if(y_reverse[i] >= 10){
        y_split <- as.character(y_reverse[i])
        y_split <- strsplit(y_split, "")[[1]]
        y_split <- as.integer(y_split)
        y_reverse[i] <- sum(y_split)
      }
    } else {
      # indice dispari
      next
    }
  }
  
  y_sum <- sum(y_reverse) 
  
  cifra_luhn <- ifelse(y_sum %% 10 == 0, 0,  10 - y_sum %% 10 )
  
  # Controllo Finale Validita
  if(cifra_luhn == 0){
    if(!quiet) message("Numero Valido")
    return(c(validità = 1, cifra_luhn = cifra_luhn))
  } else {
    if(!quiet) message("Numero NON Valido")
    return(c(validità = 0, cifra_luhn = cifra_luhn))
  }
}
# verifica se il numero in input è valido secondo il criterio di Luhn.
# Input: "x" è una stringa numerica 
# Output: un vettore contenente: 
# - come primo elemento: 1 se il numero è valido altrimenti 0.
# - come secondo elemento: la cifra di luhn. 

verificaNumeroLuhn <- function(x, quiet = FALSE){
  
  # Controllo: solo character 
  if(!inherits(x, "character")) {
    stop('x must be a character.')
  }
  
  # Tranformo in vettore numerico
  y <- strsplit(x, "")[[1]]
  
  # aggiunge uno 0 se il numero di cifre è dispari
  if(length(y) %% 2 != 0){
    y[length(y)+1] <- "0"
  }
  
  y <- as.integer(y)
  
  # Inverto l'ordine 
  y_reverse <- y[c(seq(length(y),1,-1))]
  
  # Ciclo per il controllo
  for(i in 1:length(y_reverse)) {
    
    if(i %% 2 == 0){
      # le cifre in posizione pari vengono raddoppiate
      y_reverse[i] <- 2*y_reverse[i]
    
      # SE somma maggiore di 10
      if(y_reverse[i] >= 10){
        y_split <- as.character(y_reverse[i])
        y_split <- strsplit(y_split, "")[[1]]
        y_split <- as.integer(y_split)
        y_reverse[i] <- sum(y_split)
      }
    } else {
      # SE indice dispari 
      next
    }
  }
  # Somma totale
  y_sum <- sum(y_reverse) 
  
  # Calcolo cifra di Luhn
  cifra_luhn <- ifelse(y_sum %% 10 == 0, 0,  10 - y_sum %% 10 )
  
  # Controllo Finale Validita
  if(cifra_luhn == 0){
    if(!quiet) message("Numero Valido")
    return(c(validità = 1, cifra_luhn = cifra_luhn))
  } else {
    if(!quiet) message("Numero NON Valido")
    return(c(validità = 0, cifra_luhn = cifra_luhn))
  }
}

# Numero carta di esempio 
# x <- "4716435917330099"
x = "1131710376"

verificaNumeroLuhn(x, quiet = FALSE)
## Numero Valido
##   validità cifra_luhn 
##          1          0

Verifica di Partite IVA italiane

Per prima cosa dobbiamo capire la struttura di una partita IVA italiana: per esempio la consideriamo la partita iva 12345678901.

  • 1234567: le prime sette cifre rappresentano il numero di matricola del soggetto assegnato dal relativo ufficio provinciale, che si ottiene incrementando di una unità il numero assegnato al soggetto che lo precede.

  • le cifre dalla ottava alla decima indicano il codice dell’ufficio provinciale del fisco che ha rilasciato la matricola, generalmente corrispondente al codice ISTAT della provincia; l’undicesima cifra, infine, rappresenta un codice di controllo, introdotto al fine di verificare la correttezza delle prime dieci cifre.

L’algoritmo impiegato per calcolare la cifra di controllo è la formula di Luhn.

verificaPartitaIva <- function(x){
  
  # Controllo: solo stringhe
  if(!inherits(x, "character") ) {
    stop('x must be a character containing a "partita iva".')
  }
  
  controllo_luhn <- verificaNumeroLuhn(x, quiet = TRUE)
  
  y = strsplit(x, "")[[1]]
  
  partita_iva <- dplyr::tibble(
    pIva = x, 
    Matricola = paste0(y[1:7], collapse = ""),
    Geo = paste0(y[8:10], collapse = ""),
    numeroLuhn = y[11],
    isValid = controllo_luhn[1] == 1,
  )
  
  partita_iva
  
}

Ricerca dell’Ufficio di Emissione

Come seconda cosa possiamo salvare i codici identificativi per ciascuna provincia italiana dal sito dell’ Agenzia delle Entrate ed utilizzarli per identificare il codice geografico di una partita Iva.

library(rvest)
library(stringr)
library(dplyr)

codice_provincia <- function(geo = NULL) {
  
  url <- "https://www1.agenziaentrate.gov.it/documentazione/versamenti/codici/ricerca/VisualizzaTabella.php?settore=V&ArcName=UFFICI"
  
  html <- rvest::read_html(url)
  
  dataset <- rvest::html_nodes(html, css = "table") 
  dataset <- rvest::html_table(dataset)[[1]]
  
  dataset <- dplyr::mutate(dataset, 
                            Codice = as.character(Codice),
                            Codice = dplyr::case_when(
                              stringr::str_length(Codice) == 1 ~ paste0("00", Codice),
                              stringr::str_length(Codice) == 2 ~ paste0("0", Codice),
                              stringr::str_length(Codice) == 3 ~ Codice
                              )
                           )
                                    
  dataset <- dplyr::select(dataset, Codice, Provincia = "Descrizione") 
  # La provincia di bologna è mancante nel sito dell'Agenzia
  dataset <- dplyr::bind_rows(dataset, 
                              dplyr::tibble(Codice = "037", 
                                           Provincia = "BOLOGNA")
                              )
  
  if(is.null(geo)){
    dataset
  } else {
    dplyr::filter(dataset, Codice == geo)$Provincia
  }
}

codice_provincia()
Codice Provincia
084 AGRIGENTO
006 ALESSANDRIA
042 ANCONA
007 AOSTA
051 AREZZO
044 ASCOLI PICENO
005 ASTI
064 AVELLINO
072 BARI
025 BELLUNO
062 BENEVENTO
016 BERGAMO
021 BOLZANO
017 BRESCIA I
098 BRESCIA II
074 BRINDISI
092 CAGLIARI
085 CALTANISSETTA
070 CAMPOBASSO
061 CASERTA
087 CATANIA
079 CATANZARO
069 CHIETI
013 COMO
078 COSENZA
019 CREMONA
004 CUNEO
086 ENNA
038 FERRARA
048 FIRENZE I
097 FIRENZE II
071 FOGGIA
040 FORLI’
060 FROSINONE
010 GENOVA I
099 GENOVA II
031 GORIZIA
053 GROSSETO
008 IMPERIA
094 ISERNIA
011 LA SPEZIA
059 LATINA
075 LECCE
049 LIVORNO
046 LUCCA
043 MACERATA
020 MANTOVA
045 MASSA CARRARA
077 MATERA
083 MESSINA
015 MILANO I
096 MILANO II
036 MODENA
063 NAPOLI I
121 NAPOLI II
003 NOVARA
091 NUORO
028 PADOVA
082 PALERMO
034 PARMA
018 PAVIA
054 PERUGIA
041 PESARO
068 PESCARA
033 PIACENZA
050 PISA
047 PISTOIA
093 PORDENONE
076 POTENZA
088 RAGUSA
039 RAVENNA
080 REGGIO CALABRIA
035 REGGIO EMILIA
058 ROMA I
100 ROMA II
029 ROVIGO
065 SALERNO
090 SASSARI
009 SAVONA
052 SIENA
089 SIRACUSA
014 SONDRIO
073 TARANTO
067 TERAMO
055 TERNI
001 TORINO
081 TRAPANI
022 TRENTO
026 TREVISO
032 TRIESTE
030 UDINE
012 VARESE
027 VENEZIA
002 VERCELLI
023 VERONA
024 VICENZA
056 VITERBO
037 BOLOGNA

Esempio 1

# Partita Iva Università di Bologna 
x <- "01131710376"

y <- verificaPartitaIva(x)

y
pIva Matricola Geo numeroLuhn isValid
01131710376 0113171 037 6 FALSE
x <- "0113171037"
verificaNumeroLuhn(x)
## Numero Valido
##   validità cifra_luhn 
##          1          0
codice_provincia(y$Geo)
## [1] "BOLOGNA"