Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: VB.NET: Pyöristämisen tuska .NET

Sivun loppuun

K_L [08.03.2007 13:40:01]

#

Terve,

Tästä oli vissiin juttua aiemminkin, mutta kokeillaan onnea taas.

Elikkä pyöristämisen tuska VB .NET

Minulla on tarve saada katkaistua luku haluamaltani kohdalta, siten ettei se pyöristy. Helppo homma otetaan esim.

25,9962148 halutaan muotoon 25,9
x =Math.Round(25.9962148, 1)
x = 26,0

No sitten
x = 25.9962148 * 10 = 259,962148
cint(x) = 260
x / 10 = 26

Voihan rähmä, no jos näin toimisi

x = Math.Round(25.9962148 - 0.5 / Math.Pow(10, 1))
x = 25,9
Uskomatonta se toimii

Hetkinen! entä jos arvo onkin 25,90

x = Math.Round(25.90 - 0.5 / Math.Pow(10, 1))
x = 25,8 WTF?

no jes enpäs muistanut, math.round käyttää sitä toista tapaa pyöristää...
siis 25,9 - 0,05 = 25,85 joka sitten pyöristyy alas.
25,9962148 - 0,05 = 25,9462148 eli pyöristys menee ok...

Mitä minun kannattaisi kokeilla seuraavaksi. Ehkä jotain mikä tunnistaa montako desimaalia on alkuarvossa? Jokin string hässäkkä? Muu hyvä idea?

Ideoita otetaan vastaan.

Antti Laaksonen [08.03.2007 14:35:03]

#

Tämän kaavan pitäisi toimia:

uusi = Fix(luku * 10) / 10

Funktio Fix palauttaa aina luvun kokonaisosan.

K_L [08.03.2007 15:00:16]

#

Kiitos... Se toimi moitteettomasti.
Sopiva nimi tälle funktiolle :D

koo [08.03.2007 21:29:30]

#

Ikävää olla ilonpilaaja, mutta tämä juttu toimii kunnolla vain, jos katkaistavat luvut ovat esmes Decimal-tyyppisiä tai hommaa muuten vain pyöritellään pelkästään kokonaisosilla.

Doublella tms. mikään desimaaliosajuttu ei voi teoriassakaan toimia, sillä kymmenjärjestelmän 0.1 ei mene tasan binäärijärjestelmässä. Ihmeellistä, ettei esmes Math.Round-dokumentaatiokaan sano tästä yhtään mitään. Voi olla, ettei pieni epätarkkuus haittaa muita, mutta minä en ainakaan laskisi rahasummilla noin.

K_L [09.03.2007 08:51:06]

#

Näin näyttäisi olevan. Onneksi tämä fix funktio pelitää kuten pitääkin. Nimittäin 0,1 virhe voisi aiheuttaa järkyttäviä kustannuksia. Ajoin tuolle funktiolle raja-arvo testin sille luku alueelle, mikä on määritelty mahdollisiksi arvoiksi. (0,000001 - 500,000000 0,000001 välein) Näyttäisi toimivan ok.

koo [09.03.2007 10:18:02]

#

Puhuukohan tässä toinen aidasta ja toinen aidan seipäistä? Millainen raja-arvotesti antaa tuloksen "näyttäisi toimivan"?

Jos tosiaan haluaa pyöristää tai katkaista luvun yhden desimaalin kohdalta, se ei toimi kunnolla mitenkään muuten kuin niin, että luku on sisäisesti jotakin Decimal-, Money- tms. kymmenjärjestelmäistä tyyppiä. Vaihtoehto on kertoa luku kymmenellä, laskeskella pelkillä kokonaisosilla ja vasta lopuksi esmes tulostuksessa hoitaa desimaalipilkku sen viimeisen digitin eteen.

Jos käyttää jotain Double-, Float- tms. binäärijärjestelmäistä liukulukutyyppiä, homma menee oikein korkeintaan sattumalta. Faktahan on se, että kymmenjärjestelmän luku 0.110 on binäärisenä jotain 0.00011001100...2 eli päättymätön pötkö bittejä.

Se mitä tässä yritetään tehdä, on vähän sama kuin kymppijärjestelmässä ajattelevalle ihmiselle sanottaisiin, että pyöristä tämä desimaaliluku lähimpään kolmasosaan ja laskeppa sitten parikin tuollaista lukua yhteen. 0.1-jutussa virhe on kyllä paaaaljon pienempi, mutta se kumminkin on siellä.

Rahalaskuissa kyse ei ole siitä, että noita laskeskelemalla syntyisi järkyttäviä kustannuksia. Kyse on siitä, että pennostenkin pitää vain mennä oikein ja jos pyöristyksiä tulee, ne kirjataan erikseen omalle tililleen kirjanpidossa. Esmes Nokian tilinpäätös on väärin, jos taseen vasemmassa ja oikeassa puolessa on yhdenkin sentin heitto. Ei taida tilintarkastus mennä läpi, vaikka kuinka selostaa, että tällä puolella summa vain näyttää pyöristyneen ylöspäin lähimpään pennoseen ja tällä puolella alaspäin, mutta ihan varmaan ne on niinku samat summat.

Vastaavasti fyssan laskuissa tuloksista tiedetään, että nämähän ovat oikein esmes kolmella merkitsevällä numerolla ja suuruusluokka on sitten erikseen. Binääriset liukuluvut sopii ihan hyvin tähän tarkoitukseen.

Varmaan tuo Fix-funktio pelaa ihan niin kuin pitääkin. Mutta pelaako jakolasku kymmenellä odotusten mukaan? No mitäpä näitä arvailemaan, vastaus riippuu aika paljon siitä, onko kyse laskennan välituloksista vai pelkästään jostakin tulostuksen muotoilusta.

feenix [09.03.2007 11:15:39]

#

K_L kirjoitti:

Näin näyttäisi olevan. Onneksi tämä fix funktio pelitää kuten pitääkin. Nimittäin 0,1 virhe voisi aiheuttaa järkyttäviä kustannuksia. Ajoin tuolle funktiolle raja-arvo testin sille luku alueelle, mikä on määritelty mahdollisiksi arvoiksi. (0,000001 - 500,000000 0,000001 välein) Näyttäisi toimivan ok.

Järkyttäviä kustannuksia? Et kai laske rahasummia tai muita floateilla? Niin ei saa ikinä tehdä.

K_L [09.03.2007 12:40:55]

#

No tässä tapauksessa ei ole kyse muotoilusta, vaan se on osa ehtolausetta.
Syytä miksi ehto tehdään noin en tiedä. Testissä joka tehtiin 0,000001 välein, ehto ei toiminut kertaakaan väärin. Se suoritettiin asiakkaan määrittelemällä mahdollisten arvojen välillä.

Kun katsoo välituloksia, niin ei ole kertaakaan ylimääräsiä desimaaleja tai katkaisu olisi pyöristynyt ylös tai alas.

Vähän järjetön testi. Logi tiedosto 8 gigaa :D

Täsähän ei lasketa varsinaisesti rahaa...

koo [09.03.2007 14:04:44]

#

Minä nyt vaan vieläkin nipotan, että homma toimii oikein vain kymmenjärjestelmän luvuilla tai jos luvut muuntaa niin, että voi pelata kokonaisosilla.

Jos kyse on ehtolauseesta, mikä tarkalleen ottaen on se otus, johon vertailu tehdään? Onko sekin jokin Double tms.? Eli oliko testin vertailumuuttujassa sisäänrakennettuna just se sama virhe kuin testattavassa arvossakin? Että toimii sitten moitteettomasti!

Enivei, yhden operaation tuloksena tuo pyöristys/katkaisuvirhe binäärijärjestelmässä on tosi pieni. Mutta kun tähän hommaan on todistettavasti oikea ratkaisumallikin, miksi pelata sillä jonka tietää luontaisesti vialliseksi? Onhan se tietty jännää odotella, että milloin virhe kertyy ja laukeaa käsiin.

Vähän rautakankimallia: Tee testi seuraavan pseudokoodin (tämä kun ei riipu kielestä) mukaan ja kerro meille, että toimiiko odotusten mukaan. Ja aina kun katsot jotain lukua, tulosta se esmes 16 desimaalilla. Paljon alle 8 gigaa:

Double a = 0.11
Double b = Fix(a*10)/10
Double c = 0
For i = 1 .. 10
Do
  c = c + b
Enddo
Double d = Fix(c*10)/10

K_L [12.03.2007 07:58:20]

#

For luku = 10.0 To 0 Step -(0.01)
           tulos = Fix(luku * 10) / 10

       Next

Ja otos tuloksesta
Lähtö on vasemmalla ja tulos on oikealla

10 10
9.99 9.9
9.98 9.9
9.97 9.9
9.96 9.9
9.95 9.9
9.94 9.9
9.93 9.9
9.92 9.9
9.91 9.9
9.9 9.9
9.89 9.8
9.88 9.8
9.87 9.8
9.86 9.8
9.85 9.8
9.84 9.8
9.83 9.8
9.82 9.8
9.81 9.8
9.8 9.8
9.79 9.7
9.78 9.7
9.77 9.7
9.76000000000001 9.7
9.75000000000001 9.7
9.74000000000001 9.7
9.73000000000001 9.7
9.72000000000001 9.7
9.71000000000001 9.7
9.70000000000001 9.7
9.69000000000001 9.6
9.68000000000001 9.6
9.67000000000001 9.6
9.66000000000001 9.6
9.65000000000001 9.6
9.64000000000001 9.6
9.63000000000001 9.6
9.62000000000001 9.6
9.61000000000001 9.6
9.60000000000001 9.6
9.59000000000001 9.5
9.58000000000001 9.5
9.57000000000001 9.5
9.56000000000001 9.5
9.55000000000001 9.5
9.54000000000001 9.5
9.53000000000001 9.5
9.52000000000001 9.5
9.51000000000001 9.5
9.50000000000001 9.5
9.49000000000001 9.4
9.48000000000001 9.4
9.47000000000001 9.4
9.46000000000001 9.4
9.45000000000001 9.4
9.44000000000001 9.4
9.43000000000001 9.4
9.42000000000001 9.4
9.41000000000001 9.4
9.40000000000001 9.4
9.39000000000001 9.3
9.38000000000001 9.3
9.37000000000001 9.3
9.36000000000001 9.3
9.35000000000001 9.3
9.34000000000001 9.3
9.33000000000001 9.3
9.32000000000001 9.3
9.31000000000001 9.3
9.30000000000001 9.3
9.29000000000002 9.2
9.28000000000002 9.2
9.27000000000002 9.2
9.26000000000002 9.2
9.25000000000002 9.2
9.24000000000002 9.2
9.23000000000002 9.2
9.22000000000002 9.2
9.21000000000002 9.2
9.20000000000002 9.2
9.19000000000002 9.1
9.18000000000002 9.1
9.17000000000002 9.1
9.16000000000002 9.1
9.15000000000002 9.1
9.14000000000002 9.1
9.13000000000002 9.1
9.12000000000002 9.1
9.11000000000002 9.1
9.10000000000002 9.1
9.09000000000002 9
9.08000000000002 9
9.07000000000002 9
9.06000000000002 9
9.05000000000002 9
9.04000000000002 9
9.03000000000002 9
9.02000000000002 9
9.01000000000002 9
9.00000000000002 9
...

Nyt jos joku voisi kertoa minulle, että mikä tuossa ei mukamas toimi?

koo [14.03.2007 20:54:47]

#

Et sitten testannut sillä rautakankimallilla. No ei se mitään, kyllä tuo sun oma testikin näyttää, mikä tässä jutussa ei toimi. Otetaan vielä kerran, taivutetaan ratakiskosta.

Kun liukuluku on koneen sisäisessä binäärisessä muodossa, esimerkiksi lukua (1/10)10 ei voida esittää tarkasti. Se on päättymätön binääriluku, ihan samalla tavalla kun kymmenjärjestelmässä luku (1/3)10. Seuraus: vaikka yksittäisen luvun katkaisu näyttää menevän oikein, katkaistuissa luvuissa on pieni virhe, joka voi kertyä merkittäväksi, kun katkaistuilla luvuilla lasketaan.

Omassa testissäsi luvun katkaisu näyttää menevän oikein. Yksittäisessä katkaisussa virhe on niin pieni, että se ei näy, kun tulostusta ei pakoteta tapahtumaan esmes 16 merkitsevällä numerolla.

Kuinka ollakaan, asia jota yritän tässä epätoivoisesti selostaa, onkin erinomaisen hyvin näkyvissä tuossa testin sarakkeessa, jossa katkaisua ei tehdä!

Sulla on siinä ensin luku 10. Vähennät siitä luvusta luvun 0,01, jonka arkijärjen mukaan pitäisi olla sama kuin (1/100)10. Jos homma kerta toimii, miten tuloksena voi 24 vähennyslaskun jälkeen olla jotakin sellaista kuin 9,76000000000001? Tuo 0,01 on tällä kertaa annettu ihan kirjoitettuna vakiona, mutta yhtä hyvinhän tai jopa vielä hälyttävämmin se olisi voinut tulla vaikka jostakin katkaisuoperaatiosta.

Rautakankimalli olisi voinut näyttää, kuinka katkaistulla luvulla laskemisessa saadaan merkittävä virhe aikaiseksi hyvinkin nopsasti, mutta ei sitten.

K_L [15.03.2007 12:28:26]

#

No kyllä käsitin sinun asiasi jo edellisellä yrittämällä.
Realismi kuitenkin on, että tarvitsin katkaistun luvun ehtoon. Ja kuten testit osoittavat määritellyillä raja-arvoilla ehto ei mene kertaakaan väärin.

Tarvii kyllä myöntää, että olin kuullut, mutta täysin unohtanut tämän ongelman näillä luvuilla. Valitettavasti niillä korteilla mennään, mitä on jaettu... Mutta kiitos kuitenkin.

koo [15.03.2007 15:07:13]

#

Liukuluvun desimaaliosilla laskettaessa tulee virhettä. Ehtolauseen vertailukin on eräänlainen laskuoperaatio. Samoin lukujen tyyppimuunnokset.

Aiemmassa testipätkässä ei ole mitään katkaistujen lukujen ehtokäsittelyyn liittyvää, joten siltä osin testi ei osoita mitään.

Mutta olkoon, kyllä se ainakin sinulla toimii, uskot siihen kovasti ja käytät noita juttuja vain "oikein". Joku muu saattaisi ehkä valita toisen lähestymistavan, ehkä jopa jonkun ehdottamistani.

K_L [15.03.2007 15:25:44]

#

En usko vaan tiedän...


Sivun alkuun

Vastaus

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

Tietoa sivustosta