Perustietotyyppien ominaisuuksista

Seuraavat kaksi ohjelmaa vaikuttavat ensi silmäyksellä identtisiltä:

def main():
    luku = int(input("Syötä luku: "))
    if luku < 0:
        print("Oltava ei-negatiivinen!")
    else:
        tulos = 1;
        while luku > 0:
            tulos = tulos * luku
            luku -= 1
        print("Kertoma on:", tulos)

main()
#include <iostream>

using namespace std;

int main() {
    cout << "Syota luku: ";
    int luku = 0;
    cin >> luku;

    if ( luku < 0 ) {
        cout << "Oltava ei-negatiivinen!" << endl;
    } else {
        int tulos = 1;
        while ( luku > 0 ) {
            tulos = tulos * luku;
            --luku;
        }
        cout << "Kertoma on: " << tulos << endl;
    }
}

Kuitenkin jos ohjelmat suoritetaan ja annetaan alkuarvoksi esimerkiksi 17, tulostaa Python-ohjelma 355687428096000 ja C++-ohjelma -288522240. C++:n laskema tulos on epäilemättä väärin, sillä kertoman arvo (ei-negatiivisten lukujen tulo) ei voi olla negatiivinen. Mistä ongelma johtuu?

Python on jossain määrin harvinainen ohjelmointikieli, koska siinä kokonaislukujen arvoa ei ole rajoitettu. Useimmissa muissa ohjelmointikielissä kokonaisluvut (ja muutkin lukutyypit) esitetään konekielitasolle päädyttäessä jollain kiinteällä määrällä bittejä. Tämä bittimäärä riippuu pääosin käytössä olevasta prosessoriarkkitehtuurista, mutta joskus myös käytetystä kääntäjästä. Tyypillinen bittimäärä kokonaislukuarvojen tapauksessa on 32 bittiä, mutta saattaa olla esimerkiksi 16 tai 64 bittiäkin.

Jos koneen prosessori esittää kokonaislukuarvot 32 bitillä, se ei pysty käsittelemään kuin kokonaislukuja, jotka ovat välillä -2147483648 – 2147483647 (yhteensä 232 erilaista). Jos laskutoimituksen tuloksena syntyy em. prosessorilla kokonaisluku, jota ei voi esittää 32 bitillä, tapahtuu ylivuoto. Ylivuodon seurauksena tulos on tietenkin virheellinen.

../../_images/speed_odometer.jpg

Ylivuotoa voi verrata siihen tilanteeseen, kun auton matkamittari tulee suurimpaan mahdolliseen lukemaan, jonka se kykenee esittämään, ja “pyörähtää ympäri” takaisin pienimpään lukemaan, jonka se kykenee esittämään. (Kuva: Ant75)

Vastaava tilanne voi syntyä myös reaaliluvuilla laskettaessa, olkoonkin että bittimäärät ja lukuarvot eivät ole samat, koska reaaliluvut esitetään eri muodossa kuin kokonaisluvut. Alivuodoksi kutsutaan tilannetta, jossa reaaliluvuilla laskettaessa saadaan itseisarvoltaan niin pieni tulos, että prosessori ei osaa erottaa sitä nollasta.

C++:ssa ohjelmoija voi jossain määrin yrittää vaikuttaa siihen, miten lukuja käsitellään: C++:ssa esimerkiksi on useita eri kokonaislukujen esittämiseen tarkoitettuja tietotyyppejä, kun Pythonissa oli vain yksi.

Koska kyseessä on jonkin verran nippelitietämys, tällä kurssilla yritetään selvitä seuraavilla tyypeillä:

  • int eli normaali kokonaislukutyyppi, joka sopii positiivisten ja negatiivisten arvojen käsittelyyn
  • unsigned int eli kokonaislukutyyppi, jolla voi käsitellä vain ei-negatiivisia lukuja (luonnolliset luvut)
  • long int eli tyyppi, joka saattaa mahdollistaa suuremman esitysalueen kuin normaali int-tyyppi, mutta vain jos tietokoneen prosessoriarkkitehtuuri tukee sitä
  • unsigned long int, joka on melko looginen yhdistelmä kahta edellistä.

Olennainen huomio tässä vaiheessa on se, että C++-kieli ei määrittele, kuinka monella bitillä lukutietotyypit esitetään. Ei siis kannata luottaa siihen, että int-tyyppiset kokonaisluvut esitetään aina ja kaikkialla 32 bitillä, vaikka se sattumalta onkin niin kurssin työympäristössä.