Jos tekee vähän kirjoitusvirheitä numeroita kirjoittaessa ei kannata tarkistaa viitenumeroa. Jos tekee paljon niin kannattaa tarkistaa pitkät.
Pedanttina minua on pitkään harmittanut laskujen viitenumeroiden tarkistusnumero järjestelmä. Ei niinkään sen määräytyminen kuin se ettei samassa yhteydessä kerrota asiaan liittyvistä riskeistä. Tarkistusnumeroita on nimittäin vain kymmenen joten satunnaisessa viitteessä on kymmenen prosentin mahdollisuus saada oikea numero. Siis jos arpoo kaikki viitenumeron numerot niin kerran kymmenestä se menee tarkastuksesta läpi. Homma tietysti perustuu siihen, että ihmiset yrittävät kirjoittaa viitenumeron oikein ja yksittäiset virheet jäävät kiinni varmasti.
Itse viitenumeron muodostaminen on yksinkertaista, kuten esim. täältä näkee. Viitenumeron numerot kerrotaan yksitellen numeroilla 7, 3, 1, 7, 3, 1,… Tulot summataan ja vähennetään tulos seuraavasta täydestä kymmenestä (10 => 0). Operaation seurauksena tarkistusnumerot jakautuvat tasaisesti:
Ensimmäinen: 0
Viimeinen: 9
[1 1 1 1 1 1 1 1 1 1]
Ensimmäinen: 10
Viimeinen: 99
[9 9 9 9 9 9 9 9 9 9]
Ensimmäinen: 100
Viimeinen: 999
[90 90 90 90 90 90 90 90 90 90]
Ensimmäinen: 1000
Viimeinen: 9999
[900 900 900 900 900 900 900 900 900 900]
Ensimmäinen: 10000
Viimeinen: 99999
[9000 9000 9000 9000 9000 9000 9000 9000 9000 9000]
Ensimmäinen: 100000
Viimeinen: 999999
[90000 90000 90000 90000 90000 90000 90000 90000 90000 90000]
Ensimmäinen: 1000000
Viimeinen: 9999999
[900000 900000 900000 900000 900000 900000 900000 900000 900000 900000]
Yllä laitoin koneen laskemaan tarkastussumman ensimmäiselle 1e7 luvulle ja pistin ylös mikä tarkistusnumero oli tuloksena.
Viitenumeron maksimi pituus on 19+1. Jos kirjoitusvirheen todennäköisyys on yhtä suuri jokaisen 19 merkin kohdalla, kasvaa todennäköisyys tarkistus systeemin pettämisestä viitenumeron pituuden mukana. Käyttäen itseäni koe-eläimenä arvoin 200 viiden numeron mittaista lukusarjaa ja kirjoitin numerot koneella kuten yleensä laskuja maksaessa.
Kuva 0. Näkymä näppäilyvirheiden testaus ohjelmasta. “xx” rivit siirtävät luettavan ja kirjoitettavan niin kauas etten pystynyt niitä samanaikaisesti näkemään.
Jos tuntui että tein virheen korjasin, tarvittaessa katsomalla kirjoitettua numeroa. Muuten kirjoitin luvut katsomatta tulosta. Lopputuloksena olin jotakuinkin varma turhasta testistä sillä en uskonut tehneeni yhtään virhettä. Tein kuitenkin kahdeksan virhettä.
Jos oletetaan etten kuitenkaan kämmännyt kuin kerran numeroa kohti niin virheen todennäköisyys on vähintään Pv=0.008 yhden kirjoitetun numeron kohdalla. Käytin numeronäppistä ja virheet muilla menetelmillä voivat olla toisia, mutta tämä antanee ihan kohtuullisen arvion suuruusluokasta. Hyvin lyhyellä googlaamisella en löytänyt mitään hyvää lähdettä joten käytän myöhemmin tätä itse mittaamaani.
Virheiden todennäköisyys ei välttämättä ole täysin riippumaton aiemmin tehdyistä virheistä, esimerkiksi sellainen tilanne jossa kaksi virhettä syntyy peräkkäin merkkien vaihtaessa paikkaa voi olla kohtuullisen todennäköinen.
Yhden virheen tilanteessa virhe tulee aina havaituksi, mutta useamman virheen tilanteessa läpi menee noin kymmenen prosenttia virheellisistä viitenumeroista, kuva alla. Jostain syystä kahden virheen tilanteissa skriptin laskema läpimenoprosentti on jopa vähän korkeampi. Skriptin virheellisyyden todennäköisyys ei ole olematon. Usean virheen tapauksessa tulos vaikuttaa järkevältä: satunnaiselle luku sarjalle arvottu tarkistusnumero on 0.1 todennäköisyydellä se oikea.
Kuva 1. Virhettä sataa arvonta kertaa kohti eri mittaisille viitenumeroille. Nollaan menevät viivat liittyvät viitenumeron pituuteen: neljä merkkiä pitkässä viitteessä ei voi olla viittä virhettä.
Tässä kohtaa kannattaa ripotella vähän suolaa, sillä todennäköisyyslaskenta ei ole vahvimpia puoliani. Yksinkertaistettuna todennäköisyys viitenumero jossa on enemmän kuin yksi virhe on yksi miinus ne tapaukset joissa virheitä ei ole ja ne joissa virheitä on vain yksi,
Pk=1-((1-Pv)n+n*(1-Pv)n-1*Pv) (1)
missä Pv on todennäköisyys kirjoittaa merkki väärin, 1-Pv on todennäköisyys kirjoittaa se oikein, (1-Pv)n on todennäköisyys kirjoittaa n merkkiä oikein, (1-Pv)n-1*Pv on todennäköisyys kirjoitta n numeroa pitkässä viitenumerossa yksi merkki väärin, n kertaa edellinen ottaa huomioon mahdollisuuden kämmätä kerran jokaisen merkin kohdalla.
Yksinkertaistuksia ovat mm. edellä mainittu virheiden toisistaan riippumattomuus ja tarkastusnumeron virheettömyys sekä oletus että viitteeseen voi kirjoittaa useita virheitä sitä huomaamatta.
Kuva 2. Todennäköisyys tehdä enemmän kuin yksi virhe viitenumeron pituuden mukaan, tehtäessä 8 virhettä tuhannessa.
Tarkistin tuloksen myös laskemalla skriptillä saman todennäköisyyden kuin kaavalla 1. Miljoonalla toistolla per viitenumeron pituus syntyi hyvä vastaavuus kuten kuvasta näkyy.
Matemaattisen ja tietoteknisen lähestymisen meriittejä pohtineille kerrottakoon kaavalla laskemiseen meneen noin 133 ms kun pitkällä skriptillä meni 864.17 ms, mikä on kuulemma suurin piirtein sama aika kuin se jonka vaimo on menettänyt elämästään tämän blogin kirjoittamiseen liittyvän ilakoinnin aiheuttaman mielipahan vuoksi.
Ihmisten kyky kirjoittaa oikein vaihtelee, joten jos oletetaan minut keskiverto oikein kirjoittajaksi numeroiden osalta (!) niin jotkut tekevät enemmän virheitä jolloin todennäköisyys useampi virheiseen viitenumeroon kasvaa. Kun 19 merkkiselle viitenumerolle Pv=0.008 tasolla noin yksi sadasta sisältää enemmän kuin yhden virheen niin Pv=0.015 tasolla niitä on jo enemmän kuin kolme sadasta.
Kuva 3. Kuten kuva 2, mutta kirjoitusvirheen todennäköisyys Pv=0.015
Koska monivirheisistä viitenumeroista kuitenkin vain yksi kymmenestä menee tarkastuksessa läpi voidaan sanoa että minulla ja ehkä keskiverto näppäilijällä yksi pitkä viitenumero tuhannesta menee läpi. Siis, jos oletetaan kirjoittajan korjaavan kehotuksen jälkeen virheet täydellisesti.
Jos laskun maksaja ansaitsee 15 € tunnissa (netto) ja käyttää 19 numeroisen viitteen tarkastamiseen 10 sekuntia niin tuhannen viitteen tarkastaminen maksaa noin 40 €. Jos homman selvittäminen läpi menneen virheen jälkeen maksaa 50 € ajassa ja viivästysmaksussa karhukirjeen johdosta niin näyttäisi olevan melkein sama kumman tekee. Jos epäilee Pv:nsä olevan isompi niin tarkastaminen alkaa nopeasti kannattamaan.
Käytetyt skriptit:
[sourcecode language=”python”]
import ViiteVirhe as VV
import numpy as np
import matplotlib.pyplot as plt
import pylab as P
import random
# testaa pitkien viitenumeroiden tarkastussumman jakaumaa
a=VV.TarkNumJak(19,10000000)
hist, bins=np.histogram(a)
width = 0.7 * (bins[1] – bins[0])
center = (bins[:-1] + bins[1:]) / 2
fig=plt.figure()
ax=fig.add_subplot(1,1,1)
k=np.mean(hist)
ax.bar(center, (hist-k)/1e6, align=’center’, width=width)
ax.set_title(‘Viitenumeron tarkastusnumeron (jakauma-minimi)/1e6, 10 M 19 numeroista’)
plt.show()
# Käy läpi lyhyempiä järjestyksessä
for i in range(7):
k=10**i
if k==1: k=0
print(‘Ensimmäinen: ‘ +str(k))
print(‘Viimeinen: ‘ + str(int(‘9’*(i+1))))
a=VV.TarkNumJak2(k,int(‘9’*(i+1))+1)
hist, bins=np.histogram(a)
print(hist)
r=random.SystemRandom()
S=”
oikein=0
vaarin=0
for i in range(200):
for k in range(5):
S=S+str(r.randint(0,9))
print(S)
for k in range(20):
print(‘xx’)
s=input(‘>>>> ‘)
if s==S:
oikein+=1
else:
vaarin+=1
S=”
print(‘Oikein ‘ + str(oikein))
print(‘Vaarin ‘ + str(vaarin))
# Läpi menevät, tarkistus summa aina oikein
import scipy as sc
TT=sc.zeros((19,19))
for i in range(1,20):
print(i)
for k in range(1,i+1):
for l in range(1000):
TT[i-1,k-1]+=VV.nErrorsCSC(i-1,k)
print(TT/1000)
fig=plt.figure()
ax=fig.add_subplot(1,1,1)
ax.plot([i+1 for i in range(19)],TT.T/1000) #[i+1 for i in range(19)],
ax.set_title(”)
plt.show()
# Virheellisen viitteen läpimenon todennäköisyys
import time
Pv=0.015
start_time=time.time()
M=np.zeros(18)
for i in range(2,20):
M[i-2]=VV.POfWrong(i,Pv)
print(‘Numeroita: ‘ + str(i)+ ‘ Todennäköisyys n virheitä >=2 : ‘ +
str(M[i-2]))
print(M)
print(‘Aikaa meni: ‘ + str(time.time()-start_time))
start_time=time.time()
MM=np.zeros(18)
N=1000000
for i in range(2,20):
MM[i-2]=VV.POfWrong2(i, Pv, N)
print(i)
print(MM)
print(‘Aikaa meni: ‘ + str(time.time()-start_time))
fig=plt.figure()
ax=fig.add_subplot(1,1,1)
ax.plot([i for i in range(2,20)],MM/N, ‘b+-‘,
label=’skripti’, markersize=10) #[i+1 for i in range(19)],
ax.plot([i for i in range(2,20)],M, ‘r-‘, label=’kaava (1)’) #[i+1 for i in range(19)],
legend = ax.legend(loc=’center’, shadow=True)
frame = legend.get_frame()
frame.set_facecolor(‘0.90’)
for label in legend.get_texts():
label.set_fontsize(‘large’)
for label in legend.get_lines():
label.set_linewidth(1.5)
ax.set_title(‘Ennemmän kuin yksi virhe viitenumeron rungossa (Pv=’ +
str(Pv)+ ‘)’)
ax.set_xlabel(‘Viitenumeron pituus’)
ax.set_ylabel(‘Todennäköisyys’)
ax.set_ylim(0.0001,.1)
ax.set_xlim(0,21)
ax.set_yscale(‘log’)
ax.grid(axis=’both’, which=’both’)
plt.show()
[/sourcecode]
Ja kutsutut funktiot:
[sourcecode language=”python”]
""" Arpoo viitenumeroita, laskee tarkistussumman ja tilastoi
virheiden korjattavuutta
"""
def TarkNumJak(pituus,kertoja):
import scipy as sc
import math as mt
import random
r=random.SystemRandom()
kerroin=sc.array([7,3,1,7,3,1,7,3,1,7,3,1,7,3,1,7,3,1,7,3,1,7,3,1])
TarkNum=sc.zeros(kertoja)
for k in range(kertoja):
TarkSum=0
## Viite=sc.zeros((19,1))
for i in range(pituus):
apu=r.randint(0,9)
## Viite[i]=apu
TarkSum+=apu*kerroin[i]
if mt.ceil(TarkSum/10)*10==TarkSum:
TarkSum+=10
TarkNum[k]=mt.ceil(TarkSum/10)*10-TarkSum
if TarkNum[k]==10:
TarkNum[k]=0
return TarkNum
def TarkNumJak2(Smallest,Largest):
""" Largest-1 is the largest number considered
"""
import scipy as sc
import math as mt
TarkSum=0
kerroin=sc.array([7,3,1,7,3,1,7,3,1,7,3,1,7,3,1,7,3,1,7,3,1,7,3,1])
TarkNum=sc.zeros((Largest,1))
for i in range(Smallest,Largest):
TarkSum=0
I=str(i)
for k in range(len(I)):
TarkSum+=int(I[k])*kerroin[k]
if mt.ceil(TarkSum/10)*10==TarkSum:
TarkSum+=10
TarkNum[i]=mt.ceil(TarkSum/10)*10-TarkSum
if TarkNum[i]==10:
TarkNum[i]=0
## print(TarkNum)
return TarkNum[Smallest:]
def nErrorsCSC(pituus, virheita):
""" n errors Check sum Always correct"""
import scipy as sc
import random
import math as mt
r=random.SystemRandom()
S=”
Se=”
V=[None]*(pituus+1)
for i in range(pituus+1):
V[i]=i
random.shuffle(V)
for k in range(pituus+1):
apu=r.randint(0,9)
S=S+str(apu)
Se=list(S)
if virheita>pituus+1:
virheita=pituus
for k in range(virheita):
apu2=r.randint(0,9)
while int(Se[V[k]])==apu2:
apu2=r.randint(0,9)
Se[V[k]]=str(apu2)
Se="".join(Se)
TarkSum=0
TarkSume=0
TarkNum=0
TarkNume=0
kerroin=sc.array([7,3,1,7,3,1,7,3,1,7,3,1,7,3,1,7,3,1,7,3,1,7,3,1])
for k in range(pituus+1):
TarkSum+=int(S[k])*kerroin[k]
TarkSume+=int(Se[k])*kerroin[k]
if mt.ceil(TarkSum/10)*10==TarkSum:
TarkSum+=10
TarkNum=mt.ceil(TarkSum/10)*10-TarkSum
if TarkNum==10:
TarkNum=0
if mt.ceil(TarkSume/10)*10==TarkSume:
TarkSume+=10
TarkNume=mt.ceil(TarkSume/10)*10-TarkSume
if TarkNume==10:
TarkNume=0
if TarkNum==TarkNume:
return 1
else:
return 0
def POfWrong(n, Pv):
a=1-((1-Pv)**n+n*(1-Pv)**(n-1)*Pv)
return(a)
def POfWrong2(n, Pv, N):
import random
r=random.SystemRandom()
v=0
for i in range(N):
V=0
for k in range(n):
if r.random()<=Pv:
V+=1
if V>1:
v+=1
return(v)
[/sourcecode]