diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 853605f5c29..10b27cfe927 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -69,6 +69,7 @@ var depsRules = ` internal/goarch < internal/abi; internal/byteorder, internal/cpu, internal/goarch < internal/chacha8rand; + internal/goarch, math/bits < internal/strconv; # RUNTIME is the core runtime group of packages, all of them very light-weight. internal/abi, @@ -80,6 +81,7 @@ var depsRules = ` internal/goexperiment, internal/goos, internal/profilerecord, + internal/strconv, internal/trace/tracev2, math/bits, structs diff --git a/src/strconv/atob.go b/src/internal/strconv/atob.go similarity index 95% rename from src/strconv/atob.go rename to src/internal/strconv/atob.go index 0a495008d77..cbeba7f8bc9 100644 --- a/src/strconv/atob.go +++ b/src/internal/strconv/atob.go @@ -14,7 +14,7 @@ func ParseBool(str string) (bool, error) { case "0", "f", "F", "false", "FALSE", "False": return false, nil } - return false, syntaxError("ParseBool", str) + return false, ErrSyntax } // FormatBool returns "true" or "false" according to the value of b. diff --git a/src/strconv/atob_test.go b/src/internal/strconv/atob_test.go similarity index 72% rename from src/strconv/atob_test.go rename to src/internal/strconv/atob_test.go index 40d43a9f8f9..61f543df308 100644 --- a/src/strconv/atob_test.go +++ b/src/internal/strconv/atob_test.go @@ -6,7 +6,7 @@ package strconv_test import ( "bytes" - . "strconv" + . "internal/strconv" "testing" ) @@ -36,23 +36,8 @@ var atobtests = []atobTest{ func TestParseBool(t *testing.T) { for _, test := range atobtests { b, e := ParseBool(test.in) - if test.err != nil { - // expect an error - if e == nil { - t.Errorf("ParseBool(%s) = nil; want %s", test.in, test.err) - } else { - // NumError assertion must succeed; it's the only thing we return. - if e.(*NumError).Err != test.err { - t.Errorf("ParseBool(%s) = %s; want %s", test.in, e, test.err) - } - } - } else { - if e != nil { - t.Errorf("ParseBool(%s) = %s; want nil", test.in, e) - } - if b != test.out { - t.Errorf("ParseBool(%s) = %t; want %t", test.in, b, test.out) - } + if b != test.out || e != test.err { + t.Errorf("ParseBool(%s) = %v, %v, want %v, %v", test.in, b, e, test.out, test.err) } } } diff --git a/src/strconv/atoc.go b/src/internal/strconv/atoc.go similarity index 80% rename from src/strconv/atoc.go rename to src/internal/strconv/atoc.go index 560bd7920df..52f2fc82af3 100644 --- a/src/strconv/atoc.go +++ b/src/internal/strconv/atoc.go @@ -4,23 +4,6 @@ package strconv -import "internal/stringslite" - -const fnParseComplex = "ParseComplex" - -// convErr splits an error returned by parseFloatPrefix -// into a syntax or range error for ParseComplex. -func convErr(err error, s string) (syntax, range_ error) { - if x, ok := err.(*NumError); ok { - x.Func = fnParseComplex - x.Num = stringslite.Clone(s) - if x.Err == ErrRange { - return nil, x - } - } - return err, nil -} - // ParseComplex converts the string s to a complex number // with the precision specified by bitSize: 64 for complex64, or 128 for complex128. // When bitSize=64, the result still has type complex128, but it will be @@ -47,8 +30,6 @@ func ParseComplex(s string, bitSize int) (complex128, error) { size = 32 // complex64 uses float32 parts } - orig := s - // Remove parentheses, if any. if len(s) >= 2 && s[0] == '(' && s[len(s)-1] == ')' { s = s[1 : len(s)-1] @@ -59,10 +40,10 @@ func ParseComplex(s string, bitSize int) (complex128, error) { // Read real part (possibly imaginary part if followed by 'i'). re, n, err := parseFloatPrefix(s, size) if err != nil { - err, pending = convErr(err, orig) - if err != nil { + if err != ErrRange { return 0, err } + pending = err } s = s[n:] @@ -88,20 +69,20 @@ func ParseComplex(s string, bitSize int) (complex128, error) { } fallthrough default: - return 0, syntaxError(fnParseComplex, orig) + return 0, ErrSyntax } // Read imaginary part. im, n, err := parseFloatPrefix(s, size) if err != nil { - err, pending = convErr(err, orig) - if err != nil { + if err != ErrRange { return 0, err } + pending = err } s = s[n:] if s != "i" { - return 0, syntaxError(fnParseComplex, orig) + return 0, ErrSyntax } return complex(re, im), pending } diff --git a/src/strconv/atoc_test.go b/src/internal/strconv/atoc_test.go similarity index 88% rename from src/strconv/atoc_test.go rename to src/internal/strconv/atoc_test.go index 4c1aad09000..0a7741a4ccf 100644 --- a/src/strconv/atoc_test.go +++ b/src/internal/strconv/atoc_test.go @@ -7,8 +7,7 @@ package strconv_test import ( "math" "math/cmplx" - "reflect" - . "strconv" + . "internal/strconv" "testing" ) @@ -188,30 +187,24 @@ func TestParseComplex(t *testing.T) { } for i := range tests { test := &tests[i] - if test.err != nil { - test.err = &NumError{Func: "ParseComplex", Num: test.in, Err: test.err} + c, e := ParseComplex(test.in, 128) + if !sameComplex(c, test.out) || e != test.err { + t.Errorf("ParseComplex(%s, 128) = %v, %v, want %v, %v", test.in, c, e, test.out, test.err) } - got, err := ParseComplex(test.in, 128) - if !reflect.DeepEqual(err, test.err) { - t.Fatalf("ParseComplex(%q, 128) = %v, %v; want %v, %v", test.in, got, err, test.out, test.err) - } - if !(cmplx.IsNaN(test.out) && cmplx.IsNaN(got)) && got != test.out { - t.Fatalf("ParseComplex(%q, 128) = %v, %v; want %v, %v", test.in, got, err, test.out, test.err) - } - if complex128(complex64(test.out)) == test.out { - got, err := ParseComplex(test.in, 64) - if !reflect.DeepEqual(err, test.err) { - t.Fatalf("ParseComplex(%q, 64) = %v, %v; want %v, %v", test.in, got, err, test.out, test.err) - } - got64 := complex64(got) - if complex128(got64) != test.out { - t.Fatalf("ParseComplex(%q, 64) = %v, %v; want %v, %v", test.in, got, err, test.out, test.err) + c, e := ParseComplex(test.in, 64) + c64 := complex64(c) + if !sameComplex(complex128(c64) , test.out) || e != test.err { + t.Errorf("ParseComplex(%s, 64) = %v, %v, want %v, %v", test.in, c, e, test.out, test.err) } } } } +func sameComplex(c1, c2 complex128) bool { + return cmplx.IsNaN(c1) && cmplx.IsNaN(c2) || c1 == c2 +} + // Issue 42297: allow ParseComplex(s, not_32_or_64) for legacy reasons func TestParseComplexIncorrectBitSize(t *testing.T) { const s = "1.5e308+1.0e307i" diff --git a/src/strconv/atof.go b/src/internal/strconv/atof.go similarity index 96% rename from src/strconv/atof.go rename to src/internal/strconv/atof.go index c05d2982571..ada45dc0aa9 100644 --- a/src/strconv/atof.go +++ b/src/internal/strconv/atof.go @@ -10,8 +10,6 @@ package strconv // 2) Multiply/divide decimal by powers of two until in range [0.5, 1) // 3) Multiply by 2^precision and round to get mantissa. -import "math" - var optimize = true // set to false to force slow-path conversions for testing // commonPrefixLenIgnoreCase returns the length of the common @@ -58,11 +56,11 @@ func special(s string) (f float64, n int, ok bool) { n = 3 } if n == 3 || n == 8 { - return math.Inf(sign), nsign + n, true + return inf(sign), nsign + n, true } case 'n', 'N': if commonPrefixLenIgnoreCase(s, "nan") == 3 { - return math.NaN(), 3, true + return nan(), 3, true } } return 0, 0, false @@ -546,7 +544,7 @@ func atofHex(s string, flt *floatInfo, mantissa uint64, exp int, neg, trunc bool if exp > maxExp { // infinity and range error mantissa = 1 << flt.mantbits exp = maxExp + 1 - err = rangeError(fnParseFloat, s) + err = ErrRange } bits := mantissa & (1<", "(", ")", "i", "init"} { in := test.in + suffix - _, n, err := ParseFloatPrefix(in, 64) + _, n, err := parseFloatPrefix(in, 64) if err != nil { t.Errorf("ParseFloatPrefix(%q, 64): err = %v; want no error", in, err) } diff --git a/src/strconv/eisel_lemire.go b/src/internal/strconv/atofeisel.go similarity index 95% rename from src/strconv/eisel_lemire.go rename to src/internal/strconv/atofeisel.go index 7bc9c71a405..10b8c96bba9 100644 --- a/src/strconv/eisel_lemire.go +++ b/src/internal/strconv/atofeisel.go @@ -18,7 +18,6 @@ package strconv // https://github.com/nigeltao/parse-number-fxx-test-data/blob/5280dcfccf6d0b02a65ae282dad0b6d9de50e039/script/test-go-strconv.go import ( - "math" "math/bits" ) @@ -29,7 +28,7 @@ func eiselLemire64(man uint64, exp10 int, neg bool) (f float64, ok bool) { // Exp10 Range. if man == 0 { if neg { - f = math.Float64frombits(0x8000000000000000) // Negative zero. + f = float64frombits(0x8000000000000000) // Negative zero. } return f, true } @@ -88,7 +87,7 @@ func eiselLemire64(man uint64, exp10 int, neg bool) (f float64, ok bool) { if neg { retBits |= 0x8000000000000000 } - return math.Float64frombits(retBits), true + return float64frombits(retBits), true } func eiselLemire32(man uint64, exp10 int, neg bool) (f float32, ok bool) { @@ -104,7 +103,7 @@ func eiselLemire32(man uint64, exp10 int, neg bool) (f float32, ok bool) { // Exp10 Range. if man == 0 { if neg { - f = math.Float32frombits(0x80000000) // Negative zero. + f = float32frombits(0x80000000) // Negative zero. } return f, true } @@ -163,5 +162,5 @@ func eiselLemire32(man uint64, exp10 int, neg bool) (f float32, ok bool) { if neg { retBits |= 0x80000000 } - return math.Float32frombits(uint32(retBits)), true + return float32frombits(uint32(retBits)), true } diff --git a/src/strconv/atoi.go b/src/internal/strconv/atoi.go similarity index 69% rename from src/strconv/atoi.go rename to src/internal/strconv/atoi.go index 83e931fe241..5bc259e7e55 100644 --- a/src/strconv/atoi.go +++ b/src/internal/strconv/atoi.go @@ -4,11 +4,6 @@ package strconv -import ( - "errors" - "internal/stringslite" -) - // lower(c) is a lower-case letter if and only if // c is either that lower-case letter or the equivalent upper-case letter. // Instead of writing c == 'x' || c == 'X' one can write lower(c) == 'x'. @@ -17,47 +12,28 @@ func lower(c byte) byte { return c | ('x' - 'X') } -// ErrRange indicates that a value is out of range for the target type. -var ErrRange = errors.New("value out of range") +type Error int -// ErrSyntax indicates that a value does not have the right syntax for the target type. -var ErrSyntax = errors.New("invalid syntax") +const ( + _ Error = iota + ErrRange + ErrSyntax + ErrBase + ErrBitSize +) -// A NumError records a failed conversion. -type NumError struct { - Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat, ParseComplex) - Num string // the input - Err error // the reason the conversion failed (e.g. ErrRange, ErrSyntax, etc.) -} - -func (e *NumError) Error() string { - return "strconv." + e.Func + ": " + "parsing " + Quote(e.Num) + ": " + e.Err.Error() -} - -func (e *NumError) Unwrap() error { return e.Err } - -// All ParseXXX functions allow the input string to escape to the error value. -// This hurts strconv.ParseXXX(string(b)) calls where b is []byte since -// the conversion from []byte must allocate a string on the heap. -// If we assume errors are infrequent, then we can avoid escaping the input -// back to the output by copying it first. This allows the compiler to call -// strconv.ParseXXX without a heap allocation for most []byte to string -// conversions, since it can now prove that the string cannot escape Parse. - -func syntaxError(fn, str string) *NumError { - return &NumError{fn, stringslite.Clone(str), ErrSyntax} -} - -func rangeError(fn, str string) *NumError { - return &NumError{fn, stringslite.Clone(str), ErrRange} -} - -func baseError(fn, str string, base int) *NumError { - return &NumError{fn, stringslite.Clone(str), errors.New("invalid base " + Itoa(base))} -} - -func bitSizeError(fn, str string, bitSize int) *NumError { - return &NumError{fn, stringslite.Clone(str), errors.New("invalid bit size " + Itoa(bitSize))} +func (e Error) Error() string { + switch e { + case ErrRange: + return "value out of range" + case ErrSyntax: + return "invalid syntax" + case ErrBase: + return "invalid base" + case ErrBitSize: + return "invalid bit size" + } + return "unknown error" } const intSize = 32 << (^uint(0) >> 63) @@ -74,7 +50,7 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) { const fnParseUint = "ParseUint" if s == "" { - return 0, syntaxError(fnParseUint, s) + return 0, ErrSyntax } base0 := base == 0 @@ -105,13 +81,13 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) { } default: - return 0, baseError(fnParseUint, s0, base) + return 0, ErrBase } if bitSize == 0 { bitSize = IntSize } else if bitSize < 0 || bitSize > 64 { - return 0, bitSizeError(fnParseUint, s0, bitSize) + return 0, ErrBitSize } // Cutoff is the smallest number such that cutoff*base > maxUint64. @@ -141,29 +117,29 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) { case 'a' <= lower(c) && lower(c) <= 'z': d = lower(c) - 'a' + 10 default: - return 0, syntaxError(fnParseUint, s0) + return 0, ErrSyntax } if d >= byte(base) { - return 0, syntaxError(fnParseUint, s0) + return 0, ErrSyntax } if n >= cutoff { // n*base overflows - return maxVal, rangeError(fnParseUint, s0) + return maxVal, ErrRange } n *= uint64(base) n1 := n + uint64(d) if n1 < n || n1 > maxVal { // n+d overflows - return maxVal, rangeError(fnParseUint, s0) + return maxVal, ErrRange } n = n1 } if underscores && !underscoreOK(s0) { - return 0, syntaxError(fnParseUint, s0) + return 0, ErrSyntax } return n, nil @@ -198,11 +174,10 @@ func ParseInt(s string, base int, bitSize int) (i int64, err error) { const fnParseInt = "ParseInt" if s == "" { - return 0, syntaxError(fnParseInt, s) + return 0, ErrSyntax } // Pick off leading sign. - s0 := s neg := false switch s[0] { case '+': @@ -215,9 +190,7 @@ func ParseInt(s string, base int, bitSize int) (i int64, err error) { // Convert unsigned and check range. var un uint64 un, err = ParseUint(s, base, bitSize) - if err != nil && err.(*NumError).Err != ErrRange { - err.(*NumError).Func = fnParseInt - err.(*NumError).Num = stringslite.Clone(s0) + if err != nil && err != ErrRange { return 0, err } @@ -227,10 +200,10 @@ func ParseInt(s string, base int, bitSize int) (i int64, err error) { cutoff := uint64(1 << uint(bitSize-1)) if !neg && un >= cutoff { - return int64(cutoff - 1), rangeError(fnParseInt, s0) + return int64(cutoff - 1), ErrRange } if neg && un > cutoff { - return -int64(cutoff), rangeError(fnParseInt, s0) + return -int64(cutoff), ErrRange } n := int64(un) if neg { @@ -251,7 +224,7 @@ func Atoi(s string) (int, error) { if s[0] == '-' || s[0] == '+' { s = s[1:] if len(s) < 1 { - return 0, syntaxError(fnAtoi, s0) + return 0, ErrSyntax } } @@ -259,7 +232,7 @@ func Atoi(s string) (int, error) { for _, ch := range []byte(s) { ch -= '0' if ch > 9 { - return 0, syntaxError(fnAtoi, s0) + return 0, ErrSyntax } n = n*10 + int(ch) } @@ -271,9 +244,6 @@ func Atoi(s string) (int, error) { // Slow path for invalid, big, or underscored integers. i64, err := ParseInt(s, 10, 0) - if nerr, ok := err.(*NumError); ok { - nerr.Func = fnAtoi - } return int(i64), err } diff --git a/src/strconv/atoi_test.go b/src/internal/strconv/atoi_test.go similarity index 82% rename from src/strconv/atoi_test.go rename to src/internal/strconv/atoi_test.go index ada175200c7..e8f123ea5e5 100644 --- a/src/strconv/atoi_test.go +++ b/src/internal/strconv/atoi_test.go @@ -5,10 +5,9 @@ package strconv_test import ( - "errors" "fmt" "reflect" - . "strconv" + . "internal/strconv" "testing" ) @@ -316,47 +315,6 @@ var numErrorTests = []numErrorTest{ {"1\x00.2", `strconv.ParseFloat: parsing "1\x00.2": failed`}, } -func init() { - // The parse routines return NumErrors wrapping - // the error and the string. Convert the tables above. - for i := range parseUint64Tests { - test := &parseUint64Tests[i] - if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} - } - } - for i := range parseUint64BaseTests { - test := &parseUint64BaseTests[i] - if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} - } - } - for i := range parseInt64Tests { - test := &parseInt64Tests[i] - if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} - } - } - for i := range parseInt64BaseTests { - test := &parseInt64BaseTests[i] - if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} - } - } - for i := range parseUint32Tests { - test := &parseUint32Tests[i] - if test.err != nil { - test.err = &NumError{"ParseUint", test.in, test.err} - } - } - for i := range parseInt32Tests { - test := &parseInt32Tests[i] - if test.err != nil { - test.err = &NumError{"ParseInt", test.in, test.err} - } - } -} - func TestParseUint32(t *testing.T) { for i := range parseUint32Tests { test := &parseUint32Tests[i] @@ -475,62 +433,40 @@ func TestAtoi(t *testing.T) { for i := range parseInt32Tests { test := &parseInt32Tests[i] out, err := Atoi(test.in) - var testErr error - if test.err != nil { - testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err} - } - if int(test.out) != out || !reflect.DeepEqual(testErr, err) { - t.Errorf("Atoi(%q) = %v, %v want %v, %v", - test.in, out, err, test.out, testErr) + if out !=int(test.out) || err != test.err { + t.Errorf("Atoi(%q) = %v, %v, want %v, %v", test.in, out, err, test.out, test.err) } } case 64: for i := range parseInt64Tests { test := &parseInt64Tests[i] out, err := Atoi(test.in) - var testErr error - if test.err != nil { - testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err} - } - if test.out != int64(out) || !reflect.DeepEqual(testErr, err) { - t.Errorf("Atoi(%q) = %v, %v want %v, %v", - test.in, out, err, test.out, testErr) + if int64(out) != test.out || err != test.err { + t.Errorf("Atoi(%q) = %v, %v, want %v, %v", test.in, out, err, test.out, test.err) } } } } -func bitSizeErrStub(name string, bitSize int) error { - return bitSizeError(name, "0", bitSize) -} - -func baseErrStub(name string, base int) error { - return baseError(name, "0", base) -} - -func noErrStub(name string, arg int) error { - return nil -} - type parseErrorTest struct { arg int - errStub func(name string, arg int) error + err error } var parseBitSizeTests = []parseErrorTest{ - {-1, bitSizeErrStub}, - {0, noErrStub}, - {64, noErrStub}, - {65, bitSizeErrStub}, + {-1, ErrBitSize}, + {0, nil}, + {64, nil}, + {65, ErrBitSize}, } var parseBaseTests = []parseErrorTest{ - {-1, baseErrStub}, - {0, noErrStub}, - {1, baseErrStub}, - {2, noErrStub}, - {36, noErrStub}, - {37, baseErrStub}, + {-1, ErrBase}, + {0, nil}, + {1, ErrBase}, + {2, nil}, + {36, nil}, + {37, ErrBase}, } func equalError(a, b error) bool { @@ -546,11 +482,10 @@ func equalError(a, b error) bool { func TestParseIntBitSize(t *testing.T) { for i := range parseBitSizeTests { test := &parseBitSizeTests[i] - testErr := test.errStub("ParseInt", test.arg) _, err := ParseInt("0", 0, test.arg) - if !equalError(testErr, err) { + if err != test.err { t.Errorf("ParseInt(\"0\", 0, %v) = 0, %v want 0, %v", - test.arg, err, testErr) + test.arg, err, test.err) } } } @@ -558,11 +493,10 @@ func TestParseIntBitSize(t *testing.T) { func TestParseUintBitSize(t *testing.T) { for i := range parseBitSizeTests { test := &parseBitSizeTests[i] - testErr := test.errStub("ParseUint", test.arg) _, err := ParseUint("0", 0, test.arg) - if !equalError(testErr, err) { + if err != test.err { t.Errorf("ParseUint(\"0\", 0, %v) = 0, %v want 0, %v", - test.arg, err, testErr) + test.arg, err, test.err) } } } @@ -570,11 +504,10 @@ func TestParseUintBitSize(t *testing.T) { func TestParseIntBase(t *testing.T) { for i := range parseBaseTests { test := &parseBaseTests[i] - testErr := test.errStub("ParseInt", test.arg) _, err := ParseInt("0", test.arg, 0) - if !equalError(testErr, err) { + if err != test.err { t.Errorf("ParseInt(\"0\", %v, 0) = 0, %v want 0, %v", - test.arg, err, testErr) + test.arg, err, test.err) } } } @@ -582,35 +515,14 @@ func TestParseIntBase(t *testing.T) { func TestParseUintBase(t *testing.T) { for i := range parseBaseTests { test := &parseBaseTests[i] - testErr := test.errStub("ParseUint", test.arg) _, err := ParseUint("0", test.arg, 0) - if !equalError(testErr, err) { + if err != test.err { t.Errorf("ParseUint(\"0\", %v, 0) = 0, %v want 0, %v", - test.arg, err, testErr) + test.arg, err, test.err) } } } -func TestNumError(t *testing.T) { - for _, test := range numErrorTests { - err := &NumError{ - Func: "ParseFloat", - Num: test.num, - Err: errors.New("failed"), - } - if got := err.Error(); got != test.want { - t.Errorf(`(&NumError{"ParseFloat", %q, "failed"}).Error() = %v, want %v`, test.num, got, test.want) - } - } -} - -func TestNumErrorUnwrap(t *testing.T) { - err := &NumError{Err: ErrSyntax} - if !errors.Is(err, ErrSyntax) { - t.Error("errors.Is failed, wanted success") - } -} - func BenchmarkParseInt(b *testing.B) { b.Run("Pos", func(b *testing.B) { benchmarkParseInt(b, 1) diff --git a/src/strconv/ctoa.go b/src/internal/strconv/ctoa.go similarity index 100% rename from src/strconv/ctoa.go rename to src/internal/strconv/ctoa.go diff --git a/src/strconv/ctoa_test.go b/src/internal/strconv/ctoa_test.go similarity index 98% rename from src/strconv/ctoa_test.go rename to src/internal/strconv/ctoa_test.go index 8b77898eccc..c24f30272a2 100644 --- a/src/strconv/ctoa_test.go +++ b/src/internal/strconv/ctoa_test.go @@ -5,7 +5,7 @@ package strconv_test import ( - . "strconv" + . "internal/strconv" "testing" ) diff --git a/src/strconv/decimal.go b/src/internal/strconv/decimal.go similarity index 100% rename from src/strconv/decimal.go rename to src/internal/strconv/decimal.go diff --git a/src/strconv/decimal_test.go b/src/internal/strconv/decimal_test.go similarity index 99% rename from src/strconv/decimal_test.go rename to src/internal/strconv/decimal_test.go index 13a127f5b2c..a60096e1c8e 100644 --- a/src/strconv/decimal_test.go +++ b/src/internal/strconv/decimal_test.go @@ -5,7 +5,7 @@ package strconv_test import ( - . "strconv" + . "internal/strconv" "testing" ) diff --git a/src/internal/strconv/deps.go b/src/internal/strconv/deps.go new file mode 100644 index 00000000000..04331130c21 --- /dev/null +++ b/src/internal/strconv/deps.go @@ -0,0 +1,30 @@ +// Copyright 2025 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 "unsafe" + +// Implementations to avoid importing other dependencies. + +// package math + +func float64frombits(b uint64) float64 { return *(*float64)(unsafe.Pointer(&b)) } +func float32frombits(b uint32) float32 { return *(*float32)(unsafe.Pointer(&b)) } +func float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) } +func float32bits(f float32) uint32 { return *(*uint32)(unsafe.Pointer(&f)) } + +func inf(sign int) float64 { + var v uint64 + if sign >= 0 { + v = 0x7FF0000000000000 + } else { + v = 0xFFF0000000000000 + } + return float64frombits(v) +} + +func isNaN(f float64) (is bool) { return f != f } + +func nan() float64 { return float64frombits(0x7FF8000000000001) } diff --git a/src/internal/strconv/export_test.go b/src/internal/strconv/export_test.go new file mode 100644 index 00000000000..bea741e6fbe --- /dev/null +++ b/src/internal/strconv/export_test.go @@ -0,0 +1,28 @@ +// Copyright 2017 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 + +type Uint128 = uint128 + +var ( + MulLog10_2 = mulLog10_2 + MulLog2_10 = mulLog2_10 + ParseFloatPrefix = parseFloatPrefix + Pow10 = pow10 + Umul128 = umul128 + Umul192 = umul192 +) + +func NewDecimal(i uint64) *decimal { + d := new(decimal) + d.Assign(i) + return d +} + +func SetOptimize(b bool) bool { + old := optimize + optimize = b + return old +} diff --git a/src/strconv/fp_test.go b/src/internal/strconv/fp_test.go similarity index 99% rename from src/strconv/fp_test.go rename to src/internal/strconv/fp_test.go index 376e8f591c6..042328c7d4e 100644 --- a/src/strconv/fp_test.go +++ b/src/internal/strconv/fp_test.go @@ -8,7 +8,7 @@ import ( "bufio" _ "embed" "fmt" - "strconv" + "internal/strconv" "strings" "testing" ) diff --git a/src/strconv/ftoa.go b/src/internal/strconv/ftoa.go similarity index 99% rename from src/strconv/ftoa.go rename to src/internal/strconv/ftoa.go index 1d4bf770be0..1aec5447ece 100644 --- a/src/strconv/ftoa.go +++ b/src/internal/strconv/ftoa.go @@ -10,7 +10,10 @@ package strconv -import "math" +const ( + lowerhex = "0123456789abcdef" + upperhex = "0123456789ABCDEF" +) type floatInfo struct { mantbits uint @@ -71,10 +74,10 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte { var flt *floatInfo switch bitSize { case 32: - bits = uint64(math.Float32bits(float32(val))) + bits = uint64(float32bits(float32(val))) flt = &float32info case 64: - bits = math.Float64bits(val) + bits = float64bits(val) flt = &float64info default: panic("strconv: illegal AppendFloat/FormatFloat bitSize") diff --git a/src/strconv/ftoa_test.go b/src/internal/strconv/ftoa_test.go similarity index 99% rename from src/strconv/ftoa_test.go rename to src/internal/strconv/ftoa_test.go index 40faa433a64..14d1200ff26 100644 --- a/src/strconv/ftoa_test.go +++ b/src/internal/strconv/ftoa_test.go @@ -7,7 +7,7 @@ package strconv_test import ( "math" "math/rand" - . "strconv" + . "internal/strconv" "testing" ) diff --git a/src/strconv/ftoaryu.go b/src/internal/strconv/ftoaryu.go similarity index 100% rename from src/strconv/ftoaryu.go rename to src/internal/strconv/ftoaryu.go diff --git a/src/internal/strconv/import_test.go b/src/internal/strconv/import_test.go new file mode 100644 index 00000000000..0cbc451651a --- /dev/null +++ b/src/internal/strconv/import_test.go @@ -0,0 +1,18 @@ +// Copyright 2025 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_test + +import . "internal/strconv" + +type uint128 = Uint128 + +var ( + mulLog10_2 = MulLog10_2 + mulLog2_10 = MulLog2_10 + parseFloatPrefix = ParseFloatPrefix + pow10 = Pow10 + umul128 = Umul128 + umul192 = Umul192 +) diff --git a/src/strconv/itoa.go b/src/internal/strconv/itoa.go similarity index 98% rename from src/strconv/itoa.go rename to src/internal/strconv/itoa.go index 7884e8e9878..d4ca478eb0a 100644 --- a/src/strconv/itoa.go +++ b/src/internal/strconv/itoa.go @@ -5,8 +5,8 @@ package strconv import ( + "internal/goarch" "math/bits" - "runtime" ) // FormatUint returns the string representation of i in the given base, @@ -200,7 +200,7 @@ func formatBase10(a []byte, u uint64) int { // or can split the uint64 into uint32-sized chunks. // On most systems, the uint32 math is faster, but not all. // The decision here is based on benchmarking. - itoaPure64 = host64bit && runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" && runtime.GOARCH != "s390x" + itoaPure64 = host64bit && goarch.GOARCH != "amd64" && goarch.GOARCH != "arm64" && goarch.GOARCH != "s390x" // 64-bit systems can all use 64-bit div and mod by a constant, // which the compiler rewrites to use 64x64→128-bit multiplies. diff --git a/src/strconv/itoa_test.go b/src/internal/strconv/itoa_test.go similarity index 99% rename from src/strconv/itoa_test.go rename to src/internal/strconv/itoa_test.go index 9dbf812d4be..1629e45d48c 100644 --- a/src/strconv/itoa_test.go +++ b/src/internal/strconv/itoa_test.go @@ -6,7 +6,7 @@ package strconv_test import ( "fmt" - . "strconv" + . "internal/strconv" "testing" ) diff --git a/src/strconv/math.go b/src/internal/strconv/math.go similarity index 100% rename from src/strconv/math.go rename to src/internal/strconv/math.go diff --git a/src/strconv/math_test.go b/src/internal/strconv/math_test.go similarity index 98% rename from src/strconv/math_test.go rename to src/internal/strconv/math_test.go index 19b7ea6c751..d4f881b5e74 100644 --- a/src/strconv/math_test.go +++ b/src/internal/strconv/math_test.go @@ -6,7 +6,7 @@ package strconv_test import ( "math" - . "strconv" + . "internal/strconv" "testing" ) diff --git a/src/strconv/pow10gen.go b/src/internal/strconv/pow10gen.go similarity index 100% rename from src/strconv/pow10gen.go rename to src/internal/strconv/pow10gen.go diff --git a/src/strconv/pow10tab.go b/src/internal/strconv/pow10tab.go similarity index 100% rename from src/strconv/pow10tab.go rename to src/internal/strconv/pow10tab.go diff --git a/src/strconv/testdata/testfp.txt b/src/internal/strconv/testdata/testfp.txt similarity index 100% rename from src/strconv/testdata/testfp.txt rename to src/internal/strconv/testdata/testfp.txt diff --git a/src/strconv/export_test.go b/src/strconv/export_test.go index 3518bb233ce..7a3c761e683 100644 --- a/src/strconv/export_test.go +++ b/src/strconv/export_test.go @@ -4,27 +4,7 @@ package strconv -type Uint128 = uint128 - var ( - BaseError = baseError - BitSizeError = bitSizeError - MulLog10_2 = mulLog10_2 - MulLog2_10 = mulLog2_10 - ParseFloatPrefix = parseFloatPrefix - Pow10 = pow10 - Umul128 = umul128 - Umul192 = umul192 + BaseError = baseError + BitSizeError = bitSizeError ) - -func NewDecimal(i uint64) *decimal { - d := new(decimal) - d.Assign(i) - return d -} - -func SetOptimize(b bool) bool { - old := optimize - optimize = b - return old -} diff --git a/src/strconv/import_test.go b/src/strconv/import_test.go index 05d957a6238..b44678bc7c4 100644 --- a/src/strconv/import_test.go +++ b/src/strconv/import_test.go @@ -6,15 +6,7 @@ package strconv_test import . "strconv" -type uint128 = Uint128 - var ( - baseError = BaseError - bitSizeError = BitSizeError - mulLog10_2 = MulLog10_2 - mulLog2_10 = MulLog2_10 - parseFloatPrefix = ParseFloatPrefix - pow10 = Pow10 - umul128 = Umul128 - umul192 = Umul192 + baseError = BaseError + bitSizeError = BitSizeError ) diff --git a/src/strconv/number.go b/src/strconv/number.go new file mode 100644 index 00000000000..3fa625c35ff --- /dev/null +++ b/src/strconv/number.go @@ -0,0 +1,286 @@ +// Copyright 2025 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 ( + "errors" + "internal/strconv" + "internal/stringslite" +) + +// IntSize is the size in bits of an int or uint value. +const IntSize = strconv.IntSize + +// ParseBool returns the boolean value represented by the string. +// It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. +// Any other value returns an error. +func ParseBool(str string) (bool, error) { + x, err := strconv.ParseBool(str) + if err != nil { + return x, toError("ParseBool", str, 0, 0, err) + } + return x, nil +} + +// FormatBool returns "true" or "false" according to the value of b. +func FormatBool(b bool) string { + return strconv.FormatBool(b) +} + +// AppendBool appends "true" or "false", according to the value of b, +// to dst and returns the extended buffer. +func AppendBool(dst []byte, b bool) []byte { + return strconv.AppendBool(dst, b) +} + +// ParseComplex converts the string s to a complex number +// with the precision specified by bitSize: 64 for complex64, or 128 for complex128. +// When bitSize=64, the result still has type complex128, but it will be +// convertible to complex64 without changing its value. +// +// The number represented by s must be of the form N, Ni, or N±Ni, where N stands +// for a floating-point number as recognized by [ParseFloat], and i is the imaginary +// component. If the second N is unsigned, a + sign is required between the two components +// as indicated by the ±. If the second N is NaN, only a + sign is accepted. +// The form may be parenthesized and cannot contain any spaces. +// The resulting complex number consists of the two components converted by ParseFloat. +// +// The errors that ParseComplex returns have concrete type [*NumError] +// and include err.Num = s. +// +// If s is not syntactically well-formed, ParseComplex returns err.Err = ErrSyntax. +// +// If s is syntactically well-formed but either component is more than 1/2 ULP +// away from the largest floating point number of the given component's size, +// ParseComplex returns err.Err = ErrRange and c = ±Inf for the respective component. +func ParseComplex(s string, bitSize int) (complex128, error) { + x, err := strconv.ParseComplex(s, bitSize) + if err != nil { + return x, toError("ParseComplex", s, 0, bitSize, err) + } + return x, nil +} + +// ParseFloat converts the string s to a floating-point number +// with the precision specified by bitSize: 32 for float32, or 64 for float64. +// When bitSize=32, the result still has type float64, but it will be +// convertible to float32 without changing its value. +// +// ParseFloat accepts decimal and hexadecimal floating-point numbers +// as defined by the Go syntax for [floating-point literals]. +// If s is well-formed and near a valid floating-point number, +// ParseFloat returns the nearest floating-point number rounded +// using IEEE754 unbiased rounding. +// (Parsing a hexadecimal floating-point value only rounds when +// there are more bits in the hexadecimal representation than +// will fit in the mantissa.) +// +// The errors that ParseFloat returns have concrete type *NumError +// and include err.Num = s. +// +// If s is not syntactically well-formed, ParseFloat returns err.Err = ErrSyntax. +// +// If s is syntactically well-formed but is more than 1/2 ULP +// away from the largest floating point number of the given size, +// ParseFloat returns f = ±Inf, err.Err = ErrRange. +// +// ParseFloat recognizes the string "NaN", and the (possibly signed) strings "Inf" and "Infinity" +// as their respective special floating point values. It ignores case when matching. +// +// [floating-point literals]: https://go.dev/ref/spec#Floating-point_literals +func ParseFloat(s string, bitSize int) (float64, error) { + x, err := strconv.ParseFloat(s, bitSize) + if err != nil { + return x, toError("ParseFloat", s, 0, bitSize, err) + } + return x, nil +} + +// ParseUint is like [ParseInt] but for unsigned numbers. +// +// A sign prefix is not permitted. +func ParseUint(s string, base int, bitSize int) (uint64, error) { + x, err := strconv.ParseUint(s, base, bitSize) + if err != nil { + return x, toError("ParseUint", s, base, bitSize, err) + } + return x, nil +} + +// ParseInt interprets a string s in the given base (0, 2 to 36) and +// bit size (0 to 64) and returns the corresponding value i. +// +// The string may begin with a leading sign: "+" or "-". +// +// If the base argument is 0, the true base is implied by the string's +// prefix following the sign (if present): 2 for "0b", 8 for "0" or "0o", +// 16 for "0x", and 10 otherwise. Also, for argument base 0 only, +// underscore characters are permitted as defined by the Go syntax for +// [integer literals]. +// +// The bitSize argument specifies the integer type +// that the result must fit into. Bit sizes 0, 8, 16, 32, and 64 +// correspond to int, int8, int16, int32, and int64. +// If bitSize is below 0 or above 64, an error is returned. +// +// The errors that ParseInt returns have concrete type [*NumError] +// and include err.Num = s. If s is empty or contains invalid +// digits, err.Err = [ErrSyntax] and the returned value is 0; +// if the value corresponding to s cannot be represented by a +// signed integer of the given size, err.Err = [ErrRange] and the +// returned value is the maximum magnitude integer of the +// appropriate bitSize and sign. +// +// [integer literals]: https://go.dev/ref/spec#Integer_literals +func ParseInt(s string, base int, bitSize int) (i int64, err error) { + x, err := strconv.ParseInt(s, base, bitSize) + if err != nil { + return x, toError("ParseInt", s, base, bitSize, err) + } + return x, nil +} + +// Atoi is equivalent to ParseInt(s, 10, 0), converted to type int. +func Atoi(s string) (int, error) { + x, err := strconv.Atoi(s) + if err != nil { + return x, toError("Atoi", s, 0, 0, err) + } + return strconv.Atoi(s) +} + +// FormatComplex converts the complex number c to a string of the +// form (a+bi) where a and b are the real and imaginary parts, +// formatted according to the format fmt and precision prec. +// +// The format fmt and precision prec have the same meaning as in [FormatFloat]. +// It rounds the result assuming that the original was obtained from a complex +// value of bitSize bits, which must be 64 for complex64 and 128 for complex128. +func FormatComplex(c complex128, fmt byte, prec, bitSize int) string { + return strconv.FormatComplex(c, fmt, prec, bitSize) +} + +// FormatFloat converts the floating-point number f to a string, +// according to the format fmt and precision prec. It rounds the +// result assuming that the original was obtained from a floating-point +// value of bitSize bits (32 for float32, 64 for float64). +// +// The format fmt is one of +// - 'b' (-ddddp±ddd, a binary exponent), +// - 'e' (-d.dddde±dd, a decimal exponent), +// - 'E' (-d.ddddE±dd, a decimal exponent), +// - 'f' (-ddd.dddd, no exponent), +// - 'g' ('e' for large exponents, 'f' otherwise), +// - 'G' ('E' for large exponents, 'f' otherwise), +// - 'x' (-0xd.ddddp±ddd, a hexadecimal fraction and binary exponent), or +// - 'X' (-0Xd.ddddP±ddd, a hexadecimal fraction and binary exponent). +// +// The precision prec controls the number of digits (excluding the exponent) +// printed by the 'e', 'E', 'f', 'g', 'G', 'x', and 'X' formats. +// For 'e', 'E', 'f', 'x', and 'X', it is the number of digits after the decimal point. +// For 'g' and 'G' it is the maximum number of significant digits (trailing +// zeros are removed). +// The special precision -1 uses the smallest number of digits +// necessary such that ParseFloat will return f exactly. +// The exponent is written as a decimal integer; +// for all formats other than 'b', it will be at least two digits. +func FormatFloat(f float64, fmt byte, prec, bitSize int) string { + return strconv.FormatFloat(f, fmt, prec, bitSize) +} + +// AppendFloat appends the string form of the floating-point number f, +// as generated by [FormatFloat], to dst and returns the extended buffer. +func AppendFloat(dst []byte, f float64, fmt byte, prec, bitSize int) []byte { + return strconv.AppendFloat(dst, f, fmt, prec, bitSize) +} + +// FormatUint returns the string representation of i in the given base, +// for 2 <= base <= 36. The result uses the lower-case letters 'a' to 'z' +// for digit values >= 10. +func FormatUint(i uint64, base int) string { + return strconv.FormatUint(i, base) +} + +// FormatInt returns the string representation of i in the given base, +// for 2 <= base <= 36. The result uses the lower-case letters 'a' to 'z' +// for digit values >= 10. +func FormatInt(i int64, base int) string { + return strconv.FormatInt(i, base) +} + +// Itoa is equivalent to [FormatInt](int64(i), 10). +func Itoa(i int) string { + return strconv.Itoa(i) +} + +// AppendInt appends the string form of the integer i, +// as generated by [FormatInt], to dst and returns the extended buffer. +func AppendInt(dst []byte, i int64, base int) []byte { + return strconv.AppendInt(dst, i, base) +} + +// AppendUint appends the string form of the unsigned integer i, +// as generated by [FormatUint], to dst and returns the extended buffer. +func AppendUint(dst []byte, i uint64, base int) []byte { + return strconv.AppendUint(dst, i, base) +} + +// toError converts from internal/strconv.Error to the error guaranteed by this package's APIs. +func toError(fn, s string, base, bitSize int, err error) error { + switch err { + case strconv.ErrSyntax: + return syntaxError(fn, s) + case strconv.ErrRange: + return rangeError(fn, s) + case strconv.ErrBase: + return baseError(fn, s, base) + case strconv.ErrBitSize: + return bitSizeError(fn, s, bitSize) + } + return err +} + +// ErrRange indicates that a value is out of range for the target type. +var ErrRange = errors.New("value out of range") + +// ErrSyntax indicates that a value does not have the right syntax for the target type. +var ErrSyntax = errors.New("invalid syntax") + +// A NumError records a failed conversion. +type NumError struct { + Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat, ParseComplex) + Num string // the input + Err error // the reason the conversion failed (e.g. ErrRange, ErrSyntax, etc.) +} + +func (e *NumError) Error() string { + return "strconv." + e.Func + ": " + "parsing " + Quote(e.Num) + ": " + e.Err.Error() +} + +func (e *NumError) Unwrap() error { return e.Err } + +// All ParseXXX functions allow the input string to escape to the error value. +// This hurts strconv.ParseXXX(string(b)) calls where b is []byte since +// the conversion from []byte must allocate a string on the heap. +// If we assume errors are infrequent, then we can avoid escaping the input +// back to the output by copying it first. This allows the compiler to call +// strconv.ParseXXX without a heap allocation for most []byte to string +// conversions, since it can now prove that the string cannot escape Parse. + +func syntaxError(fn, str string) *NumError { + return &NumError{fn, stringslite.Clone(str), ErrSyntax} +} + +func rangeError(fn, str string) *NumError { + return &NumError{fn, stringslite.Clone(str), ErrRange} +} + +func baseError(fn, str string, base int) *NumError { + return &NumError{fn, stringslite.Clone(str), errors.New("invalid base " + Itoa(base))} +} + +func bitSizeError(fn, str string, bitSize int) *NumError { + return &NumError{fn, stringslite.Clone(str), errors.New("invalid bit size " + Itoa(bitSize))} +} diff --git a/src/strconv/number_test.go b/src/strconv/number_test.go new file mode 100644 index 00000000000..4408bc05564 --- /dev/null +++ b/src/strconv/number_test.go @@ -0,0 +1,956 @@ +// 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. + +// Note: These tests are focused mainly on generating the right errors. +// The extensive numerical tests are in ../internal/strconv. +// Add new tests there instead of here whenever possible. + +package strconv_test + +import ( + "bytes" + "errors" + "math" + "math/cmplx" + "reflect" + . "strconv" + "testing" +) + +type atobTest struct { + in string + out bool + err error +} + +var atobtests = []atobTest{ + {"", false, ErrSyntax}, + {"asdf", false, ErrSyntax}, + {"0", false, nil}, + {"false", false, nil}, + {"true", true, nil}, +} + +func TestParseBool(t *testing.T) { + for _, test := range atobtests { + b, e := ParseBool(test.in) + if test.err != nil { + // expect an error + if e == nil { + t.Errorf("ParseBool(%s) = nil; want %s", test.in, test.err) + } else { + // NumError assertion must succeed; it's the only thing we return. + if e.(*NumError).Err != test.err { + t.Errorf("ParseBool(%s) = %s; want %s", test.in, e, test.err) + } + } + } else { + if e != nil { + t.Errorf("ParseBool(%s) = %s; want nil", test.in, e) + } + if b != test.out { + t.Errorf("ParseBool(%s) = %t; want %t", test.in, b, test.out) + } + } + } +} + +var boolString = map[bool]string{ + true: "true", + false: "false", +} + +func TestFormatBool(t *testing.T) { + for b, s := range boolString { + if f := FormatBool(b); f != s { + t.Errorf("FormatBool(%v) = %q; want %q", b, f, s) + } + } +} + +type appendBoolTest struct { + b bool + in []byte + out []byte +} + +var appendBoolTests = []appendBoolTest{ + {true, []byte("foo "), []byte("foo true")}, + {false, []byte("foo "), []byte("foo false")}, +} + +func TestAppendBool(t *testing.T) { + for _, test := range appendBoolTests { + b := AppendBool(test.in, test.b) + if !bytes.Equal(b, test.out) { + t.Errorf("AppendBool(%q, %v) = %q; want %q", test.in, test.b, b, test.out) + } + } +} + +var ( + infp0 = complex(math.Inf(+1), 0) + infm0 = complex(math.Inf(-1), 0) + inf0p = complex(0, math.Inf(+1)) + inf0m = complex(0, math.Inf(-1)) + + infpp = complex(math.Inf(+1), math.Inf(+1)) + infpm = complex(math.Inf(+1), math.Inf(-1)) + infmp = complex(math.Inf(-1), math.Inf(+1)) + infmm = complex(math.Inf(-1), math.Inf(-1)) +) + +type atocTest struct { + in string + out complex128 + err error +} + +func TestParseComplex(t *testing.T) { + tests := []atocTest{ + // Clearly invalid + {"", 0, ErrSyntax}, + {" ", 0, ErrSyntax}, + {"(", 0, ErrSyntax}, + {")", 0, ErrSyntax}, + {"i", 0, ErrSyntax}, + {"+i", 0, ErrSyntax}, + {"-i", 0, ErrSyntax}, + {"1I", 0, ErrSyntax}, + {"10 + 5i", 0, ErrSyntax}, + {"3+", 0, ErrSyntax}, + {"3+5", 0, ErrSyntax}, + {"3+5+5i", 0, ErrSyntax}, + + // Parentheses + {"()", 0, ErrSyntax}, + {"(i)", 0, ErrSyntax}, + {"(0)", 0, nil}, + {"(1i)", 1i, nil}, + {"(3.0+5.5i)", 3.0 + 5.5i, nil}, + {"(1)+1i", 0, ErrSyntax}, + {"(3.0+5.5i", 0, ErrSyntax}, + {"3.0+5.5i)", 0, ErrSyntax}, + + // NaNs + {"NaN", complex(math.NaN(), 0), nil}, + {"NANi", complex(0, math.NaN()), nil}, + {"nan+nAni", complex(math.NaN(), math.NaN()), nil}, + {"+NaN", 0, ErrSyntax}, + {"-NaN", 0, ErrSyntax}, + {"NaN-NaNi", 0, ErrSyntax}, + + // Infs + {"Inf", infp0, nil}, + {"+inf", infp0, nil}, + {"-inf", infm0, nil}, + {"Infinity", infp0, nil}, + {"+INFINITY", infp0, nil}, + {"-infinity", infm0, nil}, + {"+infi", inf0p, nil}, + {"0-infinityi", inf0m, nil}, + {"Inf+Infi", infpp, nil}, + {"+Inf-Infi", infpm, nil}, + {"-Infinity+Infi", infmp, nil}, + {"inf-inf", 0, ErrSyntax}, + + // Zeros + {"0", 0, nil}, + {"0i", 0, nil}, + {"-0.0i", 0, nil}, + {"0+0.0i", 0, nil}, + {"0e+0i", 0, nil}, + {"0e-0+0i", 0, nil}, + {"-0.0-0.0i", 0, nil}, + {"0e+012345", 0, nil}, + {"0x0p+012345i", 0, nil}, + {"0x0.00p-012345i", 0, nil}, + {"+0e-0+0e-0i", 0, nil}, + {"0e+0+0e+0i", 0, nil}, + {"-0e+0-0e+0i", 0, nil}, + + // Regular non-zeroes + {"0.1", 0.1, nil}, + {"0.1i", 0 + 0.1i, nil}, + {"0.123", 0.123, nil}, + {"0.123i", 0 + 0.123i, nil}, + {"0.123+0.123i", 0.123 + 0.123i, nil}, + {"99", 99, nil}, + {"+99", 99, nil}, + {"-99", -99, nil}, + {"+1i", 1i, nil}, + {"-1i", -1i, nil}, + {"+3+1i", 3 + 1i, nil}, + {"30+3i", 30 + 3i, nil}, + {"+3e+3-3e+3i", 3e+3 - 3e+3i, nil}, + {"+3e+3+3e+3i", 3e+3 + 3e+3i, nil}, + {"+3e+3+3e+3i+", 0, ErrSyntax}, + + // Separators + {"0.1", 0.1, nil}, + {"0.1i", 0 + 0.1i, nil}, + {"0.1_2_3", 0.123, nil}, + {"+0x_3p3i", 0x3p3i, nil}, + {"0_0+0x_0p0i", 0, nil}, + {"0x_10.3p-8+0x3p3i", 0x10.3p-8 + 0x3p3i, nil}, + {"+0x_1_0.3p-8+0x_3_0p3i", 0x10.3p-8 + 0x30p3i, nil}, + {"0x1_0.3p+8-0x_3p3i", 0x10.3p+8 - 0x3p3i, nil}, + + // Hexadecimals + {"0x10.3p-8+0x3p3i", 0x10.3p-8 + 0x3p3i, nil}, + {"+0x10.3p-8+0x3p3i", 0x10.3p-8 + 0x3p3i, nil}, + {"0x10.3p+8-0x3p3i", 0x10.3p+8 - 0x3p3i, nil}, + {"0x1p0", 1, nil}, + {"0x1p1", 2, nil}, + {"0x1p-1", 0.5, nil}, + {"0x1ep-1", 15, nil}, + {"-0x1ep-1", -15, nil}, + {"-0x2p3", -16, nil}, + {"0x1e2", 0, ErrSyntax}, + {"1p2", 0, ErrSyntax}, + {"0x1e2i", 0, ErrSyntax}, + + // ErrRange + // next float64 - too large + {"+0x1p1024", infp0, ErrRange}, + {"-0x1p1024", infm0, ErrRange}, + {"+0x1p1024i", inf0p, ErrRange}, + {"-0x1p1024i", inf0m, ErrRange}, + {"+0x1p1024+0x1p1024i", infpp, ErrRange}, + {"+0x1p1024-0x1p1024i", infpm, ErrRange}, + {"-0x1p1024+0x1p1024i", infmp, ErrRange}, + {"-0x1p1024-0x1p1024i", infmm, ErrRange}, + // the border is ...158079 + // borderline - okay + {"+0x1.fffffffffffff7fffp1023+0x1.fffffffffffff7fffp1023i", 1.7976931348623157e+308 + 1.7976931348623157e+308i, nil}, + {"+0x1.fffffffffffff7fffp1023-0x1.fffffffffffff7fffp1023i", 1.7976931348623157e+308 - 1.7976931348623157e+308i, nil}, + {"-0x1.fffffffffffff7fffp1023+0x1.fffffffffffff7fffp1023i", -1.7976931348623157e+308 + 1.7976931348623157e+308i, nil}, + {"-0x1.fffffffffffff7fffp1023-0x1.fffffffffffff7fffp1023i", -1.7976931348623157e+308 - 1.7976931348623157e+308i, nil}, + // borderline - too large + {"+0x1.fffffffffffff8p1023", infp0, ErrRange}, + {"-0x1fffffffffffff.8p+971", infm0, ErrRange}, + {"+0x1.fffffffffffff8p1023i", inf0p, ErrRange}, + {"-0x1fffffffffffff.8p+971i", inf0m, ErrRange}, + {"+0x1.fffffffffffff8p1023+0x1.fffffffffffff8p1023i", infpp, ErrRange}, + {"+0x1.fffffffffffff8p1023-0x1.fffffffffffff8p1023i", infpm, ErrRange}, + {"-0x1fffffffffffff.8p+971+0x1fffffffffffff.8p+971i", infmp, ErrRange}, + {"-0x1fffffffffffff8p+967-0x1fffffffffffff8p+967i", infmm, ErrRange}, + // a little too large + {"1e308+1e308i", 1e+308 + 1e+308i, nil}, + {"2e308+2e308i", infpp, ErrRange}, + {"1e309+1e309i", infpp, ErrRange}, + {"0x1p1025+0x1p1025i", infpp, ErrRange}, + {"2e308", infp0, ErrRange}, + {"1e309", infp0, ErrRange}, + {"0x1p1025", infp0, ErrRange}, + {"2e308i", inf0p, ErrRange}, + {"1e309i", inf0p, ErrRange}, + {"0x1p1025i", inf0p, ErrRange}, + // way too large + {"+1e310+1e310i", infpp, ErrRange}, + {"+1e310-1e310i", infpm, ErrRange}, + {"-1e310+1e310i", infmp, ErrRange}, + {"-1e310-1e310i", infmm, ErrRange}, + // under/overflow exponent + {"1e-4294967296", 0, nil}, + {"1e-4294967296i", 0, nil}, + {"1e-4294967296+1i", 1i, nil}, + {"1+1e-4294967296i", 1, nil}, + {"1e-4294967296+1e-4294967296i", 0, nil}, + {"1e+4294967296", infp0, ErrRange}, + {"1e+4294967296i", inf0p, ErrRange}, + {"1e+4294967296+1e+4294967296i", infpp, ErrRange}, + {"1e+4294967296-1e+4294967296i", infpm, ErrRange}, + } + for i := range tests { + test := &tests[i] + if test.err != nil { + test.err = &NumError{Func: "ParseComplex", Num: test.in, Err: test.err} + } + got, err := ParseComplex(test.in, 128) + if !reflect.DeepEqual(err, test.err) { + t.Fatalf("ParseComplex(%q, 128) = %v, %v; want %v, %v", test.in, got, err, test.out, test.err) + } + if !(cmplx.IsNaN(test.out) && cmplx.IsNaN(got)) && got != test.out { + t.Fatalf("ParseComplex(%q, 128) = %v, %v; want %v, %v", test.in, got, err, test.out, test.err) + } + + if complex128(complex64(test.out)) == test.out { + got, err := ParseComplex(test.in, 64) + if !reflect.DeepEqual(err, test.err) { + t.Fatalf("ParseComplex(%q, 64) = %v, %v; want %v, %v", test.in, got, err, test.out, test.err) + } + got64 := complex64(got) + if complex128(got64) != test.out { + t.Fatalf("ParseComplex(%q, 64) = %v, %v; want %v, %v", test.in, got, err, test.out, test.err) + } + } + } +} + +// Issue 42297: allow ParseComplex(s, not_32_or_64) for legacy reasons +func TestParseComplexIncorrectBitSize(t *testing.T) { + const s = "1.5e308+1.0e307i" + const want = 1.5e308 + 1.0e307i + + for _, bitSize := range []int{0, 10, 100, 256} { + c, err := ParseComplex(s, bitSize) + if err != nil { + t.Fatalf("ParseComplex(%q, %d) gave error %s", s, bitSize, err) + } + if c != want { + t.Fatalf("ParseComplex(%q, %d) = %g (expected %g)", s, bitSize, c, want) + } + } +} + +type atofTest struct { + in string + out string + err error +} + +var atoftests = []atofTest{ + {"", "0", ErrSyntax}, + {"1.25", "1.25", nil}, + {"+1", "1", nil}, + {"1x", "0", ErrSyntax}, + {"1.1.", "0", ErrSyntax}, + {"1e23", "1e+23", nil}, + {"1E23", "1e+23", nil}, + {"0x1fFe2.p0", "131042", nil}, + {"0x1fFe2.P0", "131042", nil}, + {"-0x2p3", "-16", nil}, + {"0x0.fp4", "15", nil}, + {"0x0.fp0", "0.9375", nil}, + {"0x1e2", "0", ErrSyntax}, + {"1p2", "0", ErrSyntax}, + {"0x1p1024", "+Inf", ErrRange}, + {"-0x1p1024", "-Inf", ErrRange}, + {"0x1.fffffffffffff7fffp1023", "1.7976931348623157e+308", nil}, + {"-0x1.fffffffffffff7fffp1023", "-1.7976931348623157e+308", nil}, + {"1.797693134862315808e308", "+Inf", ErrRange}, + {"-1.797693134862315808e308", "-Inf", ErrRange}, +} + +func init() { + // The atof routines return NumErrors wrapping + // the error and the string. Convert the table above. + for i := range atoftests { + test := &atoftests[i] + if test.err != nil { + test.err = &NumError{"ParseFloat", test.in, test.err} + } + } +} + +func TestAtof(t *testing.T) { + for i := 0; i < len(atoftests); i++ { + test := &atoftests[i] + out, err := ParseFloat(test.in, 64) + outs := FormatFloat(out, 'g', -1, 64) + if outs != test.out || !reflect.DeepEqual(err, test.err) { + t.Errorf("ParseFloat(%v, 64) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) + } + + if float64(float32(out)) == out { + out, err := ParseFloat(test.in, 32) + out32 := float32(out) + if float64(out32) != out { + t.Errorf("ParseFloat(%v, 32) = %v, not a float32 (closest is %v)", test.in, out, float64(out32)) + continue + } + outs := FormatFloat(float64(out32), 'g', -1, 32) + if outs != test.out || !reflect.DeepEqual(err, test.err) { + t.Errorf("ParseFloat(%v, 32) = %v, %v want %v, %v # %v", + test.in, out32, err, test.out, test.err, out) + } + } + } +} + +type parseUint64Test struct { + in string + out uint64 + err error +} + +var parseUint64Tests = []parseUint64Test{ + {"", 0, ErrSyntax}, + {"0", 0, nil}, + {"1", 1, nil}, + {"12345", 12345, nil}, + {"012345", 12345, nil}, + {"18446744073709551616", 1<<64 - 1, ErrRange}, + {"-1", 0, ErrSyntax}, +} + +type parseUint64BaseTest struct { + in string + base int + out uint64 + err error +} + +var parseUint64BaseTests = []parseUint64BaseTest{ + {"", 0, 0, ErrSyntax}, + {"0", 0, 0, nil}, + {"1", 0, 1, nil}, + {"-1", 0, 0, ErrSyntax}, + {"12345", 0, 12345, nil}, + {"012345", 0, 012345, nil}, + {"18446744073709551616", 0, 1<<64 - 1, ErrRange}, + {"0b", 0, 0, ErrSyntax}, + {"101", 2, 5, nil}, + {"101_", 2, 0, ErrSyntax}, +} + +type parseInt64Test struct { + in string + out int64 + err error +} + +var parseInt64Tests = []parseInt64Test{ + {"", 0, ErrSyntax}, + {"0", 0, nil}, + {"1", 1, nil}, + {"-1", -1, nil}, + {"12345", 12345, nil}, + {"9223372036854775808", 1<<63 - 1, ErrRange}, + {"123%45", 0, ErrSyntax}, +} + +type parseInt64BaseTest struct { + in string + base int + out int64 + err error +} + +var parseInt64BaseTests = []parseInt64BaseTest{ + {"", 0, 0, ErrSyntax}, + {"0", 0, 0, nil}, + {"1", 0, 1, nil}, + {"-1", 0, -1, nil}, + {"12345", 0, 12345, nil}, + {"12345", 9, 8303, nil}, + {"012345", 0, 012345, nil}, + {"9223372036854775808", 10, 1<<63 - 1, ErrRange}, + {"0b", 0, 0, ErrSyntax}, + {"101", 2, 5, nil}, + {"101_", 2, 0, ErrSyntax}, +} + +type parseUint32Test struct { + in string + out uint32 + err error +} + +var parseUint32Tests = []parseUint32Test{ + {"", 0, ErrSyntax}, + {"0", 0, nil}, + {"1", 1, nil}, + {"12345", 12345, nil}, + {"12345x", 0, ErrSyntax}, + {"987654321", 987654321, nil}, + {"4294967296", 1<<32 - 1, ErrRange}, + {"1_2_3_4_5", 0, ErrSyntax}, // base=10 so no underscores allowed + {"12345_", 0, ErrSyntax}, +} + +type parseInt32Test struct { + in string + out int32 + err error +} + +var parseInt32Tests = []parseInt32Test{ + {"", 0, ErrSyntax}, + {"0", 0, nil}, + {"-0", 0, nil}, + {"1", 1, nil}, + {"-1", -1, nil}, + {"12345", 12345, nil}, + {"-12345", -12345, nil}, + {"2147483648", 1<<31 - 1, ErrRange}, + {"12345_", 0, ErrSyntax}, +} + +type numErrorTest struct { + num, want string +} + +var numErrorTests = []numErrorTest{ + {"0", `strconv.ParseFloat: parsing "0": failed`}, + {"`", "strconv.ParseFloat: parsing \"`\": failed"}, + {"1\x00.2", `strconv.ParseFloat: parsing "1\x00.2": failed`}, +} + +func init() { + // The parse routines return NumErrors wrapping + // the error and the string. Convert the tables above. + for i := range parseUint64Tests { + test := &parseUint64Tests[i] + if test.err != nil { + test.err = &NumError{"ParseUint", test.in, test.err} + } + } + for i := range parseUint64BaseTests { + test := &parseUint64BaseTests[i] + if test.err != nil { + test.err = &NumError{"ParseUint", test.in, test.err} + } + } + for i := range parseInt64Tests { + test := &parseInt64Tests[i] + if test.err != nil { + test.err = &NumError{"ParseInt", test.in, test.err} + } + } + for i := range parseInt64BaseTests { + test := &parseInt64BaseTests[i] + if test.err != nil { + test.err = &NumError{"ParseInt", test.in, test.err} + } + } + for i := range parseUint32Tests { + test := &parseUint32Tests[i] + if test.err != nil { + test.err = &NumError{"ParseUint", test.in, test.err} + } + } + for i := range parseInt32Tests { + test := &parseInt32Tests[i] + if test.err != nil { + test.err = &NumError{"ParseInt", test.in, test.err} + } + } +} + +func TestParseUint32(t *testing.T) { + for i := range parseUint32Tests { + test := &parseUint32Tests[i] + out, err := ParseUint(test.in, 10, 32) + if uint64(test.out) != out || !reflect.DeepEqual(test.err, err) { + t.Errorf("ParseUint(%q, 10, 32) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) + } + } +} + +func TestParseUint64(t *testing.T) { + for i := range parseUint64Tests { + test := &parseUint64Tests[i] + out, err := ParseUint(test.in, 10, 64) + if test.out != out || !reflect.DeepEqual(test.err, err) { + t.Errorf("ParseUint(%q, 10, 64) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) + } + } +} + +func TestParseUint64Base(t *testing.T) { + for i := range parseUint64BaseTests { + test := &parseUint64BaseTests[i] + out, err := ParseUint(test.in, test.base, 64) + if test.out != out || !reflect.DeepEqual(test.err, err) { + t.Errorf("ParseUint(%q, %v, 64) = %v, %v want %v, %v", + test.in, test.base, out, err, test.out, test.err) + } + } +} + +func TestParseInt32(t *testing.T) { + for i := range parseInt32Tests { + test := &parseInt32Tests[i] + out, err := ParseInt(test.in, 10, 32) + if int64(test.out) != out || !reflect.DeepEqual(test.err, err) { + t.Errorf("ParseInt(%q, 10 ,32) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) + } + } +} + +func TestParseInt64(t *testing.T) { + for i := range parseInt64Tests { + test := &parseInt64Tests[i] + out, err := ParseInt(test.in, 10, 64) + if test.out != out || !reflect.DeepEqual(test.err, err) { + t.Errorf("ParseInt(%q, 10, 64) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) + } + } +} + +func TestParseInt64Base(t *testing.T) { + for i := range parseInt64BaseTests { + test := &parseInt64BaseTests[i] + out, err := ParseInt(test.in, test.base, 64) + if test.out != out || !reflect.DeepEqual(test.err, err) { + t.Errorf("ParseInt(%q, %v, 64) = %v, %v want %v, %v", + test.in, test.base, out, err, test.out, test.err) + } + } +} + +func TestParseUint(t *testing.T) { + switch IntSize { + case 32: + for i := range parseUint32Tests { + test := &parseUint32Tests[i] + out, err := ParseUint(test.in, 10, 0) + if uint64(test.out) != out || !reflect.DeepEqual(test.err, err) { + t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) + } + } + case 64: + for i := range parseUint64Tests { + test := &parseUint64Tests[i] + out, err := ParseUint(test.in, 10, 0) + if test.out != out || !reflect.DeepEqual(test.err, err) { + t.Errorf("ParseUint(%q, 10, 0) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) + } + } + } +} + +func TestParseInt(t *testing.T) { + switch IntSize { + case 32: + for i := range parseInt32Tests { + test := &parseInt32Tests[i] + out, err := ParseInt(test.in, 10, 0) + if int64(test.out) != out || !reflect.DeepEqual(test.err, err) { + t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) + } + } + case 64: + for i := range parseInt64Tests { + test := &parseInt64Tests[i] + out, err := ParseInt(test.in, 10, 0) + if test.out != out || !reflect.DeepEqual(test.err, err) { + t.Errorf("ParseInt(%q, 10, 0) = %v, %v want %v, %v", + test.in, out, err, test.out, test.err) + } + } + } +} + +func TestAtoi(t *testing.T) { + switch IntSize { + case 32: + for i := range parseInt32Tests { + test := &parseInt32Tests[i] + out, err := Atoi(test.in) + var testErr error + if test.err != nil { + testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err} + } + if int(test.out) != out || !reflect.DeepEqual(testErr, err) { + t.Errorf("Atoi(%q) = %v, %v want %v, %v", + test.in, out, err, test.out, testErr) + } + } + case 64: + for i := range parseInt64Tests { + test := &parseInt64Tests[i] + out, err := Atoi(test.in) + var testErr error + if test.err != nil { + testErr = &NumError{"Atoi", test.in, test.err.(*NumError).Err} + } + if test.out != int64(out) || !reflect.DeepEqual(testErr, err) { + t.Errorf("Atoi(%q) = %v, %v want %v, %v", + test.in, out, err, test.out, testErr) + } + } + } +} + +func bitSizeErrStub(name string, bitSize int) error { + return bitSizeError(name, "0", bitSize) +} + +func baseErrStub(name string, base int) error { + return baseError(name, "0", base) +} + +func noErrStub(name string, arg int) error { + return nil +} + +type parseErrorTest struct { + arg int + errStub func(name string, arg int) error +} + +var parseBitSizeTests = []parseErrorTest{ + {-1, bitSizeErrStub}, + {0, noErrStub}, + {64, noErrStub}, + {65, bitSizeErrStub}, +} + +var parseBaseTests = []parseErrorTest{ + {-1, baseErrStub}, + {0, noErrStub}, + {1, baseErrStub}, + {2, noErrStub}, + {36, noErrStub}, + {37, baseErrStub}, +} + +func equalError(a, b error) bool { + if a == nil { + return b == nil + } + if b == nil { + return a == nil + } + return a.Error() == b.Error() +} + +func TestParseIntBitSize(t *testing.T) { + for i := range parseBitSizeTests { + test := &parseBitSizeTests[i] + testErr := test.errStub("ParseInt", test.arg) + _, err := ParseInt("0", 0, test.arg) + if !equalError(testErr, err) { + t.Errorf("ParseInt(\"0\", 0, %v) = 0, %v want 0, %v", + test.arg, err, testErr) + } + } +} + +func TestParseUintBitSize(t *testing.T) { + for i := range parseBitSizeTests { + test := &parseBitSizeTests[i] + testErr := test.errStub("ParseUint", test.arg) + _, err := ParseUint("0", 0, test.arg) + if !equalError(testErr, err) { + t.Errorf("ParseUint(\"0\", 0, %v) = 0, %v want 0, %v", + test.arg, err, testErr) + } + } +} + +func TestParseIntBase(t *testing.T) { + for i := range parseBaseTests { + test := &parseBaseTests[i] + testErr := test.errStub("ParseInt", test.arg) + _, err := ParseInt("0", test.arg, 0) + if !equalError(testErr, err) { + t.Errorf("ParseInt(\"0\", %v, 0) = 0, %v want 0, %v", + test.arg, err, testErr) + } + } +} + +func TestParseUintBase(t *testing.T) { + for i := range parseBaseTests { + test := &parseBaseTests[i] + testErr := test.errStub("ParseUint", test.arg) + _, err := ParseUint("0", test.arg, 0) + if !equalError(testErr, err) { + t.Errorf("ParseUint(\"0\", %v, 0) = 0, %v want 0, %v", + test.arg, err, testErr) + } + } +} + +func TestNumError(t *testing.T) { + for _, test := range numErrorTests { + err := &NumError{ + Func: "ParseFloat", + Num: test.num, + Err: errors.New("failed"), + } + if got := err.Error(); got != test.want { + t.Errorf(`(&NumError{"ParseFloat", %q, "failed"}).Error() = %v, want %v`, test.num, got, test.want) + } + } +} + +func TestNumErrorUnwrap(t *testing.T) { + err := &NumError{Err: ErrSyntax} + if !errors.Is(err, ErrSyntax) { + t.Error("errors.Is failed, wanted success") + } +} + +func TestFormatComplex(t *testing.T) { + tests := []struct { + c complex128 + fmt byte + prec int + bitSize int + out string + }{ + // a variety of signs + {1 + 2i, 'g', -1, 128, "(1+2i)"}, + {3 - 4i, 'g', -1, 128, "(3-4i)"}, + {-5 + 6i, 'g', -1, 128, "(-5+6i)"}, + {-7 - 8i, 'g', -1, 128, "(-7-8i)"}, + + // test that fmt and prec are working + {3.14159 + 0.00123i, 'e', 3, 128, "(3.142e+00+1.230e-03i)"}, + {3.14159 + 0.00123i, 'f', 3, 128, "(3.142+0.001i)"}, + {3.14159 + 0.00123i, 'g', 3, 128, "(3.14+0.00123i)"}, + + // ensure bitSize rounding is working + {1.2345678901234567 + 9.876543210987654i, 'f', -1, 128, "(1.2345678901234567+9.876543210987654i)"}, + {1.2345678901234567 + 9.876543210987654i, 'f', -1, 64, "(1.2345679+9.876543i)"}, + + // other cases are handled by FormatFloat tests + } + for _, test := range tests { + out := FormatComplex(test.c, test.fmt, test.prec, test.bitSize) + if out != test.out { + t.Fatalf("FormatComplex(%v, %q, %d, %d) = %q; want %q", + test.c, test.fmt, test.prec, test.bitSize, out, test.out) + } + } +} + +func TestFormatComplexInvalidBitSize(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected panic due to invalid bitSize") + } + }() + _ = FormatComplex(1+2i, 'g', -1, 100) +} + +type itob64Test struct { + in int64 + base int + out string +} + +var itob64tests = []itob64Test{ + {0, 10, "0"}, + {1, 10, "1"}, + {-1, 10, "-1"}, + {12345678, 10, "12345678"}, + {-1 << 63, 10, "-9223372036854775808"}, + {16, 17, "g"}, + {25, 25, "10"}, + {(((((17*36+24)*36+21)*36+34)*36+12)*36+24)*36 + 32, 36, "holycow"}, +} + +func TestItoa(t *testing.T) { + for _, test := range itob64tests { + s := FormatInt(test.in, test.base) + if s != test.out { + t.Errorf("FormatInt(%v, %v) = %v want %v", + test.in, test.base, s, test.out) + } + x := AppendInt([]byte("abc"), test.in, test.base) + if string(x) != "abc"+test.out { + t.Errorf("AppendInt(%q, %v, %v) = %q want %v", + "abc", test.in, test.base, x, test.out) + } + + if test.in >= 0 { + s := FormatUint(uint64(test.in), test.base) + if s != test.out { + t.Errorf("FormatUint(%v, %v) = %v want %v", + test.in, test.base, s, test.out) + } + x := AppendUint(nil, uint64(test.in), test.base) + if string(x) != test.out { + t.Errorf("AppendUint(%q, %v, %v) = %q want %v", + "abc", uint64(test.in), test.base, x, test.out) + } + } + + if test.base == 10 && int64(int(test.in)) == test.in { + s := Itoa(int(test.in)) + if s != test.out { + t.Errorf("Itoa(%v) = %v want %v", + test.in, s, test.out) + } + } + } + + // Override when base is illegal + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected panic due to illegal base") + } + }() + FormatUint(12345678, 1) +} + +type uitob64Test struct { + in uint64 + base int + out string +} + +var uitob64tests = []uitob64Test{ + {1<<63 - 1, 10, "9223372036854775807"}, + {1 << 63, 10, "9223372036854775808"}, + {1<<63 + 1, 10, "9223372036854775809"}, + {1<<64 - 2, 10, "18446744073709551614"}, + {1<<64 - 1, 10, "18446744073709551615"}, + {1<<64 - 1, 2, "1111111111111111111111111111111111111111111111111111111111111111"}, +} + +func TestUitoa(t *testing.T) { + for _, test := range uitob64tests { + s := FormatUint(test.in, test.base) + if s != test.out { + t.Errorf("FormatUint(%v, %v) = %v want %v", + test.in, test.base, s, test.out) + } + x := AppendUint([]byte("abc"), test.in, test.base) + if string(x) != "abc"+test.out { + t.Errorf("AppendUint(%q, %v, %v) = %q want %v", + "abc", test.in, test.base, x, test.out) + } + + } +} + +var varlenUints = []struct { + in uint64 + out string +}{ + {1, "1"}, + {12, "12"}, + {123, "123"}, + {1234, "1234"}, + {12345, "12345"}, + {123456, "123456"}, + {1234567, "1234567"}, + {12345678, "12345678"}, + {123456789, "123456789"}, + {1234567890, "1234567890"}, + {12345678901, "12345678901"}, + {123456789012, "123456789012"}, + {1234567890123, "1234567890123"}, + {12345678901234, "12345678901234"}, + {123456789012345, "123456789012345"}, + {1234567890123456, "1234567890123456"}, + {12345678901234567, "12345678901234567"}, + {123456789012345678, "123456789012345678"}, + {1234567890123456789, "1234567890123456789"}, + {12345678901234567890, "12345678901234567890"}, +} + +func TestFormatUintVarlen(t *testing.T) { + for _, test := range varlenUints { + s := FormatUint(test.in, 10) + if s != test.out { + t.Errorf("FormatUint(%v, 10) = %v want %v", test.in, s, test.out) + } + } +}