Olen tässä VB6:lla vääntänyt eilisestä alkaen massiivisen laista laskinsoftaa jossa kuitenkin tiettyjen suuruisten numeroiden kanssa Long'it heittävät overflow'ia. VB6:sessa ei ole keinoa saada muuttujien tila riittämään suurille luvuille kuten 50 000 000 ? Jollei, on varmaan pakko kirjoittaa muutama rivi joka jatkaa softan kulkua vaikka muuttujan tila ylittyyki.
Kyllähän tuo 50 000 000 mahtuu VB6:n Longiin. Tuleeko se overflow laskun välivaiheesta? Jos kyllä niin ratkaisu voisi olla että laskee Doublella ja lopputulos -> Long.
vesimies kirjoitti:
... Doublella ...
Ah, kiitos kun muistutit Doublesta, nyt ei pitäisi heti tulla overflow'ia. Laitoin 12 kertaa numero 9 eikä yhdessäkään kohdassa tullut mitään valitusta.
Varoitus: Double on 64-bittinen liukuluku, joka pyöristää lukua siinä vaiheessa kun bitit loppuvat kesken. Käytännössä liukulukujen käyttö laskimessa on huono juttu, koska tulokset eivät ole tarkkoja.
Currency on ensialkuun parempi vaihtoehto, se on 64-bittinen kokonaisluku, jonka desimaalin paikka on kuitenkin kiinteästi siirretty eri kohtaan. Currency voi kuitenkin omien rajojensa sisällä muistaa täydellisesti annetut luvut, liian pienet luvut pyöristetään neljään desimaalilukuun ja liian suurista luvuista tulee virhe.
Vielä parempi vaihtoehto on käyttää vain Variantissa olevaa Decimalia. Se antaa muistaakseeni 192-bittisen tarkkuuden kokonaisluvuille. Desimaalin paikkaa voi tosin vaihtaa, joten se on liukuluku: liian isoista luvuista ei synny virhettä, luvun alapäässä vaan alkaa tapahtua pyöristämistä.
Simppelisti sanottuna VB6 ei sisällä muuttujaa luvuille joka olisi erittäin iso eikä aiheuttaisi lukujen muutoksia.
String-muuttujaan (merkkijonoon) voi tallentaa todella suuria lukuja. Tällöin täytyy kuitenkin toteuttaa itse lukujen laskutoimitukset.
Ottaen huomioon kuinka rasittavaa stringien käsittely VB6:ssa on, käyttäisin ennemmin vaikkapa byte-taulukkoa kuin Stringiä.
Useissa ohjelmointikielissä ei ole vakiona mahdollisuus käyttää kovin tarkkoja lukuja, ja useimmille kielille löytyy ko. rajoitteen kiertämiseen valmiita "big number" kirjastoja. Eli jos ei jaksa itse toteuttaa lasekmista alusta alkaen, niin kannattaa tutkailla valmiita vaihtoehtoja.
Moikka rautamiekka!
Microsoft Script Control 1.0 (msscript.ocx), joka on vapaasti impattavissa netistä mahdollistaa laskutoimitukset suoraan merkkijonosta.
oheisella kaavalla pääsee ainakin 15-numeroisiin lukuihin...
Private sc As MSScriptControl.ScriptControl
Private strToCompute As String
Private IsComputing As Boolean
Private Sub cmd1_Click()
Text1.Text = Text1.Text + cmd1.Caption
strToCompute = strToCompute + cmd1.Tag
End Sub
'jne...
Private Sub cmdPlus_Click()
If Text1.Text <> "" Then
strToCompute = strToCompute + " + "
Text1.Text = ""
End If
End Sub
'jne
Private Sub cmdClear_Click()
Text1.Text = ""
strToCompute = ""
End Sub
Private Sub cmdCompute_Click()
If strToCompute <> "" Then
Select Case Right(strToCalculte, 1)
Case "+", "-", "*", "/"
Exit Sub
Case Chr$(48) To Chr$(57)
IsComputing = True
strCode = "Sub Main()" + vbCrLf + _
"Text1.Text = " + strToCompute + _
vbCrLf + "End Sub"
Set sc = New MSScriptControl.ScriptControl
With sc
.Language = "VBScript"
.AddObject "Form1", Me, True
.AllowUI = True
.AddCode strCode
.Run "Main"
End With
Set sc = Nothing
strToCompute = ""
End Select
End If
End Sub
Private Sub Text1_Change()
If Text1.Text <> "" Then
Text1.SelLength = Len(Text1.Text)
Text1.SelStart = Len(Text1.Text)
End If
If IsComputing Then
Text1.Text = Str(Val(Text1.Text))
strToCompute = Text1.Text
IsComputing = False
End If
End SubDecimal antaa 28. Ylitse menevät numerot antavat kuin antavatkin virheen, eli yksi ysi lisää ja virhettä pukkaa. Siinä mielessä turvallisempi kuin liukuluvut, ainakin saa tietää että yli mentiin.
Option Explicit
Private Sub Command1_Click()
Dim varNum As Variant, strNum As String
strNum = "9999999999999999999999999999"
varNum = CDec(strNum)
Debug.Print strNum
Debug.Print varNum
End SubPitää muistaa ettei monet VB:n omat matemaattiset operaattorit toimi Decimalin kanssa, varmaankin juuri siksi se on saatavilla vain Variantin kautta. CDec pitää heittää aikalailla joka väliin, että varmistaa oikean muuttujatyypin.
Toistaiseksi tekemäni pieni softa on saavuttanut miljardin rajan eikä Long ole vieläkään heittänyt herjaa. Lopetin jo ajamasta softaa kun en saanut sitä laittamaan pilkkua joka kolmannen numeron jälkeen.
Mitä tulee Double'en, laitan softaani varoituksen sen käytöstä ja mahdollisesta tulosten manipuloinnista jos numero sattuu menemään liian suureksi, vai luuletteko että (maineen) kannalta olisi parempi laittaa Long ja käskeä softa herjaamaan kun mennään sen rajan oli ?
Kyllä minusta enemmän mainetta saa Doublella (15 merkitsevää numeroa ja laaja lukualue) kuin Longilla (kahden miljardin lukualue pelkillä kokonaisluvuilla nollan molemmin puolin). Riippuu toki, onko ohjelma tavallinen laskin vai onko tarkoitus tehdä jotain hienompaa (esim. tarkkoja murtolukulaskuja, joissa kannattaakin käyttää kokonaislukuja, koska ne ovat aina tarkkoja).
Katsoin huvikseni missä kohtaa Doublen tarkkuus tarkkaan ottaen loppuu kokonaisluvuille. Tämä on nyt .Net-koodia mutta sama Double se sielläkin on.
Imports System.Math
Public Module TestiModuuli
Public Sub DoubleTesti()
Dim a, b, c As Double ' aina 0 < a <= c <= b
a = 10000000000.0 ' = 1.0E+10
b = 1.0E+30
Dim lisätty As Double = a + 1.0
Dim ero As Double = lisätty - a ' -> 1.0
lisätty = b + 1.0
ero = lisätty - b ' -> 0.0
Const tarkk As Double = 5.0
' tämän on parempi olla jonkin verran isompi kuin tutkittava lisäys
Dim laskuri As Integer = 0
Do Until b - a < tarkk
laskuri += 1
c = Sqrt(a * b) ' "geometrinen keskiarvo"
' Katsotaan muuttaako ykkösen lisääminen lukua:
If c + 1.0 <> c Then
' Muutti, etsitään isommista luvuista:
a = c
Else
' Ei muuttanut, etsitään pienemmistä luvuista:
b = c
End If
Loop
Debug.Print("Suurin kokonaislukuna toimiva Double = noin " & Format(c, StrDup(15, "0"c)))
Debug.Print("Ja looppeja tehtiin " & CStr(laskuri) & " kpl.")
End Sub
End ModuleSuurin kokonaislukuna toimiva Double = noin 9617292297544020
Ja looppeja tehtiin 57 kpl.
Se oli siis 5:n tarkkuudella.
Sivumennen mainittakoon että .Netissä on 64-bittinen kokonaislukumuuttuja nimeltään Long, jota tosin itse olen tarvinnut vain DateTime-hommissa, missä aika voidaan esittää Ticks-muodossa. 1 Tick = 10^-7 s = 100 ns joten tuhansiin vuosiin niitä mahtuu melko paljon.
Jokin tuossa mun virityksessä on pielessä. Antaa liian suuren arvon, koska
Dim luku As Double luku = 9617292297544005 ' -> 9617292297544004.0 luku = 9617292297544006 ' -> 9617292297544006.0 luku = 9617292297544007 ' -> 9617292297544008.0 luku = 9617292297544008 ' -> 9617292297544008.0
Täytynee tutkia siten että onko (c + 1.0) - c = 1.0. Näin tulee tulokseksi
9007199254740990
Näyttää lupaavammalta:
Dim luku As Double luku = 9007199254740980 ' -> 9.00719925474098E+15 luku = 9007199254740981 ' -> 9007199254740981.0 luku = 9007199254740982 ' -> 9007199254740982.0 luku = 9007199254740983 ' -> 9007199254740983.0 luku = 9007199254740984 ' -> 9007199254740984.0 luku = 9007199254740985 ' -> 9007199254740985.0 luku = 9007199254740986 ' -> 9007199254740986.0 luku = 9007199254740987 ' -> 9007199254740987.0 luku = 9007199254740988 ' -> 9007199254740988.0 luku = 9007199254740989 ' -> 9007199254740989.0 luku = 9007199254740990 ' -> 9.00719925474099E+15
Metabolix kirjoitti:
Kyllä minusta enemmän mainetta saa Doublella (15 merkitsevää numeroa ja laaja lukualue) kuin Longilla (kahden miljardin lukualue pelkillä kokonaisluvuilla nollan molemmin puolin). Riippuu toki, onko ohjelma tavallinen laskin vai onko tarkoitus tehdä jotain hienompaa (esim. tarkkoja murtolukulaskuja, joissa kannattaakin käyttää kokonaislukuja, koska ne ovat aina tarkkoja).
Tässä laskimessa lasketaan vain plus- ja kertolaskuja mutta käyttäjä saattaa piruuttaan antaa hulluja lukuja. Toisaalta rajoitin jokaisen tekstilootan 12:sta merkkiin.
-1.79769313486231E308 to -4.94065645841247E-324 for negative values
4.94065645841247E-324 to 1.79769313486232E308 for positive values
vesimies: et vielä iskenyt oikeaan kohtaan, tuo E+15 on vain erilainen tapa ilmaista luku. Nähdäksesi merkkijonona lopputuloksen oikein tarvitset jonkin funktion väliin, kuten vaikkapa FormatNumber. Alla vähän VB6-koodia, joka on säädetty ensimmäiseen kohtaan jossa Doublen tarkkuus vaikutti loppuvan kesken:
Option Explicit
Private Sub Form_Load()
Dim varLuku As Variant, dblLuku As Double
varLuku = CDec("999999999999999")
dblLuku = varLuku
Do Until varLuku <> dblLuku
varLuku = varLuku + 1
dblLuku = varLuku
Loop
MsgBox FormatNumber$(varLuku - 1) & vbNewLine & FormatNumber$(varLuku) & vbNewLine & FormatNumber$(dblLuku)
End SubLiukulukujen ongelma on kuitenkin vielä olemassa siinä, että matemaattisten operaatioiden seurauksena luvut eivät välttämättä anna samoja lopputuloksia kuin kokonaisluvuilla laskiessa. En muista tähän nyt laittaa mitään tiettyä toimivaa esimerkkiä, mutta muistan että jo jokin hyvinkin yksinkertainen kerto- tai jakolasku saattaa antaa yllättäen vaikkapa desimaalilukuja, vaikka lopputuloksena pitäisi olla kokonaisluku. Eli jos vastaus vaikka pitäisi olla 1337, niin Doublen arvo onkin 1337.0000000000000001 tms. – ja luonnollisesti tästä on joissakin tapauksissa haittaa.
Tarkoitin tuolla listalla vaan sitä että välillä 9007199254740980 - 9007199254740990 näyttäisi tulevan se luku mikä pitääkin (seurasin watch-ikkunasta rivi riviltä), toisin kuin 9617292297544000:n paikkeilla. En sitten tiedä voiko siihen luottaa, että myös kaikki 9007199254740980:a pienemmät luvut käyttäytyisivät kunnolla.
Laskin vielä yhdellä tavalla. Kaksoistarkkuuden liukuluvun mantissassa on 52 bittiä. Tarkoittaa kaiketi sitä, että suurin mantissan arvo on
2 ^ 53 - 1 (= n. 9.0E+15),
= 9007199254740991
ja olisi myös suurin luku jonka voi esittää 1:n tarkkuudella?
Eikös muuten desimaalivuodon korjaa aina vanha kunnon
Int(luku + 0.5)
Jos tarkoitus on käyttää vain kokonaislukuja, niin yksi vaihtoehto olisi currency
Arvoalueen kokonaisosa -922337203685477 - 922337203685477, ei pyöristysvirheitä.
Aihe on jo aika vanha, joten et voi enää vastata siihen.