1
0
mirror of https://github.com/golang/go synced 2024-11-22 09:24:41 -07:00

big: make Int and Rat implement fmt.Scanner

R=gri
CC=golang-dev
https://golang.org/cl/4552056
This commit is contained in:
Evan Shaw 2011-05-27 15:51:00 -07:00 committed by Robert Griesemer
parent 5a35757f3f
commit 3b980579b4
6 changed files with 275 additions and 55 deletions

View File

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"os" "os"
"rand" "rand"
"strings"
) )
// An Int represents a signed multi-precision integer. // An Int represents a signed multi-precision integer.
@ -325,7 +326,7 @@ func charset(ch int) string {
return lowercaseDigits[0:2] return lowercaseDigits[0:2]
case 'o': case 'o':
return lowercaseDigits[0:8] return lowercaseDigits[0:8]
case 'd', 'v': case 'd', 's', 'v':
return lowercaseDigits[0:10] return lowercaseDigits[0:10]
case 'x': case 'x':
return lowercaseDigits[0:16] return lowercaseDigits[0:16]
@ -374,6 +375,49 @@ func (x *Int) Format(s fmt.State, ch int) {
} }
// Scan is a support routine for fmt.Scanner. It accepts the formats
// 'b' (binary), 'o' (octal), 'd' (decimal), 'x' (lowercase hexadecimal),
// and 'X' (uppercase hexadecimal).
func (x *Int) Scan(s fmt.ScanState, ch int) os.Error {
var base int
switch ch {
case 'b':
base = 2
case 'o':
base = 8
case 'd':
base = 10
case 'x', 'X':
base = 16
case 's', 'v':
// let scan determine the base
default:
return os.ErrorString("Int.Scan: invalid verb")
}
ch, _, err := s.ReadRune()
if err != nil {
return err
}
neg := false
switch ch {
case '-':
neg = true
case '+': // nothing to do
default:
s.UnreadRune()
}
x.abs, _, err = x.abs.scan(s, base)
if err != nil {
return err
}
x.neg = len(x.abs) > 0 && neg // 0 has no sign
return nil
}
// Int64 returns the int64 representation of z. // Int64 returns the int64 representation of z.
// If z cannot be represented in an int64, the result is undefined. // If z cannot be represented in an int64, the result is undefined.
func (x *Int) Int64() int64 { func (x *Int) Int64() int64 {
@ -401,26 +445,27 @@ func (x *Int) Int64() int64 {
// base 2. Otherwise the selected base is 10. // base 2. Otherwise the selected base is 10.
// //
func (z *Int) SetString(s string, base int) (*Int, bool) { func (z *Int) SetString(s string, base int) (*Int, bool) {
if len(s) == 0 || base < 0 || base == 1 || 16 < base { neg := false
return z, false if len(s) > 0 {
} switch s[0] {
case '-':
neg := s[0] == '-' neg = true
if neg || s[0] == '+' { fallthrough
case '+':
s = s[1:] s = s[1:]
if len(s) == 0 {
return z, false
} }
} }
var scanned int r := strings.NewReader(s)
z.abs, _, scanned = z.abs.scan(s, base) abs, _, err := z.abs.scan(r, base)
if scanned != len(s) { if err != nil {
return z, false return z, false
} }
z.neg = len(z.abs) > 0 && neg // 0 has no sign _, _, err = r.ReadRune()
return z, true z.abs = abs
z.neg = len(abs) > 0 && neg // 0 has no sign
return z, err == os.EOF // err == os.EOF => scan consumed all of s
} }
@ -784,11 +829,11 @@ func (z *Int) GobEncode() ([]byte, os.Error) {
// GobDecode implements the gob.GobDecoder interface. // GobDecode implements the gob.GobDecoder interface.
func (z *Int) GobDecode(buf []byte) os.Error { func (z *Int) GobDecode(buf []byte) os.Error {
if len(buf) == 0 { if len(buf) == 0 {
return os.NewError("Int.GobDecode: no data") return os.ErrorString("Int.GobDecode: no data")
} }
b := buf[0] b := buf[0]
if b>>1 != version { if b>>1 != version {
return os.NewError(fmt.Sprintf("Int.GobDecode: encoding version %d not supported", b>>1)) return os.ErrorString(fmt.Sprintf("Int.GobDecode: encoding version %d not supported", b>>1))
} }
z.neg = b&1 != 0 z.neg = b&1 != 0
z.abs = z.abs.setBytes(buf[1:]) z.abs = z.abs.setBytes(buf[1:])

View File

@ -397,6 +397,49 @@ func TestFormat(t *testing.T) {
} }
var scanTests = []struct {
input string
format string
output string
remaining int
}{
{"1010", "%b", "10", 0},
{"0b1010", "%v", "10", 0},
{"12", "%o", "10", 0},
{"012", "%v", "10", 0},
{"10", "%d", "10", 0},
{"10", "%v", "10", 0},
{"a", "%x", "10", 0},
{"0xa", "%v", "10", 0},
{"A", "%X", "10", 0},
{"-A", "%X", "-10", 0},
{"+0b1011001", "%v", "89", 0},
{"0xA", "%v", "10", 0},
{"0 ", "%v", "0", 1},
{"2+3", "%v", "2", 2},
{"0XABC 12", "%v", "2748", 3},
}
func TestScan(t *testing.T) {
var buf bytes.Buffer
for i, test := range scanTests {
x := new(Int)
buf.Reset()
buf.WriteString(test.input)
if _, err := fmt.Fscanf(&buf, test.format, x); err != nil {
t.Errorf("#%d error: %s", i, err.String())
}
if x.String() != test.output {
t.Errorf("#%d got %s; want %s", i, x.String(), test.output)
}
if buf.Len() != test.remaining {
t.Errorf("#%d got %d bytes remaining; want %d", i, buf.Len(), test.remaining)
}
}
}
// Examples from the Go Language Spec, section "Arithmetic operators" // Examples from the Go Language Spec, section "Arithmetic operators"
var divisionSignsTests = []struct { var divisionSignsTests = []struct {
x, y int64 x, y int64

View File

@ -18,7 +18,11 @@ package big
// These are the building blocks for the operations on signed integers // These are the building blocks for the operations on signed integers
// and rationals. // and rationals.
import "rand" import (
"io"
"os"
"rand"
)
// An unsigned integer x of the form // An unsigned integer x of the form
@ -604,68 +608,95 @@ func (x nat) bitLen() int {
} }
func hexValue(ch byte) int { func hexValue(ch int) int {
var d byte var d int
switch { switch {
case '0' <= ch && ch <= '9': case '0' <= ch && ch <= '9':
d = ch - '0' d = ch - '0'
case 'a' <= ch && ch <= 'f': case 'a' <= ch && ch <= 'z':
d = ch - 'a' + 10 d = ch - 'a' + 10
case 'A' <= ch && ch <= 'F': case 'A' <= ch && ch <= 'Z':
d = ch - 'A' + 10 d = ch - 'A' + 10
default: default:
return -1 return -1
} }
return int(d) return d
} }
// scan returns the natural number corresponding to the // scan returns the natural number corresponding to the longest
// longest possible prefix of s representing a natural number in a // possible prefix read from r representing a natural number in a
// given conversion base, the actual conversion base used, and the // given conversion base, the actual conversion base used, and an
// prefix length. The syntax of natural numbers follows the syntax // error, if any. The syntax of natural numbers follows the syntax of
// of unsigned integer literals in Go. // unsigned integer literals in Go.
// //
// If the base argument is 0, the string prefix determines the actual // If the base argument is 0, the string prefix determines the actual
// conversion base. A prefix of ``0x'' or ``0X'' selects base 16; the // conversion base. A prefix of ``0x'' or ``0X'' selects base 16; the
// ``0'' prefix selects base 8, and a ``0b'' or ``0B'' prefix selects // ``0'' prefix selects base 8, and a ``0b'' or ``0B'' prefix selects
// base 2. Otherwise the selected base is 10. // base 2. Otherwise the selected base is 10.
// //
func (z nat) scan(s string, base int) (nat, int, int) { func (z nat) scan(r io.RuneScanner, base int) (nat, int, os.Error) {
n := 0
ch, _, err := r.ReadRune()
if err != nil {
return z, 0, err
}
// determine base if necessary // determine base if necessary
i, n := 0, len(s)
if base == 0 { if base == 0 {
base = 10 base = 10
if n > 0 && s[0] == '0' { if ch == '0' {
base, i = 8, 1 n++
if n > 1 { switch ch, _, err = r.ReadRune(); err {
switch s[1] { case nil:
base = 8
switch ch {
case 'x', 'X': case 'x', 'X':
base, i = 16, 2 base = 16
case 'b', 'B': case 'b', 'B':
base, i = 2, 2 base = 2
} }
if base == 2 || base == 16 {
n--
if ch, _, err = r.ReadRune(); err != nil {
return z, 0, os.ErrorString("syntax error scanning binary or hexadecimal number")
}
}
case os.EOF:
return z, 10, nil
default:
return z, 0, err
} }
} }
} }
// reject illegal bases or strings consisting only of prefix // reject illegal bases
if base < 2 || 16 < base || (base != 8 && i >= n) { if base < 2 || 'z'-'a'+10 < base {
return z, 0, 0 return z, 0, os.ErrorString("illegal number base")
} }
// convert string // convert string
z = z.make(0) z = z.make(0)
for ; i < n; i++ { for {
d := hexValue(s[i]) d := hexValue(ch)
if 0 <= d && d < base { if 0 <= d && d < base {
z = z.mulAddWW(z, Word(base), Word(d)) z = z.mulAddWW(z, Word(base), Word(d))
} else { } else {
r.UnreadRune()
if n > 0 {
break break
} }
return z, 0, os.ErrorString("syntax error scanning number")
}
n++
if ch, _, err = r.ReadRune(); err != nil {
if err == os.EOF {
break
}
return z, 0, err
}
} }
return z.norm(), base, i return z.norm(), base, nil
} }

View File

@ -4,7 +4,10 @@
package big package big
import "testing" import (
"strings"
"testing"
)
var cmpTests = []struct { var cmpTests = []struct {
x, y nat x, y nat
@ -180,6 +183,8 @@ var strTests = []struct {
{nat{1234567890}, uppercaseDigits[0:10], "1234567890"}, {nat{1234567890}, uppercaseDigits[0:10], "1234567890"},
{nat{0xdeadbeef}, lowercaseDigits[0:16], "deadbeef"}, {nat{0xdeadbeef}, lowercaseDigits[0:16], "deadbeef"},
{nat{0xdeadbeef}, uppercaseDigits[0:16], "DEADBEEF"}, {nat{0xdeadbeef}, uppercaseDigits[0:16], "DEADBEEF"},
{nat{0x229be7}, lowercaseDigits[0:17], "1a2b3c"},
{nat{0x309663e6}, uppercaseDigits[0:32], "O9COV6"},
} }
@ -190,15 +195,58 @@ func TestString(t *testing.T) {
t.Errorf("string%+v\n\tgot s = %s; want %s", a, s, a.s) t.Errorf("string%+v\n\tgot s = %s; want %s", a, s, a.s)
} }
x, b, n := nat(nil).scan(a.s, len(a.c)) x, b, err := nat(nil).scan(strings.NewReader(a.s), len(a.c))
if x.cmp(a.x) != 0 { if x.cmp(a.x) != 0 {
t.Errorf("scan%+v\n\tgot z = %v; want %v", a, x, a.x) t.Errorf("scan%+v\n\tgot z = %v; want %v", a, x, a.x)
} }
if b != len(a.c) { if b != len(a.c) {
t.Errorf("scan%+v\n\tgot b = %d; want %d", a, b, len(a.c)) t.Errorf("scan%+v\n\tgot b = %d; want %d", a, b, len(a.c))
} }
if n != len(a.s) { if err != nil {
t.Errorf("scan%+v\n\tgot n = %d; want %d", a, n, len(a.s)) t.Errorf("scan%+v\n\tgot error = %s", a, err)
}
}
}
var natScanTests = []struct {
s string // string to be scanned
x nat // expected nat
base int // expected base
ok bool // expected success
}{
{s: ""},
{"0", nil, 10, true},
{"0 ", nil, 8, true},
{s: "0x"},
{"08", nil, 8, true},
{"0b1", nat{1}, 2, true},
{"0b11000101", nat{0xc5}, 2, true},
{"03271", nat{03271}, 8, true},
{"10ab", nat{10}, 10, true},
{"1234567890", nat{1234567890}, 10, true},
{"0xdeadbeef", nat{0xdeadbeef}, 16, true},
{"0XDEADBEEF", nat{0xdeadbeef}, 16, true},
}
func TestScanBase0(t *testing.T) {
for _, a := range natScanTests {
x, b, err := nat(nil).scan(strings.NewReader(a.s), 0)
if err == nil && !a.ok {
t.Errorf("scan%+v\n\texpected error", a)
}
if err != nil {
if a.ok {
t.Errorf("scan%+v\n\tgot error = %s", a, err)
}
continue
}
if x.cmp(a.x) != 0 {
t.Errorf("scan%+v\n\tgot z = %v; want %v", a, x, a.x)
}
if b != a.base {
t.Errorf("scan%+v\n\tgot b = %d; want %d", a, b, a.base)
} }
} }
} }
@ -344,14 +392,14 @@ var expNNTests = []struct {
func TestExpNN(t *testing.T) { func TestExpNN(t *testing.T) {
for i, test := range expNNTests { for i, test := range expNNTests {
x, _, _ := nat(nil).scan(test.x, 0) x, _, _ := nat(nil).scan(strings.NewReader(test.x), 0)
y, _, _ := nat(nil).scan(test.y, 0) y, _, _ := nat(nil).scan(strings.NewReader(test.y), 0)
out, _, _ := nat(nil).scan(test.out, 0) out, _, _ := nat(nil).scan(strings.NewReader(test.out), 0)
var m nat var m nat
if len(test.m) > 0 { if len(test.m) > 0 {
m, _, _ = nat(nil).scan(test.m, 0) m, _, _ = nat(nil).scan(strings.NewReader(test.m), 0)
} }
z := nat(nil).expNN(x, y, m) z := nat(nil).expNN(x, y, m)

View File

@ -6,7 +6,11 @@
package big package big
import "strings" import (
"fmt"
"os"
"strings"
)
// A Rat represents a quotient a/b of arbitrary precision. The zero value for // A Rat represents a quotient a/b of arbitrary precision. The zero value for
// a Rat, 0/0, is not a legal Rat. // a Rat, 0/0, is not a legal Rat.
@ -209,6 +213,28 @@ func (z *Rat) Set(x *Rat) *Rat {
} }
func ratTok(ch int) bool {
return strings.IndexRune("+-/0123456789.eE", ch) >= 0
}
// Scan is a support routine for fmt.Scanner. It accepts the formats
// 'e', 'E', 'f', 'F', 'g', 'G', and 'v'. All formats are equivalent.
func (z *Rat) Scan(s fmt.ScanState, ch int) os.Error {
tok, err := s.Token(true, ratTok)
if err != nil {
return err
}
if strings.IndexRune("efgEFGv", ch) < 0 {
return os.ErrorString("Rat.Scan: invalid verb")
}
if _, ok := z.SetString(string(tok)); !ok {
return os.ErrorString("Rat.Scan: invalid syntax")
}
return nil
}
// SetString sets z to the value of s and returns z and a boolean indicating // SetString sets z to the value of s and returns z and a boolean indicating
// success. s can be given as a fraction "a/b" or as a floating-point number // success. s can be given as a fraction "a/b" or as a floating-point number
// optionally followed by an exponent. If the operation failed, the value of z // optionally followed by an exponent. If the operation failed, the value of z
@ -225,8 +251,8 @@ func (z *Rat) SetString(s string) (*Rat, bool) {
return z, false return z, false
} }
s = s[sep+1:] s = s[sep+1:]
var n int var err os.Error
if z.b, _, n = z.b.scan(s, 10); n != len(s) { if z.b, _, err = z.b.scan(strings.NewReader(s), 10); err != nil {
return z, false return z, false
} }
return z.norm(), true return z.norm(), true

View File

@ -4,7 +4,11 @@
package big package big
import "testing" import (
"bytes"
"fmt"
"testing"
)
var setStringTests = []struct { var setStringTests = []struct {
@ -53,6 +57,29 @@ func TestRatSetString(t *testing.T) {
} }
func TestRatScan(t *testing.T) {
var buf bytes.Buffer
for i, test := range setStringTests {
x := new(Rat)
buf.Reset()
buf.WriteString(test.in)
_, err := fmt.Fscanf(&buf, "%v", x)
if err == nil != test.ok {
if test.ok {
t.Errorf("#%d error: %s", i, err.String())
} else {
t.Errorf("#%d expected error", i)
}
continue
}
if err == nil && x.RatString() != test.out {
t.Errorf("#%d got %s want %s", i, x.RatString(), test.out)
}
}
}
var floatStringTests = []struct { var floatStringTests = []struct {
in string in string
prec int prec int