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 Sub
Decimal 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 Sub
Pitää 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 Double
n 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 Module
Suurin 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 Sub
Liukulukujen 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.