Go Home Page
The Go Programming Language

Source file src/pkg/strconv/atoi.go

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package strconv

import "os"

type NumError struct {
    Num   string
    Error os.Error
}

func (e *NumError) String() string { return "parsing " + e.Num + ": " + e.Error.String() }


func computeIntsize() uint {
    siz := uint(8)
    for 1<<siz != 0 {
        siz *= 2
    }
    return siz
}

var IntSize = computeIntsize()

// Return the first number n such that n*base >= 1<<64.
func cutoff64(base int) uint64 {
    if base < 2 {
        return 0
    }
    return (1<<64-1)/uint64(base) + 1
}

// Btoui64 interprets a string s in an arbitrary base b (2 to 36)
// and returns the corresponding value n.  If b == 0, the base
// is taken from the string prefix: base 16 for "0x", base 8 for "0",
// and base 10 otherwise.
//
// The errors that Btoui64 returns have concrete type *NumError
// and include err.Num = s.  If s is empty or contains invalid
// digits, err.Error = os.EINVAL; if the value corresponding
// to s cannot be represented by a uint64, err.Error = os.ERANGE.
func Btoui64(s string, b int) (n uint64, err os.Error) {
    s0 := s
    switch {
    case len(s) < 1:
        err = os.EINVAL
        goto Error

    case 2 <= b && b <= 36:
        // valid base; nothing to do

    case b == 0:
        // Look for octal, hex prefix.
        switch {
        case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'):
            b = 16
            s = s[2:]
            if len(s) < 1 {
                err = os.EINVAL
                goto Error
            }
        case s[0] == '0':
            b = 8
        default:
            b = 10
        }

    default:
        err = os.ErrorString("invalid base " + Itoa(b))
        goto Error
    }

    n = 0
    cutoff := cutoff64(b)

    for i := 0; i < len(s); i++ {
        var v byte
        d := s[i]
        switch {
        case '0' <= d && d <= '9':
            v = d - '0'
        case 'a' <= d && d <= 'z':
            v = d - 'a' + 10
        case 'A' <= d && d <= 'Z':
            v = d - 'A' + 10
        default:
            n = 0
            err = os.EINVAL
            goto Error
        }
        if int(v) >= b {
            n = 0
            err = os.EINVAL
            goto Error
        }

        if n >= cutoff {
            // n*b overflows
            n = 1<<64 - 1
            err = os.ERANGE
            goto Error
        }
        n *= uint64(b)

        n1 := n + uint64(v)
        if n1 < n {
            // n+v overflows
            n = 1<<64 - 1
            err = os.ERANGE
            goto Error
        }
        n = n1
    }

    return n, nil

Error:
    return n, &NumError{s0, err}
}

// Atoui64 interprets a string s as a decimal number and
// returns the corresponding value n.
//
// Atoui64 returns err == os.EINVAL if s is empty or contains invalid digits.
// It returns err == os.ERANGE if s cannot be represented by a uint64.
func Atoui64(s string) (n uint64, err os.Error) {
    return Btoui64(s, 10)
}

// Btoi64 is like Btoui64 but allows signed numbers and
// returns its result in an int64.
func Btoi64(s string, base int) (i int64, err os.Error) {
    // Empty string bad.
    if len(s) == 0 {
        return 0, &NumError{s, os.EINVAL}
    }

    // Pick off leading sign.
    s0 := s
    neg := false
    if s[0] == '+' {
        s = s[1:]
    } else if s[0] == '-' {
        neg = true
        s = s[1:]
    }

    // Convert unsigned and check range.
    var un uint64
    un, err = Btoui64(s, base)
    if err != nil && err.(*NumError).Error != os.ERANGE {
        err.(*NumError).Num = s0
        return 0, err
    }
    if !neg && un >= 1<<63 {
        return 1<<63 - 1, &NumError{s0, os.ERANGE}
    }
    if neg && un > 1<<63 {
        return -1 << 63, &NumError{s0, os.ERANGE}
    }
    n := int64(un)
    if neg {
        n = -n
    }
    return n, nil
}

// Atoi64 is like Atoui64 but allows signed numbers and
// returns its result in an int64.
func Atoi64(s string) (i int64, err os.Error) { return Btoi64(s, 10) }


// Atoui is like Atoui64 but returns its result as a uint.
func Atoui(s string) (i uint, err os.Error) {
    i1, e1 := Atoui64(s)
    if e1 != nil && e1.(*NumError).Error != os.ERANGE {
        return 0, e1
    }
    i = uint(i1)
    if uint64(i) != i1 {
        return ^uint(0), &NumError{s, os.ERANGE}
    }
    return i, nil
}

// Atoi is like Atoi64 but returns its result as an int.
func Atoi(s string) (i int, err os.Error) {
    i1, e1 := Atoi64(s)
    if e1 != nil && e1.(*NumError).Error != os.ERANGE {
        return 0, e1
    }
    i = int(i1)
    if int64(i) != i1 {
        if i1 < 0 {
            return -1 << (IntSize - 1), &NumError{s, os.ERANGE}
        }
        return 1<<(IntSize-1) - 1, &NumError{s, os.ERANGE}
    }
    return i, nil
}