Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

strconv: add ParseComplex and FormatComplex #36815

Closed
wants to merge 48 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
55b2570
- add ParseComplex func
pjebs Jan 26, 2020
8afa9a5
- added ParseComplex function
pjebs Jan 27, 2020
a16a927
- fix up bug
pjebs Jan 29, 2020
8df2934
- added some tests
pjebs Jan 29, 2020
bdf80fd
- rename parseFloat to parseComplexComponent
pjebs Mar 27, 2020
fd5df02
- implement FormatComplex
pjebs Mar 27, 2020
0f31e5a
- return j immediately
pjebs Mar 27, 2020
04ebaf1
- simplify code
pjebs Mar 27, 2020
07a8ba4
- update docs
pjebs Mar 29, 2020
cefda0c
- update readme
pjebs Apr 11, 2020
4ec856a
- amend based on review comments
pjebs Apr 20, 2020
d0e2ade
- amend based on review comments
pjebs Apr 20, 2020
3e44b92
- amend based on review comments
pjebs Apr 20, 2020
1d98820
- remove unnecessary left
pjebs Apr 20, 2020
76ac483
- update/add/elaborate comments
pjebs Apr 21, 2020
70fa595
- handle NaN case
pjebs Apr 21, 2020
39c41e6
- add all test cases except hexadecimal
pjebs Apr 21, 2020
f86cfad
- fix up tests
pjebs Apr 21, 2020
0b92e41
- added hex tests.
pjebs Apr 21, 2020
356ebcb
- add "i" test case
pjebs Apr 26, 2020
a15d62c
- add extra note
pjebs Apr 26, 2020
45a837d
- rearrange some code
pjebs Apr 28, 2020
703ee5c
- add more test cases
pjebs Apr 29, 2020
ad8fbfd
- add test to prohibit NaN-NaNi
pjebs Apr 29, 2020
cf61745
- update docs based on feedback
pjebs Apr 29, 2020
77e646b
- add skeleton parseFloat func in line with feedback
pjebs Apr 29, 2020
f3f6211
Revert "- add skeleton parseFloat func in line with feedback"
pjebs Apr 30, 2020
3e41873
- add more test cases due to how ParseFloatPrefix works
pjebs Apr 30, 2020
578f866
- ParseComplex rewritten with new parseFloatPrefix function.
pjebs Apr 30, 2020
7a9466b
- finishing touches
pjebs Apr 30, 2020
75c8304
- don't shadow imag builtin func
pjebs May 1, 2020
3351300
- condense test syntax
pjebs May 1, 2020
a9f662b
- change algorithm
pjebs May 1, 2020
c561916
- add more test cases
pjebs May 1, 2020
76942b9
- update documentation
pjebs May 5, 2020
da29b0e
- add ErrRange test cases
pjebs May 5, 2020
ceaa172
- partially fix up ErrRange problem
pjebs May 5, 2020
933c984
- changed back: FatalF -> ErrorF -> FatalF (When in Run, FatalF doe…
pjebs May 5, 2020
afccb3f
- partial fix of NaN issue
pjebs May 5, 2020
a8fe3fa
- ammended ErrRange issue
pjebs May 5, 2020
fc2663a
- amend ParseComplex to cater for ErrRange
pjebs May 6, 2020
5dcb0fb
- update ParseFloat docs to reveal "Infinity"
pjebs May 6, 2020
42cb8e0
- revert docs back to correct possessive case
pjebs May 7, 2020
f4eebf8
- reduce tests for zeros/infs/NaN
pjebs May 7, 2020
96cea1a
- remove some cases that were already tested
pjebs May 7, 2020
d45bbbb
- use global variables for math.Inf usage
pjebs May 7, 2020
d336057
- final tests for atoc
pjebs May 8, 2020
036a075
all: merge master into strconv.ParseCompex
pjebs May 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions src/strconv/atoc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2020 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

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 = 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
// 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) {
size := 128
if bitSize == 64 {
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]
}

var pending error // pending range error, or nil

// 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 {
return 0, err
}
}
s = s[n:]

// If we have nothing left, we're done.
if len(s) == 0 {
return complex(re, 0), pending
}

// Otherwise, look at the next character.
switch s[0] {
case '+':
// Consume the '+' to avoid an error if we have "+NaNi", but
// do this only if we don't have a "++" (don't hide that error).
if len(s) > 1 && s[1] != '+' {
s = s[1:]
}
case '-':
// ok
case 'i':
// If 'i' is the last character, we only have an imaginary part.
if len(s) == 1 {
return complex(0, re), pending
}
fallthrough
default:
return 0, syntaxError(fnParseComplex, orig)
}

// Read imaginary part.
im, n, err := parseFloatPrefix(s, size)
if err != nil {
err, pending = convErr(err, orig)
if err != nil {
return 0, err
}
}
s = s[n:]
if s != "i" {
return 0, syntaxError(fnParseComplex, orig)
}
return complex(re, im), pending
}
195 changes: 195 additions & 0 deletions src/strconv/atoc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// Copyright 2020 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 (
"math"
"math/cmplx"
"reflect"
. "strconv"
"testing"
)

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},
{"0x_10.3p-8+0x3p3i", 0x10.3p-8 + 0x3p3i, nil},
{"+0x_1_0.3p-8+0x3p3i", 0x10.3p-8 + 0x3p3i, nil},
{"0x10.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 _, tt := range tests {
tt := tt // for capture in Run closures below
if tt.err != nil {
tt.err = &NumError{Func: "ParseComplex", Num: tt.in, Err: tt.err}
}
t.Run(tt.in, func(t *testing.T) {
got, err := ParseComplex(tt.in, 128)
if !reflect.DeepEqual(err, tt.err) {
t.Fatalf("ParseComplex(%q, 128) = %v, %v want %v, %v", tt.in, got, err, tt.out, tt.err)
}
if !(cmplx.IsNaN(tt.out) && cmplx.IsNaN(got)) && got != tt.out {
t.Fatalf("ParseComplex(%q, 128) = %v, %v want %v, %v", tt.in, got, err, tt.out, tt.err)
}
})
}
}
4 changes: 2 additions & 2 deletions src/strconv/atof.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,8 +677,8 @@ func atof64(s string) (f float64, n int, err error) {
// away from the largest floating point number of the given size,
// ParseFloat returns f = ±Inf, err.Err = ErrRange.
//
// ParseFloat recognizes the strings "NaN", "+Inf", and "-Inf" as their
// respective special floating point values. It ignores case when matching.
// ParseFloat recognizes the strings "NaN", and the (possibly signed) strings "Inf" and "Infinity"
// as their respective special floating point values. It ignores case when matching.
func ParseFloat(s string, bitSize int) (float64, error) {
f, n, err := parseFloatPrefix(s, bitSize)
if err == nil && n != len(s) {
Expand Down
2 changes: 1 addition & 1 deletion src/strconv/atoi.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var ErrSyntax = errors.New("invalid syntax")

// A NumError records a failed conversion.
type NumError struct {
Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat)
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.)
}
Expand Down
27 changes: 27 additions & 0 deletions src/strconv/ctoa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2020 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

// 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 {
if bitSize != 64 && bitSize != 128 {
panic("invalid bitSize")
}
bitSize >>= 1 // complex64 uses float32 internally

// Check if imaginary part has a sign. If not, add one.
im := FormatFloat(imag(c), fmt, prec, bitSize)
if im[0] != '+' && im[0] != '-' {
im = "+" + im
}

return "(" + FormatFloat(real(c), fmt, prec, bitSize) + im + "i)"
}