Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: [VB6] Omassa laskinsoftassa overflow

Sivun loppuun

rautamiekka [08.04.2009 14:35:32]

#

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.

vesimies [08.04.2009 16:36:32]

#

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.

rautamiekka [08.04.2009 16:59:12]

#

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.

Merri [09.04.2009 09:57:48]

#

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ä.

rautamiekka [11.04.2009 02:10:49]

#

Simppelisti sanottuna VB6 ei sisällä muuttujaa luvuille joka olisi erittäin iso eikä aiheuttaisi lukujen muutoksia.

Antti Laaksonen [11.04.2009 22:04:35]

#

String-muuttujaan (merkkijonoon) voi tallentaa todella suuria lukuja. Tällöin täytyy kuitenkin toteuttaa itse lukujen laskutoimitukset.

Grez [11.04.2009 22:15:17]

#

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.

neau33 [12.04.2009 17:52:02]

#

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

Merri [12.04.2009 22:51:49]

#

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.

rautamiekka [14.04.2009 20:56:32]

#

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 ?

Metabolix [14.04.2009 21:02:54]

#

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).

vesimies [17.04.2009 18:15:51]

#

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 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.

vesimies [17.04.2009 21:06:45]

#

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

rautamiekka [18.04.2009 08:25:02]

#

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.

Merri [18.04.2009 09:12:11]

#

VB6 Data Type Summary

-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.

vesimies [18.04.2009 19:05:54]

#

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.

vesimies [19.04.2009 15:12:35]

#

Laskin vielä yhdellä tavalla. Kaksoistarkkuuden liukuluvun mantissassa on 52 bittiä. Tarkoittaa kaiketi sitä, että suurin mantissan arvo on

2 ^ 53 - 1
= 9007199254740991
(= n. 9.0E+15),

ja olisi myös suurin luku jonka voi esittää 1:n tarkkuudella?

Eikös muuten desimaalivuodon korjaa aina vanha kunnon

Int(luku + 0.5)

Grez [19.04.2009 15:32:22]

#

Jos tarkoitus on käyttää vain kokonaislukuja, niin yksi vaihtoehto olisi currency

Arvoalueen kokonaisosa -922337203685477 - 922337203685477, ei pyöristysvirheitä.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta