1
0
mirror of https://github.com/golang/go synced 2024-10-01 05:18:33 -06:00

math/big: permit internal nat.scan to accept decimal point

This will simplify parsing of rational and (eventually) floating point numbers.

Also streamlined inner loop. As a result, scan runs faster for all but short
(<= 10 digit) numbers. For short numbers it is < 10% slower (cause is known
and could be addressed in a future CL).

Minor unrelated cleanups. Added additional tests.

benchmark                     old ns/op     new ns/op     delta
BenchmarkScanPi               134465        125122        -6.95%
BenchmarkScan10Base2          493           540           +9.53%
BenchmarkScan100Base2         3608          3244          -10.09%
BenchmarkScan1000Base2        35376         32377         -8.48%
BenchmarkScan10000Base2       481504        450028        -6.54%
BenchmarkScan100000Base2      17936774      17648080      -1.61%
BenchmarkScan10Base8          258           280           +8.53%
BenchmarkScan100Base8         1389          1323          -4.75%
BenchmarkScan1000Base8        14221         13036         -8.33%
BenchmarkScan10000Base8       271298        258993        -4.54%
BenchmarkScan100000Base8      15715465      15672580      -0.27%
BenchmarkScan10Base10         249           268           +7.63%
BenchmarkScan100Base10        1324          1220          -7.85%
BenchmarkScan1000Base10       13398         12234         -8.69%
BenchmarkScan10000Base10      259157        249342        -3.79%
BenchmarkScan100000Base10     15670613      15582409      -0.56%
BenchmarkScan10Base16         231           251           +8.66%
BenchmarkScan100Base16        1093          1065          -2.56%
BenchmarkScan1000Base16       12687         12196         -3.87%
BenchmarkScan10000Base16      282349        271443        -3.86%
BenchmarkScan100000Base16     16742669      16552917      -1.13%

Change-Id: I4b9b078792788aef872b307399f00ffd34903127
Reviewed-on: https://go-review.googlesource.com/2960
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Robert Griesemer 2015-01-15 18:38:25 -08:00
parent f5b8813e93
commit 4e0618c992
5 changed files with 258 additions and 123 deletions

View File

@ -477,7 +477,7 @@ func (z *Int) scan(r io.RuneScanner, base int) (*Int, int, error) {
} }
// determine mantissa // determine mantissa
z.abs, base, err = z.abs.scan(r, base) z.abs, base, _, err = z.abs.scan(r, base)
if err != nil { if err != nil {
return nil, base, err return nil, base, err
} }

View File

@ -621,7 +621,7 @@ func TestDivisionSigns(t *testing.T) {
} }
} }
var bitTests = []nat{ var bitsTests = []nat{
nil, nil,
{0}, {0},
{1}, {1},
@ -639,7 +639,7 @@ func norm(x nat) nat {
} }
func TestBits(t *testing.T) { func TestBits(t *testing.T) {
for _, test := range bitTests { for _, test := range bitsTests {
var z Int var z Int
z.neg = true z.neg = true
got := z.SetBits(test) got := z.SetBits(test)

View File

@ -68,7 +68,7 @@ func (z nat) norm() nat {
func (z nat) make(n int) nat { func (z nat) make(n int) nat {
if n <= cap(z) { if n <= cap(z) {
return z[0:n] // reuse z return z[:n] // reuse z
} }
// Choosing a good value for e has significant performance impact // Choosing a good value for e has significant performance impact
// because it increases the chance that a value can be reused. // because it increases the chance that a value can be reused.
@ -78,7 +78,7 @@ func (z nat) make(n int) nat {
func (z nat) setWord(x Word) nat { func (z nat) setWord(x Word) nat {
if x == 0 { if x == 0 {
return z.make(0) return z[:0]
} }
z = z.make(1) z = z.make(1)
z[0] = x z[0] = x
@ -122,7 +122,7 @@ func (z nat) add(x, y nat) nat {
return z.add(y, x) return z.add(y, x)
case m == 0: case m == 0:
// n == 0 because m >= n; result is 0 // n == 0 because m >= n; result is 0
return z.make(0) return z[:0]
case n == 0: case n == 0:
// result is x // result is x
return z.set(x) return z.set(x)
@ -148,7 +148,7 @@ func (z nat) sub(x, y nat) nat {
panic("underflow") panic("underflow")
case m == 0: case m == 0:
// n == 0 because m >= n; result is 0 // n == 0 because m >= n; result is 0
return z.make(0) return z[:0]
case n == 0: case n == 0:
// result is x // result is x
return z.set(x) return z.set(x)
@ -384,7 +384,7 @@ func (z nat) mul(x, y nat) nat {
case m < n: case m < n:
return z.mul(y, x) return z.mul(y, x)
case m == 0 || n == 0: case m == 0 || n == 0:
return z.make(0) return z[:0]
case n == 1: case n == 1:
return z.mulAddWW(x, y[0], 0) return z.mulAddWW(x, y[0], 0)
} }
@ -488,7 +488,7 @@ func (z nat) divW(x nat, y Word) (q nat, r Word) {
q = z.set(x) // result is x q = z.set(x) // result is x
return return
case m == 0: case m == 0:
q = z.make(0) // result is 0 q = z[:0] // result is 0
return return
} }
// m > 0 // m > 0
@ -504,7 +504,7 @@ func (z nat) div(z2, u, v nat) (q, r nat) {
} }
if u.cmp(v) < 0 { if u.cmp(v) < 0 {
q = z.make(0) q = z[:0]
r = z2.set(u) r = z2.set(u)
return return
} }
@ -543,7 +543,7 @@ func (z nat) divLarge(u, uIn, v nat) (q, r nat) {
u = nil // u is an alias for uIn or v - cannot reuse u = nil // u is an alias for uIn or v - cannot reuse
} }
u = u.make(len(uIn) + 1) u = u.make(len(uIn) + 1)
u.clear() u.clear() // TODO(gri) no need to clear if we allocated a new u
// D1. // D1.
shift := leadingZeros(v[n-1]) shift := leadingZeros(v[n-1])
@ -607,51 +607,82 @@ func (x nat) bitLen() int {
} }
// MaxBase is the largest number base accepted for string conversions. // MaxBase is the largest number base accepted for string conversions.
const MaxBase = 'z' - 'a' + 10 + 1 // = hexValue('z') + 1 const MaxBase = 'z' - 'a' + 10 + 1
func hexValue(ch rune) Word { // maxPow returns (b**n, n) such that b**n is the largest power b**n <= _M.
d := int(MaxBase + 1) // invalid base // For instance maxPow(10) == (1e19, 19) for 19 decimal digits in a 64bit Word.
switch { // In other words, at most n digits in base b fit into a Word.
case '0' <= ch && ch <= '9': // TODO(gri) replace this with a table, generated at build time.
d = int(ch - '0') func maxPow(b Word) (p Word, n int) {
case 'a' <= ch && ch <= 'z': p, n = b, 1 // assuming b <= _M
d = int(ch - 'a' + 10) for max := _M / b; p <= max; {
case 'A' <= ch && ch <= 'Z': // p == b**n && p <= max
d = int(ch - 'A' + 10) p *= b
n++
} }
return Word(d) // p == b**n && p <= _M
return
} }
// scan sets z to the natural number corresponding to the longest possible prefix // pow returns x**n for n > 0, and 1 otherwise.
// read from r representing an unsigned integer in a given conversion base. func pow(x Word, n int) (p Word) {
// It returns z, the actual conversion base used, and an error, if any. In the p = 1
// error case, the value of z is undefined. The syntax follows the syntax of for n > 0 {
// unsigned integer literals in Go. if n&1 != 0 {
p *= x
}
x *= x
n >>= 1
}
return
}
// scan scans the number corresponding to the longest possible prefix
// from r representing an unsigned number in a given conversion base.
// It returns the corresponding natural number res, the actual base b,
// a digit count, and an error err, if any.
// //
// The base argument must be 0 or a value from 2 through MaxBase. If the base // number = [ prefix ] digits | digits "." [ digits ] | "." digits .
// is 0, the string prefix determines the actual conversion base. A prefix of // prefix = "0" [ "x" | "X" | "b" | "B" ] .
// ``0x'' or ``0X'' selects base 16; the ``0'' prefix selects base 8, and a // digits = digit { digit } .
// ``0b'' or ``0B'' prefix selects base 2. Otherwise the selected base is 10. // digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" .
// //
func (z nat) scan(r io.RuneScanner, base int) (nat, int, error) { // The base argument must be a value between 0 and MaxBase (inclusive).
// reject invalid bases // For base 0, the number prefix determines the actual base: A prefix of
if base < 0 || base == 1 || MaxBase < base { // ``0x'' or ``0X'' selects base 16; the ``0'' prefix selects base 8, and
return z, 0, errors.New("invalid number base") // a ``0b'' or ``0B'' prefix selects base 2. Otherwise the selected base
// is 10 and no prefix is permitted.
//
// Base argument 1 selects actual base 10 but also enables scanning a number
// with a decimal point.
//
// A result digit count > 0 corresponds to the number of (non-prefix) digits
// parsed. A digit count <= 0 indicates the presence of a decimal point (for
// base == 1, only), and the number of fractional digits is -count. In this
// case, the value of the scanned number is res * 10**count.
//
func (z nat) scan(r io.RuneScanner, base int) (res nat, b, count int, err error) {
// reject illegal bases
if base < 0 || base > MaxBase {
err = errors.New("illegal number base")
return
} }
// one char look-ahead // one char look-ahead
ch, _, err := r.ReadRune() ch, _, err := r.ReadRune()
if err != nil { if err != nil {
return z, 0, err return
} }
// determine base if necessary // determine actual base
b := Word(base) switch base {
if base == 0 { case 0:
// actual base is 10 unless there's a base prefix
b = 10 b = 10
if ch == '0' { if ch == '0' {
switch ch, _, err = r.ReadRune(); err { switch ch, _, err = r.ReadRune(); err {
case nil: case nil:
// possibly one of 0x, 0X, 0b, 0B
b = 8 b = 8
switch ch { switch ch {
case 'x', 'X': case 'x', 'X':
@ -661,62 +692,120 @@ func (z nat) scan(r io.RuneScanner, base int) (nat, int, error) {
} }
if b == 2 || b == 16 { if b == 2 || b == 16 {
if ch, _, err = r.ReadRune(); err != nil { if ch, _, err = r.ReadRune(); err != nil {
return z, 0, err // io.EOF is also an error in this case
return
} }
} }
case io.EOF: case io.EOF:
return z.make(0), 10, nil // input is "0"
res = z[:0]
count = 1
err = nil
return
default: default:
return z, 10, err // read error
return
} }
} }
case 1:
// actual base is 10 and decimal point is permitted
b = 10
default:
b = base
} }
// convert string // convert string
// - group as many digits d as possible together into a "super-digit" dd with "super-base" bb // Algorithm: Collect digits in groups of at most n digits in di
// - only when bb does not fit into a word anymore, do a full number mulAddWW using bb and dd // and then use mulAddWW for every such group to add them to the
z = z.make(0) // result.
bb := Word(1) z = z[:0]
dd := Word(0) b1 := Word(b)
for max := _M / b; ; { bn, n := maxPow(b1) // at most n digits in base b1 fit into Word
d := hexValue(ch) di := Word(0) // 0 <= di < b1**i < bn
if d >= b { i := 0 // 0 <= i < n
dp := -1 // position of decimal point
for {
if base == 1 && ch == '.' {
base = 10 // no 2nd decimal point permitted
dp = count
// advance
if ch, _, err = r.ReadRune(); err != nil {
if err == io.EOF {
err = nil
break
}
return
}
}
// convert rune into digit value d1
var d1 Word
switch {
case '0' <= ch && ch <= '9':
d1 = Word(ch - '0')
case 'a' <= ch && ch <= 'z':
d1 = Word(ch - 'a' + 10)
case 'A' <= ch && ch <= 'Z':
d1 = Word(ch - 'A' + 10)
default:
d1 = MaxBase + 1
}
if d1 >= b1 {
r.UnreadRune() // ch does not belong to number anymore r.UnreadRune() // ch does not belong to number anymore
break break
} }
count++
if bb <= max { // collect d1 in di
bb *= b di = di*b1 + d1
dd = dd*b + d i++
} else {
// bb * b would overflow // if di is "full", add it to the result
z = z.mulAddWW(z, bb, dd) if i == n {
bb = b z = z.mulAddWW(z, bn, di)
dd = d di = 0
i = 0
} }
// advance
if ch, _, err = r.ReadRune(); err != nil { if ch, _, err = r.ReadRune(); err != nil {
if err != io.EOF { if err == io.EOF {
return z, int(b), err err = nil
}
break break
} }
return
}
} }
if count == 0 {
// no digits found
switch { switch {
case bb > 1:
// there was at least one mantissa digit
z = z.mulAddWW(z, bb, dd)
case base == 0 && b == 8: case base == 0 && b == 8:
// there was only the octal prefix 0 (possibly followed by digits > 7); // there was only the octal prefix 0 (possibly followed by digits > 7);
// return base 10, not 8 // count as one digit and return base 10, not 8
return z, 10, nil count = 1
b = 10
case base != 0 || b != 8: case base != 0 || b != 8:
// there was neither a mantissa digit nor the octal prefix 0 // there was neither a mantissa digit nor the octal prefix 0
return z, int(b), errors.New("syntax error scanning number") err = errors.New("syntax error scanning number")
}
return
}
// count > 0
// add remaining digits to result
if i > 0 {
z = z.mulAddWW(z, pow(b1, i), di)
}
res = z.norm()
// adjust for fraction, if any
if dp >= 0 {
// 0 <= dp <= count > 0
count = dp - count
} }
return z.norm(), int(b), nil return
} }
// Character sets for string conversion. // Character sets for string conversion.
@ -799,14 +888,7 @@ func (x nat) string(charset string) string {
} }
} else { } else {
// determine "big base"; i.e., the largest possible value bb bb, ndigits := maxPow(Word(b))
// that is a power of base b and still fits into a Word
// (as in 10^19 for 19 decimal digits in a 64bit Word)
bb := b // big base is b**ndigits
ndigits := 1 // number of base b digits
for max := Word(_M / b); bb <= max; bb *= b {
ndigits++ // maximize ndigits where bb = b**ndigits, bb <= _M
}
// construct table of successive squares of bb*leafSize to use in subdivisions // construct table of successive squares of bb*leafSize to use in subdivisions
// result (table != nil) <=> (len(x) > leafSize > 0) // result (table != nil) <=> (len(x) > leafSize > 0)
@ -1047,7 +1129,7 @@ func (x nat) trailingZeroBits() uint {
func (z nat) shl(x nat, s uint) nat { func (z nat) shl(x nat, s uint) nat {
m := len(x) m := len(x)
if m == 0 { if m == 0 {
return z.make(0) return z[:0]
} }
// m > 0 // m > 0
@ -1064,7 +1146,7 @@ func (z nat) shr(x nat, s uint) nat {
m := len(x) m := len(x)
n := m - int(s/_W) n := m - int(s/_W)
if n <= 0 { if n <= 0 {
return z.make(0) return z[:0]
} }
// n > 0 // n > 0
@ -1103,12 +1185,14 @@ func (z nat) setBit(x nat, i uint, b uint) nat {
panic("set bit is not 0 or 1") panic("set bit is not 0 or 1")
} }
func (z nat) bit(i uint) uint { // bit returns the value of the i'th bit, with lsb == bit 0.
j := int(i / _W) func (x nat) bit(i uint) uint {
if j >= len(z) { j := i / _W
if j >= uint(len(x)) {
return 0 return 0
} }
return uint(z[j] >> (i % _W) & 1) // 0 <= j < len(x)
return uint(x[j] >> (i % _W) & 1)
} }
func (z nat) and(x, y nat) nat { func (z nat) and(x, y nat) nat {

View File

@ -88,7 +88,7 @@ var prodNN = []argNN{
} }
func natFromString(s string) nat { func natFromString(s string) nat {
x, _, err := nat(nil).scan(strings.NewReader(s), 0) x, _, _, err := nat(nil).scan(strings.NewReader(s), 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -271,7 +271,7 @@ 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, err := nat(nil).scan(strings.NewReader(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)
} }
@ -289,12 +289,12 @@ var natScanTests = []struct {
base int // input base base int // input base
x nat // expected nat x nat // expected nat
b int // expected base b int // expected base
count int // expected digit count
ok bool // expected success ok bool // expected success
next rune // next character (or 0, if at EOF) next rune // next character (or 0, if at EOF)
}{ }{
// error: illegal base // error: illegal base
{base: -1}, {base: -1},
{base: 1},
{base: 37}, {base: 37},
// error: no mantissa // error: no mantissa
@ -306,31 +306,46 @@ var natScanTests = []struct {
{s: "0x"}, {s: "0x"},
{s: "345", base: 2}, {s: "345", base: 2},
// error: incorrect use of decimal point
{s: ".0"},
{s: ".0", base: 10},
{s: ".", base: 1},
{s: "0x.0"},
// no errors // no errors
{"0", 0, nil, 10, true, 0}, {"0", 0, nil, 10, 1, true, 0},
{"0", 10, nil, 10, true, 0}, {"0", 10, nil, 10, 1, true, 0},
{"0", 36, nil, 36, true, 0}, {"0", 36, nil, 36, 1, true, 0},
{"1", 0, nat{1}, 10, true, 0}, {"1", 0, nat{1}, 10, 1, true, 0},
{"1", 10, nat{1}, 10, true, 0}, {"1", 10, nat{1}, 10, 1, true, 0},
{"0 ", 0, nil, 10, true, ' '}, {"0 ", 0, nil, 10, 1, true, ' '},
{"08", 0, nil, 10, true, '8'}, {"08", 0, nil, 10, 1, true, '8'},
{"018", 0, nat{1}, 8, true, '8'}, {"08", 10, nat{8}, 10, 2, true, 0},
{"0b1", 0, nat{1}, 2, true, 0}, {"018", 0, nat{1}, 8, 1, true, '8'},
{"0b11000101", 0, nat{0xc5}, 2, true, 0}, {"0b1", 0, nat{1}, 2, 1, true, 0},
{"03271", 0, nat{03271}, 8, true, 0}, {"0b11000101", 0, nat{0xc5}, 2, 8, true, 0},
{"10ab", 0, nat{10}, 10, true, 'a'}, {"03271", 0, nat{03271}, 8, 4, true, 0},
{"1234567890", 0, nat{1234567890}, 10, true, 0}, {"10ab", 0, nat{10}, 10, 2, true, 'a'},
{"xyz", 36, nat{(33*36+34)*36 + 35}, 36, true, 0}, {"1234567890", 0, nat{1234567890}, 10, 10, true, 0},
{"xyz?", 36, nat{(33*36+34)*36 + 35}, 36, true, '?'}, {"xyz", 36, nat{(33*36+34)*36 + 35}, 36, 3, true, 0},
{"0x", 16, nil, 16, true, 'x'}, {"xyz?", 36, nat{(33*36+34)*36 + 35}, 36, 3, true, '?'},
{"0xdeadbeef", 0, nat{0xdeadbeef}, 16, true, 0}, {"0x", 16, nil, 16, 1, true, 'x'},
{"0XDEADBEEF", 0, nat{0xdeadbeef}, 16, true, 0}, {"0xdeadbeef", 0, nat{0xdeadbeef}, 16, 8, true, 0},
{"0XDEADBEEF", 0, nat{0xdeadbeef}, 16, 8, true, 0},
// no errors, decimal point
{"0.", 0, nil, 10, 1, true, '.'},
{"0.", 1, nil, 10, 0, true, 0},
{"0.1.2", 1, nat{1}, 10, -1, true, '.'},
{".000", 1, nil, 10, -3, true, 0},
{"12.3", 1, nat{123}, 10, -1, true, 0},
{"012.345", 1, nat{12345}, 10, -3, true, 0},
} }
func TestScanBase(t *testing.T) { func TestScanBase(t *testing.T) {
for _, a := range natScanTests { for _, a := range natScanTests {
r := strings.NewReader(a.s) r := strings.NewReader(a.s)
x, b, err := nat(nil).scan(r, a.base) x, b, count, err := nat(nil).scan(r, a.base)
if err == nil && !a.ok { if err == nil && !a.ok {
t.Errorf("scan%+v\n\texpected error", a) t.Errorf("scan%+v\n\texpected error", a)
} }
@ -346,6 +361,9 @@ func TestScanBase(t *testing.T) {
if b != a.b { if b != a.b {
t.Errorf("scan%+v\n\tgot b = %d; want %d", a, b, a.base) t.Errorf("scan%+v\n\tgot b = %d; want %d", a, b, a.base)
} }
if count != a.count {
t.Errorf("scan%+v\n\tgot count = %d; want %d", a, count, a.count)
}
next, _, err := r.ReadRune() next, _, err := r.ReadRune()
if err == io.EOF { if err == io.EOF {
next = 0 next = 0
@ -413,7 +431,7 @@ var pi = "3" +
// Test case for BenchmarkScanPi. // Test case for BenchmarkScanPi.
func TestScanPi(t *testing.T) { func TestScanPi(t *testing.T) {
var x nat var x nat
z, _, err := x.scan(strings.NewReader(pi), 10) z, _, _, err := x.scan(strings.NewReader(pi), 10)
if err != nil { if err != nil {
t.Errorf("scanning pi: %s", err) t.Errorf("scanning pi: %s", err)
} }
@ -445,7 +463,7 @@ func BenchmarkScanPi(b *testing.B) {
func BenchmarkStringPiParallel(b *testing.B) { func BenchmarkStringPiParallel(b *testing.B) {
var x nat var x nat
x, _, _ = x.scan(strings.NewReader(pi), 0) x, _, _, _ = x.scan(strings.NewReader(pi), 0)
if x.decimalString() != pi { if x.decimalString() != pi {
panic("benchmark incorrect: conversion failed") panic("benchmark incorrect: conversion failed")
} }
@ -757,14 +775,13 @@ 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(strings.NewReader(test.x), 0) x := natFromString(test.x)
y, _, _ := nat(nil).scan(strings.NewReader(test.y), 0) y := natFromString(test.y)
out, _, _ := nat(nil).scan(strings.NewReader(test.out), 0) out := natFromString(test.out)
var m nat var m nat
if len(test.m) > 0 { if len(test.m) > 0 {
m, _, _ = nat(nil).scan(strings.NewReader(test.m), 0) m = natFromString(test.m)
} }
z := nat(nil).expNN(x, y, m) z := nat(nil).expNN(x, y, m)
@ -843,3 +860,37 @@ func BenchmarkFibo(b *testing.B) {
fibo(1e5) fibo(1e5)
} }
} }
var bitTests = []struct {
x string
i uint
want uint
}{
{"0", 0, 0},
{"0", 1, 0},
{"0", 1000, 0},
{"0x1", 0, 1},
{"0x10", 0, 0},
{"0x10", 3, 0},
{"0x10", 4, 1},
{"0x10", 5, 0},
{"0x8000000000000000", 62, 0},
{"0x8000000000000000", 63, 1},
{"0x8000000000000000", 64, 0},
{"0x3" + strings.Repeat("0", 32), 127, 0},
{"0x3" + strings.Repeat("0", 32), 128, 1},
{"0x3" + strings.Repeat("0", 32), 129, 1},
{"0x3" + strings.Repeat("0", 32), 130, 0},
}
func TestBit(t *testing.T) {
for i, test := range bitTests {
x := natFromString(test.x)
if got := x.bit(test.i); got != test.want {
t.Errorf("#%d: %s.bit(%d) = %v; want %v", i, test.x, test.i, got, test.want)
}
}
}

View File

@ -549,7 +549,7 @@ func (z *Rat) SetString(s string) (*Rat, bool) {
} }
s = s[sep+1:] s = s[sep+1:]
var err error var err error
if z.b.abs, _, err = z.b.abs.scan(strings.NewReader(s), 10); err != nil { if z.b.abs, _, _, err = z.b.abs.scan(strings.NewReader(s), 10); err != nil {
return nil, false return nil, false
} }
if len(z.b.abs) == 0 { if len(z.b.abs) == 0 {