starlark: bring floating-point into spec compliance (#313)

This change makes go.starlark.net's floating-point implementation
match the proposed spec (see https://github.com/bazelbuild/starlark/pull/119),
and thus much more closely match the behavior of the Java implementation.

The major changes are:
- Float values are totally ordered; NaN compares greater than +Inf.
- The string form of a finite float value always contains an exponent
  or a decimal point, so they are self-evidently not int values.
- Operations that would cause a large integer to become rounded to
  an infinite float are now an error.

The resolve.AllowFloat boolean, and the corresponding -float command-line
flag, now have no effect. Floating point support is always enabled.
diff --git a/cmd/starlark/starlark.go b/cmd/starlark/starlark.go
index af4f71f..31555f1 100644
--- a/cmd/starlark/starlark.go
+++ b/cmd/starlark/starlark.go
@@ -35,7 +35,7 @@
 	flag.BoolVar(&compile.Disassemble, "disassemble", compile.Disassemble, "show disassembly during compilation of each function")
 
 	// non-standard dialect flags
-	flag.BoolVar(&resolve.AllowFloat, "float", resolve.AllowFloat, "allow floating-point numbers")
+	flag.BoolVar(&resolve.AllowFloat, "float", resolve.AllowFloat, "obsolete; no effect")
 	flag.BoolVar(&resolve.AllowSet, "set", resolve.AllowSet, "allow set data type")
 	flag.BoolVar(&resolve.AllowLambda, "lambda", resolve.AllowLambda, "allow lambda expressions")
 	flag.BoolVar(&resolve.AllowNestedDef, "nesteddef", resolve.AllowNestedDef, "allow nested def statements")
diff --git a/go.mod b/go.mod
index 87e6d96..e6aa6ba 100644
--- a/go.mod
+++ b/go.mod
@@ -6,5 +6,5 @@
 	github.com/chzyer/logex v1.1.10 // indirect
 	github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
 	github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
-	golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
+	golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 // indirect
 )
diff --git a/go.sum b/go.sum
index a969512..7d77852 100644
--- a/go.sum
+++ b/go.sum
@@ -4,5 +4,5 @@
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/resolve/resolve.go b/resolve/resolve.go
index e4b6752..26d74b4 100644
--- a/resolve/resolve.go
+++ b/resolve/resolve.go
@@ -100,7 +100,7 @@
 var (
 	AllowNestedDef      = false // allow def statements within function bodies
 	AllowLambda         = false // allow lambda expressions
-	AllowFloat          = false // allow floating point literals, the 'float' built-in, and x / y
+	AllowFloat          = false // obsolete; no effect
 	AllowSet            = false // allow the 'set' built-in
 	AllowGlobalReassign = false // allow reassignment to top-level names; also, allow if/for/while at top-level
 	AllowRecursion      = false // allow while statements and recursive functions
@@ -418,9 +418,6 @@
 		r.predeclared[id.Name] = bind // save it
 	} else if r.isUniversal(id.Name) {
 		// use of universal name
-		if !AllowFloat && id.Name == "float" {
-			r.errorf(id.NamePos, doesnt+"support floating point")
-		}
 		if !AllowSet && id.Name == "set" {
 			r.errorf(id.NamePos, doesnt+"support sets")
 		}
@@ -636,9 +633,6 @@
 		r.use(e)
 
 	case *syntax.Literal:
-		if !AllowFloat && e.Token == syntax.FLOAT {
-			r.errorf(e.TokenPos, doesnt+"support floating point")
-		}
 
 	case *syntax.ListExpr:
 		for _, x := range e.List {
@@ -713,9 +707,6 @@
 		r.expr(e.X)
 
 	case *syntax.BinaryExpr:
-		if !AllowFloat && e.Op == syntax.SLASH {
-			r.errorf(e.OpPos, doesnt+"support floating point (use //)")
-		}
 		r.expr(e.X)
 		r.expr(e.Y)
 
diff --git a/resolve/resolve_test.go b/resolve/resolve_test.go
index 090dbc5..cb2c672 100644
--- a/resolve/resolve_test.go
+++ b/resolve/resolve_test.go
@@ -15,7 +15,6 @@
 )
 
 func setOptions(src string) {
-	resolve.AllowFloat = option(src, "float")
 	resolve.AllowGlobalReassign = option(src, "globalreassign")
 	resolve.AllowLambda = option(src, "lambda")
 	resolve.AllowNestedDef = option(src, "nesteddef")
@@ -38,7 +37,7 @@
 			continue
 		}
 
-		// A chunk may set options by containing e.g. "option:float".
+		// A chunk may set options by containing e.g. "option:nesteddef".
 		setOptions(chunk.Source)
 
 		if err := resolve.File(f, isPredeclared, isUniversal); err != nil {
diff --git a/resolve/testdata/resolve.star b/resolve/testdata/resolve.star
index 4f1a270..8399bf3 100644
--- a/resolve/testdata/resolve.star
+++ b/resolve/testdata/resolve.star
@@ -307,12 +307,7 @@
 def i(*x, **x): pass ### "duplicate parameter: x"
 
 ---
-# No floating point
-a = float("3.141") ### `dialect does not support floating point`
-b = 1 / 2          ### `dialect does not support floating point \(use //\)`
-c = 3.141          ### `dialect does not support floating point`
----
-# Floating point support (option:float)
+# Floating-point support is now standard.
 a = float("3.141")
 b = 1 / 2
 c = 3.141
diff --git a/starlark/eval.go b/starlark/eval.go
index c17b8b7..633fc30 100644
--- a/starlark/eval.go
+++ b/starlark/eval.go
@@ -713,14 +713,22 @@
 			case Int:
 				return x.Add(y), nil
 			case Float:
-				return x.Float() + y, nil
+				xf, err := x.finiteFloat()
+				if err != nil {
+					return nil, err
+				}
+				return xf + y, nil
 			}
 		case Float:
 			switch y := y.(type) {
 			case Float:
 				return x + y, nil
 			case Int:
-				return x + y.Float(), nil
+				yf, err := y.finiteFloat()
+				if err != nil {
+					return nil, err
+				}
+				return x + yf, nil
 			}
 		case *List:
 			if y, ok := y.(*List); ok {
@@ -745,14 +753,22 @@
 			case Int:
 				return x.Sub(y), nil
 			case Float:
-				return x.Float() - y, nil
+				xf, err := x.finiteFloat()
+				if err != nil {
+					return nil, err
+				}
+				return xf - y, nil
 			}
 		case Float:
 			switch y := y.(type) {
 			case Float:
 				return x - y, nil
 			case Int:
-				return x - y.Float(), nil
+				yf, err := y.finiteFloat()
+				if err != nil {
+					return nil, err
+				}
+				return x - yf, nil
 			}
 		}
 
@@ -763,7 +779,11 @@
 			case Int:
 				return x.Mul(y), nil
 			case Float:
-				return x.Float() * y, nil
+				xf, err := x.finiteFloat()
+				if err != nil {
+					return nil, err
+				}
+				return xf * y, nil
 			case String:
 				return stringRepeat(y, x)
 			case *List:
@@ -780,7 +800,11 @@
 			case Float:
 				return x * y, nil
 			case Int:
-				return x * y.Float(), nil
+				yf, err := y.finiteFloat()
+				if err != nil {
+					return nil, err
+				}
+				return x * yf, nil
 			}
 		case String:
 			if y, ok := y.(Int); ok {
@@ -804,30 +828,40 @@
 	case syntax.SLASH:
 		switch x := x.(type) {
 		case Int:
+			xf, err := x.finiteFloat()
+			if err != nil {
+				return nil, err
+			}
 			switch y := y.(type) {
 			case Int:
-				yf := y.Float()
-				if yf == 0.0 {
-					return nil, fmt.Errorf("real division by zero")
+				yf, err := y.finiteFloat()
+				if err != nil {
+					return nil, err
 				}
-				return x.Float() / yf, nil
+				if yf == 0.0 {
+					return nil, fmt.Errorf("floating-point division by zero")
+				}
+				return xf / yf, nil
 			case Float:
 				if y == 0.0 {
-					return nil, fmt.Errorf("real division by zero")
+					return nil, fmt.Errorf("floating-point division by zero")
 				}
-				return x.Float() / y, nil
+				return xf / y, nil
 			}
 		case Float:
 			switch y := y.(type) {
 			case Float:
 				if y == 0.0 {
-					return nil, fmt.Errorf("real division by zero")
+					return nil, fmt.Errorf("floating-point division by zero")
 				}
 				return x / y, nil
 			case Int:
-				yf := y.Float()
+				yf, err := y.finiteFloat()
+				if err != nil {
+					return nil, err
+				}
 				if yf == 0.0 {
-					return nil, fmt.Errorf("real division by zero")
+					return nil, fmt.Errorf("floating-point division by zero")
 				}
 				return x / yf, nil
 			}
@@ -843,10 +877,14 @@
 				}
 				return x.Div(y), nil
 			case Float:
+				xf, err := x.finiteFloat()
+				if err != nil {
+					return nil, err
+				}
 				if y == 0.0 {
 					return nil, fmt.Errorf("floored division by zero")
 				}
-				return floor((x.Float() / y)), nil
+				return floor(xf / y), nil
 			}
 		case Float:
 			switch y := y.(type) {
@@ -856,7 +894,10 @@
 				}
 				return floor(x / y), nil
 			case Int:
-				yf := y.Float()
+				yf, err := y.finiteFloat()
+				if err != nil {
+					return nil, err
+				}
 				if yf == 0.0 {
 					return nil, fmt.Errorf("floored division by zero")
 				}
@@ -874,23 +915,31 @@
 				}
 				return x.Mod(y), nil
 			case Float:
-				if y == 0 {
-					return nil, fmt.Errorf("float modulo by zero")
+				xf, err := x.finiteFloat()
+				if err != nil {
+					return nil, err
 				}
-				return x.Float().Mod(y), nil
+				if y == 0 {
+					return nil, fmt.Errorf("floating-point modulo by zero")
+				}
+				return xf.Mod(y), nil
 			}
 		case Float:
 			switch y := y.(type) {
 			case Float:
 				if y == 0.0 {
-					return nil, fmt.Errorf("float modulo by zero")
+					return nil, fmt.Errorf("floating-point modulo by zero")
 				}
 				return x.Mod(y), nil
 			case Int:
 				if y.Sign() == 0 {
-					return nil, fmt.Errorf("float modulo by zero")
+					return nil, fmt.Errorf("floating-point modulo by zero")
 				}
-				return x.Mod(y.Float()), nil
+				yf, err := y.finiteFloat()
+				if err != nil {
+					return nil, err
+				}
+				return x.Mod(yf), nil
 			}
 		case String:
 			return interpolate(string(x), y)
@@ -1497,20 +1546,7 @@
 			if !ok {
 				return nil, fmt.Errorf("%%%c format requires float, not %s", c, arg.Type())
 			}
-			switch c {
-			case 'e':
-				fmt.Fprintf(buf, "%e", f)
-			case 'f':
-				fmt.Fprintf(buf, "%f", f)
-			case 'g':
-				fmt.Fprintf(buf, "%g", f)
-			case 'E':
-				fmt.Fprintf(buf, "%E", f)
-			case 'F':
-				fmt.Fprintf(buf, "%F", f)
-			case 'G':
-				fmt.Fprintf(buf, "%G", f)
-			}
+			Float(f).format(buf, c)
 		case 'c':
 			switch arg := arg.(type) {
 			case Int:
diff --git a/starlark/eval_test.go b/starlark/eval_test.go
index a57ccad..13876a7 100644
--- a/starlark/eval_test.go
+++ b/starlark/eval_test.go
@@ -24,7 +24,6 @@
 
 // A test may enable non-standard options by containing (e.g.) "option:recursion".
 func setOptions(src string) {
-	resolve.AllowFloat = option(src, "float")
 	resolve.AllowGlobalReassign = option(src, "globalreassign")
 	resolve.LoadBindsGlobally = option(src, "loadbindsglobally")
 	resolve.AllowLambda = option(src, "lambda")
diff --git a/starlark/int.go b/starlark/int.go
index c40532a..397be86 100644
--- a/starlark/int.go
+++ b/starlark/int.go
@@ -193,6 +193,16 @@
 	return Float(iSmall)
 }
 
+// finiteFloat returns the finite float value nearest i,
+// or an error if the magnitude is too large.
+func (i Int) finiteFloat() (Float, error) {
+	f := i.Float()
+	if math.IsInf(float64(f), 0) {
+		return 0, fmt.Errorf("int too large to convert to float")
+	}
+	return f, nil
+}
+
 func (x Int) Sign() int {
 	xSmall, xBig := x.get()
 	if xBig != nil {
diff --git a/starlark/library.go b/starlark/library.go
index 9036877..e9e2f94 100644
--- a/starlark/library.go
+++ b/starlark/library.go
@@ -12,6 +12,7 @@
 import (
 	"errors"
 	"fmt"
+	"math"
 	"math/big"
 	"os"
 	"sort"
@@ -46,7 +47,7 @@
 		"dir":       NewBuiltin("dir", dir),
 		"enumerate": NewBuiltin("enumerate", enumerate),
 		"fail":      NewBuiltin("fail", fail),
-		"float":     NewBuiltin("float", float), // requires resolve.AllowFloat
+		"float":     NewBuiltin("float", float),
 		"getattr":   NewBuiltin("getattr", getattr),
 		"hasattr":   NewBuiltin("hasattr", hasattr),
 		"hash":      NewBuiltin("hash", hash),
@@ -330,13 +331,39 @@
 			return Float(0.0), nil
 		}
 	case Int:
-		return x.Float(), nil
+		return x.finiteFloat()
 	case Float:
 		return x, nil
 	case String:
-		f, err := strconv.ParseFloat(string(x), 64)
+		if x == "" {
+			return nil, fmt.Errorf("float: empty string")
+		}
+		// +/- NaN or Inf or Infinity (case insensitive)?
+		s := string(x)
+		switch x[len(x)-1] {
+		case 'y', 'Y':
+			if strings.EqualFold(s, "infinity") || strings.EqualFold(s, "+infinity") {
+				return inf, nil
+			} else if strings.EqualFold(s, "-infinity") {
+				return neginf, nil
+			}
+		case 'f', 'F':
+			if strings.EqualFold(s, "inf") || strings.EqualFold(s, "+inf") {
+				return inf, nil
+			} else if strings.EqualFold(s, "-inf") {
+				return neginf, nil
+			}
+		case 'n', 'N':
+			if strings.EqualFold(s, "nan") || strings.EqualFold(s, "+nan") || strings.EqualFold(s, "-nan") {
+				return nan, nil
+			}
+		}
+		f, err := strconv.ParseFloat(s, 64)
+		if math.IsInf(f, 0) {
+			return nil, fmt.Errorf("floating-point number too large")
+		}
 		if err != nil {
-			return nil, nameErr(b, err)
+			return nil, fmt.Errorf("invalid float literal: %s", s)
 		}
 		return Float(f), nil
 	default:
@@ -344,6 +371,12 @@
 	}
 }
 
+var (
+	inf    = Float(math.Inf(+1))
+	neginf = Float(math.Inf(-1))
+	nan    = Float(math.NaN())
+)
+
 // https://github.com/google/starlark-go/blob/master/doc/spec.md#getattr
 func getattr(thread *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
 	var object, dflt Value
diff --git a/starlark/testdata/assign.star b/starlark/testdata/assign.star
index 38108f9..64d60fb 100644
--- a/starlark/testdata/assign.star
+++ b/starlark/testdata/assign.star
@@ -232,14 +232,11 @@
 tuple = ()
 
 ---
-# option:float option:set
-# Same as above, but set and float are dialect-specific;
+# option:set
+# Same as above, but set is dialect-specific;
 # we shouldn't notice any difference.
 load("assert.star", "assert")
 
-float = 1.0
-assert.eq(type(float), "float")
-
 set = [1, 2, 3]
 assert.eq(type(set), "list")
 
diff --git a/starlark/testdata/bool.star b/starlark/testdata/bool.star
index 78ef825..6c084a3 100644
--- a/starlark/testdata/bool.star
+++ b/starlark/testdata/bool.star
@@ -1,5 +1,4 @@
 # Tests of Starlark 'bool'
-# option:float
 
 load("assert.star", "assert")
 
@@ -9,6 +8,13 @@
 assert.true(not not True)
 assert.true(not not 1 >= 1)
 
+# precedence of not
+assert.true(not not 2 > 1)
+# assert.true(not (not 2) > 1)   # TODO(adonovan): fix: gives error for False > 1.
+# assert.true(not ((not 2) > 1)) # TODO(adonovan): fix
+# assert.true(not ((not (not 2)) > 1)) # TODO(adonovan): fix
+# assert.true(not not not (2 > 1))
+
 # bool conversion
 assert.eq(
     [bool(), bool(1), bool(0), bool("hello"), bool("")],
diff --git a/starlark/testdata/builtins.star b/starlark/testdata/builtins.star
index 2385c16..8dcee46 100644
--- a/starlark/testdata/builtins.star
+++ b/starlark/testdata/builtins.star
@@ -1,5 +1,5 @@
 # Tests of Starlark built-in functions
-# option:float option:set
+# option:set
 
 load("assert.star", "assert")
 
diff --git a/starlark/testdata/float.star b/starlark/testdata/float.star
index 4d7a269..b4df38d 100644
--- a/starlark/testdata/float.star
+++ b/starlark/testdata/float.star
@@ -1,5 +1,5 @@
 # Tests of Starlark 'float'
-# option:float option:set
+# option:set
 
 load("assert.star", "assert")
 
@@ -7,6 +7,20 @@
 # - precision
 # - limits
 
+# type
+assert.eq(type(0.0), "float")
+
+# truth
+assert.true(123.0)
+assert.true(-1.0)
+assert.true(not 0.0)
+assert.true(-1.0e-45)
+assert.true(float("NaN"))
+
+# not iterable
+assert.fails(lambda: len(0.0), 'has no len')
+assert.fails(lambda: [x for x in 0.0], 'float value is not iterable')
+
 # literals
 assert.eq(type(1.234), "float")
 assert.eq(type(1e10), "float")
@@ -16,10 +30,117 @@
 assert.eq(type(1.234e+10), "float")
 assert.eq(type(1.234e-10), "float")
 
-# truth
-assert.true(123.0)
-assert.true(-1.0)
-assert.true(not 0.0)
+# int/float equality
+assert.eq(0.0, 0)
+assert.eq(0, 0.0)
+assert.eq(1.0, 1)
+assert.eq(1, 1.0)
+assert.true(1.23e45 != 1229999999999999973814869011019624571608236031)
+assert.true(1.23e45 == 1229999999999999973814869011019624571608236032)
+assert.true(1.23e45 != 1229999999999999973814869011019624571608236033)
+assert.true(1229999999999999973814869011019624571608236031 != 1.23e45)
+assert.true(1229999999999999973814869011019624571608236032 == 1.23e45)
+assert.true(1229999999999999973814869011019624571608236033 != 1.23e45)
+
+# loss of precision
+p53 = 1<<53
+assert.eq(float(p53-1), p53-1)
+assert.eq(float(p53+0), p53+0)
+assert.eq(float(p53+1), p53+0) #
+assert.eq(float(p53+2), p53+2)
+assert.eq(float(p53+3), p53+4) #
+assert.eq(float(p53+4), p53+4)
+assert.eq(float(p53+5), p53+4) #
+assert.eq(float(p53+6), p53+6)
+assert.eq(float(p53+7), p53+8) #
+assert.eq(float(p53+8), p53+8)
+
+assert.true(float(p53+1) != p53+1) # comparisons are exact
+assert.eq(float(p53+1) - (p53+1), 0) # arithmetic entails rounding
+
+assert.fails(lambda: {123.0: "f", 123: "i"}, "duplicate key: 123")
+
+# equal int/float values have same hash
+d = {123.0: "x"}
+d[123] = "y"
+assert.eq(len(d), 1)
+assert.eq(d[123.0], "y")
+
+# literals (mostly covered by scanner tests)
+assert.eq(str(0.), "0.0")
+assert.eq(str(.0), "0.0")
+assert.true(5.0 != 4.999999999999999)
+assert.eq(5.0, 4.9999999999999999) # both literals denote 5.0
+assert.eq(1.23e45, 1.23 * 1000000000000000000000000000000000000000000000)
+assert.eq(str(1.23e-45 - (1.23 / 1000000000000000000000000000000000000000000000)), "-1.5557538194652854e-61")
+
+nan = float("NaN")
+inf = float("+Inf")
+neginf = float("-Inf")
+negzero = (-1e-323 / 10)
+
+# -- arithmetic --
+
+# +float, -float
+assert.eq(+(123.0), 123.0)
+assert.eq(-(123.0), -123.0)
+assert.eq(-(-(123.0)), 123.0)
+assert.eq(+(inf), inf)
+assert.eq(-(inf), neginf)
+assert.eq(-(neginf), inf)
+assert.eq(str(-(nan)), "nan")
+# +
+assert.eq(1.2e3 + 5.6e7, 5.60012e+07)
+assert.eq(1.2e3 + 1, 1201)
+assert.eq(1 + 1.2e3, 1201)
+assert.eq(str(1.2e3 + nan), "nan")
+assert.eq(inf + 0, inf)
+assert.eq(inf + 1, inf)
+assert.eq(inf + inf, inf)
+assert.eq(str(inf + neginf), "nan")
+# -
+assert.eq(1.2e3 - 5.6e7, -5.59988e+07)
+assert.eq(1.2e3 - 1, 1199)
+assert.eq(1 - 1.2e3, -1199)
+assert.eq(str(1.2e3 - nan), "nan")
+assert.eq(inf - 0, inf)
+assert.eq(inf - 1, inf)
+assert.eq(str(inf - inf), "nan")
+assert.eq(inf - neginf, inf)
+# *
+assert.eq(1.5e6 * 2.2e3, 3.3e9)
+assert.eq(1.5e6 * 123, 1.845e+08)
+assert.eq(123 * 1.5e6, 1.845e+08)
+assert.eq(str(1.2e3 * nan), "nan")
+assert.eq(str(inf * 0), "nan")
+assert.eq(inf * 1, inf)
+assert.eq(inf * inf, inf)
+assert.eq(inf * neginf, neginf)
+# %
+assert.eq(100.0 % 7.0, 2)
+assert.eq(100.0 % -7.0, -5) # NB: different from Go / Java
+assert.eq(-100.0 % 7.0, 5) # NB: different from Go / Java
+assert.eq(-100.0 % -7.0, -2)
+assert.eq(-100.0 % 7, 5)
+assert.eq(100 % 7.0, 2)
+assert.eq(str(1.2e3 % nan), "nan")
+assert.eq(str(inf % 1), "nan")
+assert.eq(str(inf % inf), "nan")
+assert.eq(str(inf % neginf), "nan")
+# /
+assert.eq(str(100.0 / 7.0), "14.285714285714286")
+assert.eq(str(100 / 7.0), "14.285714285714286")
+assert.eq(str(100.0 / 7), "14.285714285714286")
+assert.eq(str(100.0 / nan), "nan")
+# //
+assert.eq(100.0 // 7.0, 14)
+assert.eq(100 // 7.0, 14)
+assert.eq(100.0 // 7, 14)
+assert.eq(100.0 // -7.0, -15)
+assert.eq(100 // -7.0, -15)
+assert.eq(100.0 // -7, -15)
+assert.eq(str(1 // neginf), "-0.0")
+assert.eq(str(100.0 // nan), "nan")
 
 # addition
 assert.eq(0.0 + 1.0, 1.0)
@@ -56,9 +177,9 @@
 assert.eq(2.5 / 2, 1.25)
 assert.eq(5 / 4.0, 1.25)
 assert.eq(5 / 4, 1.25)
-assert.fails(lambda: 1.0 / 0, "real division by zero")
-assert.fails(lambda: 1.0 / 0.0, "real division by zero")
-assert.fails(lambda: 1 / 0.0, "real division by zero")
+assert.fails(lambda: 1.0 / 0, "floating-point division by zero")
+assert.fails(lambda: 1.0 / 0.0, "floating-point division by zero")
+assert.fails(lambda: 1 / 0.0, "floating-point division by zero")
 
 # floored division
 assert.eq(100.0 // 8.0, 12.0)
@@ -90,64 +211,69 @@
 assert.eq(2.5 % 2.0, 0.5)
 assert.eq(2.5 % 2, 0.5)
 assert.eq(5 % 4.0, 1.0)
-assert.fails(lambda: 1.0 % 0, "float modulo by zero")
-assert.fails(lambda: 1.0 % 0.0, "float modulo by zero")
-assert.fails(lambda: 1 % 0.0, "float modulo by zero")
+assert.fails(lambda: 1.0 % 0, "floating-point modulo by zero")
+assert.fails(lambda: 1.0 % 0.0, "floating-point modulo by zero")
+assert.fails(lambda: 1 % 0.0, "floating-point modulo by zero")
 
 # floats cannot be used as indices, even if integral
 assert.fails(lambda: "abc"[1.0], "want int")
 assert.fails(lambda: ["A", "B", "C"].insert(1.0, "D"), "want int")
+assert.fails(lambda: range(3)[1.0], "got float, want int")
 
-# nan
-nan = float("NaN")
-def isnan(x): return x != x
-assert.true(nan != nan)
-assert.true(not (nan == nan))
+# -- comparisons --
+# NaN
+assert.true(nan == nan) # \
+assert.true(nan >= nan) #  unlike Python
+assert.true(nan <= nan) # /
+assert.true(not (nan > nan))
+assert.true(not (nan < nan))
+assert.true(not (nan != nan)) # unlike Python
+# Sort is stable: 0.0 and -0.0 are equal, but they are not permuted.
+# Similarly 1 and 1.0.
+assert.eq(
+    str(sorted([inf, neginf, nan, 1e300, -1e300, 1.0, -1.0, 1, -1, 1e-300, -1e-300, 0, 0.0, negzero, 1e-300, -1e-300])),
+    "[-inf, -1e+300, -1.0, -1, -1e-300, -1e-300, 0, 0.0, -0.0, 1e-300, 1e-300, 1.0, 1, 1e+300, +inf, nan]")
 
-# ordered comparisons with NaN
-assert.true(not nan < nan)
-assert.true(not nan > nan)
-assert.true(not nan <= nan)
-assert.true(not nan >= nan)
-assert.true(not nan == nan) # use explicit operator, not assert.ne
-assert.true(nan != nan)
-assert.true(not nan < 0)
-assert.true(not nan > 0)
-assert.true(not [nan] < [nan])
-assert.true(not [nan] > [nan])
+# Sort is stable, and its result contains no adjacent x, y such that y > x.
+# Note: Python's reverse sort is unstable; see https://bugs.python.org/issue36095.
+assert.eq(str(sorted([7, 3, nan, 1, 9])), "[1, 3, 7, 9, nan]")
+assert.eq(str(sorted([7, 3, nan, 1, 9], reverse=True)), "[nan, 9, 7, 3, 1]")
 
-# Even a value containing NaN is not equal to itself.
-nanlist = [nan]
-assert.true(not nanlist < nanlist)
-assert.true(not nanlist > nanlist)
-assert.ne(nanlist, nanlist)
+# All NaN values compare equal. (Identical objects compare equal.)
+nandict = {nan: 1}
+nandict[nan] = 2
+assert.eq(len(nandict), 1) # (same as Python)
+assert.eq(nandict[nan], 2) # (same as Python)
+assert.fails(lambda: {nan: 1, nan: 2}, "duplicate key: nan")
 
-# Since NaN values never compare equal,
-# a dict may have any number of NaN keys.
-nandict = {nan: 1, nan: 2, nan: 3}
-assert.eq(len(nandict), 3)
-assert.eq(str(nandict), "{NaN: 1, NaN: 2, NaN: 3}")
-assert.true(nan not in nandict)
-assert.eq(nandict.get(nan, None), None)
+nandict[float('nan')] = 3 # a distinct NaN object
+assert.eq(str(nandict), "{nan: 3}") # (Python: {nan: 2, nan: 3})
+
+assert.eq(str({inf: 1, neginf: 2}), "{+inf: 1, -inf: 2}")
+
+# zero
+assert.eq(0.0, negzero)
 
 # inf
-inf = float("Inf")
-neginf = float("-Inf")
-assert.true(isnan(+inf / +inf))
-assert.true(isnan(+inf / -inf))
-assert.true(isnan(-inf / +inf))
+assert.eq(+inf / +inf, nan)
+assert.eq(+inf / -inf, nan)
+assert.eq(-inf / +inf, nan)
 assert.eq(0.0 / +inf, 0.0)
 assert.eq(0.0 / -inf, 0.0)
 assert.true(inf > -inf)
 assert.eq(inf, -neginf)
-assert.eq(float(int("2" + "0" * 308)), inf) # 2e308 is too large to represent as a float
-assert.eq(float(int("-2" + "0" * 308)), -inf)
 # TODO(adonovan): assert inf > any finite number, etc.
 
 # negative zero
 negz = -0
 assert.eq(negz, 0)
 
+# min/max ordering with NaN (the greatest float value)
+assert.eq(max([1, nan, 3]), nan)
+assert.eq(max([nan, 2, 3]), nan)
+assert.eq(min([1, nan, 3]), 1)
+assert.eq(min([nan, 2, 3]), 2)
+
 # float/float comparisons
 fltmax = 1.7976931348623157e+308 # approx
 fltmin = 4.9406564584124654e-324 # approx
@@ -178,25 +304,75 @@
 assert.fails(lambda: int(inf), "cannot convert.*infinity")
 assert.fails(lambda: int(nan), "cannot convert.*NaN")
 
-# float conversion
+# -- float() function --
 assert.eq(float(), 0.0)
+# float(bool)
 assert.eq(float(False), 0.0)
 assert.eq(float(True), 1.0)
+# float(int)
 assert.eq(float(0), 0.0)
 assert.eq(float(1), 1.0)
+assert.eq(float(123), 123.0)
+assert.eq(float(123 * 1000000 * 1000000 * 1000000 * 1000000 * 1000000), 1.23e+32)
+# float(float)
 assert.eq(float(1.1), 1.1)
-assert.eq(float("1.1"), 1.1)
-assert.fails(lambda: float("1.1abc"), "invalid syntax")
-assert.fails(lambda: float("1e100.0"), "invalid syntax")
-assert.fails(lambda: float("1e1000"), "out of range")
 assert.fails(lambda: float(None), "want number or string")
+assert.ne(False, 0.0) # differs from Python
+assert.ne(True, 1.0)
+# float(string)
+assert.eq(float("1.1"), 1.1)
+assert.fails(lambda: float("1.1abc"), "invalid float literal")
+assert.fails(lambda: float("1e100.0"), "invalid float literal")
+assert.fails(lambda: float("1e1000"), "floating-point number too large")
 assert.eq(float("-1.1"), -1.1)
 assert.eq(float("+1.1"), +1.1)
 assert.eq(float("+Inf"), inf)
 assert.eq(float("-Inf"), neginf)
-assert.true(isnan(float("NaN")))
-assert.fails(lambda: float("+NaN"), "invalid syntax")
-assert.fails(lambda: float("-NaN"), "invalid syntax")
+assert.eq(float("NaN"), nan)
+assert.eq(float("NaN"), nan)
+assert.eq(float("+NAN"), nan)
+assert.eq(float("-nan"), nan)
+assert.eq(str(float("Inf")), "+inf")
+assert.eq(str(float("+INF")), "+inf")
+assert.eq(str(float("-inf")), "-inf")
+assert.eq(str(float("+InFiniTy")), "+inf")
+assert.eq(str(float("-iNFiniTy")), "-inf")
+assert.fails(lambda: float("one point two"), "invalid float literal: one point two")
+assert.fails(lambda: float("1.2.3"), "invalid float literal: 1.2.3")
+assert.fails(lambda: float(123 << 500 << 500 << 50), "int too large to convert to float")
+assert.fails(lambda: float(-123 << 500 << 500 << 50), "int too large to convert to float")
+assert.fails(lambda: float(str(-123 << 500 << 500 << 50)), "floating-point number too large")
+
+# -- implicit float(int) conversions --
+assert.fails(lambda: (1<<500<<500<<500) + 0.0, "int too large to convert to float")
+assert.fails(lambda: 0.0 + (1<<500<<500<<500), "int too large to convert to float")
+assert.fails(lambda: (1<<500<<500<<500) - 0.0, "int too large to convert to float")
+assert.fails(lambda: 0.0 - (1<<500<<500<<500), "int too large to convert to float")
+assert.fails(lambda: (1<<500<<500<<500) * 1.0, "int too large to convert to float")
+assert.fails(lambda: 1.0 * (1<<500<<500<<500), "int too large to convert to float")
+assert.fails(lambda: (1<<500<<500<<500) / 1.0, "int too large to convert to float")
+assert.fails(lambda: 1.0 / (1<<500<<500<<500), "int too large to convert to float")
+assert.fails(lambda: (1<<500<<500<<500) // 1.0, "int too large to convert to float")
+assert.fails(lambda: 1.0 // (1<<500<<500<<500), "int too large to convert to float")
+assert.fails(lambda: (1<<500<<500<<500) % 1.0, "int too large to convert to float")
+assert.fails(lambda: 1.0 % (1<<500<<500<<500), "int too large to convert to float")
+
+
+# -- int function --
+assert.eq(int(0.0), 0)
+assert.eq(int(1.0), 1)
+assert.eq(int(1.1), 1)
+assert.eq(int(0.9), 0)
+assert.eq(int(-1.1), -1.0)
+assert.eq(int(-1.0), -1.0)
+assert.eq(int(-0.9), 0.0)
+assert.eq(int(1.23e+32), 123000000000000004979083645550592)
+assert.eq(int(-1.23e-32), 0)
+assert.eq(int(1.23e-32), 0)
+assert.fails(lambda: int(float("+Inf")), "cannot convert float infinity to integer")
+assert.fails(lambda: int(float("-Inf")), "cannot convert float infinity to integer")
+assert.fails(lambda: int(float("NaN")), "cannot convert float NaN to integer")
+
 
 # hash
 # Check that equal float and int values have the same internal hash.
@@ -214,16 +390,67 @@
 checkhash()
 
 # string formatting
-assert.eq("%s" % 123.45e67, "1.2345e+69")
-assert.eq("%r" % 123.45e67, "1.2345e+69")
-assert.eq("%e" % 123.45e67, "1.234500e+69")
-assert.eq("%f" % 123.45e67, "1234500000000000033987094856609369647752433474509923447907937257783296.000000")
-assert.eq("%g" % 123.45e67, "1.2345e+69")
+
+# %d
+assert.eq("%d" % 0, "0")
+assert.eq("%d" % 0.0, "0")
+assert.eq("%d" % 123, "123")
+assert.eq("%d" % 123.0, "123")
+assert.eq("%d" % 1.23e45, "1229999999999999973814869011019624571608236032")
+# (see below for '%d' % NaN/Inf)
+assert.eq("%d" % negzero, "0")
+assert.fails(lambda: "%d" % float("NaN"), "cannot convert float NaN to integer")
+assert.fails(lambda: "%d" % float("+Inf"), "cannot convert float infinity to integer")
+assert.fails(lambda: "%d" % float("-Inf"), "cannot convert float infinity to integer")
+
+# %e
+assert.eq("%e" % 0, "0.000000e+00")
+assert.eq("%e" % 0.0, "0.000000e+00")
 assert.eq("%e" % 123, "1.230000e+02")
-assert.eq("%f" % 123, "123.000000")
-assert.eq("%g" % 123, "123")
+assert.eq("%e" % 123.0, "1.230000e+02")
+assert.eq("%e" % 1.23e45, "1.230000e+45")
+assert.eq("%e" % -1.23e-45, "-1.230000e-45")
+assert.eq("%e" % nan, "nan")
+assert.eq("%e" % inf, "+inf")
+assert.eq("%e" % neginf, "-inf")
+assert.eq("%e" % negzero, "-0.000000e+00")
 assert.fails(lambda: "%e" % "123", "requires float, not str")
+# %f
+assert.eq("%f" % 0, "0.000000")
+assert.eq("%f" % 0.0, "0.000000")
+assert.eq("%f" % 123, "123.000000")
+assert.eq("%f" % 123.0, "123.000000")
+# Note: Starlark/Java emits 1230000000000000000000000000000000000000000000.000000. Why?
+assert.eq("%f" % 1.23e45, "1229999999999999973814869011019624571608236032.000000")
+assert.eq("%f" % -1.23e-45, "-0.000000")
+assert.eq("%f" % nan, "nan")
+assert.eq("%f" % inf, "+inf")
+assert.eq("%f" % neginf, "-inf")
+assert.eq("%f" % negzero, "-0.000000")
 assert.fails(lambda: "%f" % "123", "requires float, not str")
+# %g
+assert.eq("%g" % 0, "0.0")
+assert.eq("%g" % 0.0, "0.0")
+assert.eq("%g" % 123, "123.0")
+assert.eq("%g" % 123.0, "123.0")
+assert.eq("%g" % 1.110, "1.11")
+assert.eq("%g" % 1e5, "100000.0")
+assert.eq("%g" % 1e6, "1e+06") # Note: threshold of scientific notation is 1e17 in Starlark/Java
+assert.eq("%g" % 1.23e45, "1.23e+45")
+assert.eq("%g" % -1.23e-45, "-1.23e-45")
+assert.eq("%g" % nan, "nan")
+assert.eq("%g" % inf, "+inf")
+assert.eq("%g" % neginf, "-inf")
+assert.eq("%g" % negzero, "-0.0")
+# str uses %g
+assert.eq(str(0.0), "0.0")
+assert.eq(str(123.0), "123.0")
+assert.eq(str(1.23e45), "1.23e+45")
+assert.eq(str(-1.23e-45), "-1.23e-45")
+assert.eq(str(nan), "nan")
+assert.eq(str(inf), "+inf")
+assert.eq(str(neginf), "-inf")
+assert.eq(str(negzero), "-0.0")
 assert.fails(lambda: "%g" % "123", "requires float, not str")
 
 i0 = 1
diff --git a/starlark/testdata/int.star b/starlark/testdata/int.star
index c987fd1..a2e6716 100644
--- a/starlark/testdata/int.star
+++ b/starlark/testdata/int.star
@@ -1,5 +1,4 @@
 # Tests of Starlark 'int'
-# option:float
 
 load("assert.star", "assert")
 
@@ -21,72 +20,72 @@
 assert.eq(maxint32, 2147483647)
 assert.eq(minint32, -2147483648)
 
-
 # truth
 def truth():
-  assert.true(not 0)
-  for m in [1, maxint32]: # Test small/big ranges
-    assert.true(123*m)
-    assert.true(-1*m)
+    assert.true(not 0)
+    for m in [1, maxint32]:  # Test small/big ranges
+        assert.true(123 * m)
+        assert.true(-1 * m)
 
 truth()
 
 # floored division
 # (For real division, see float.star.)
 def division():
-  for m in [1, maxint32]: # Test small/big ranges
-    assert.eq((100*m) // (7*m), 14)
-    assert.eq((100*m) // (-7*m), -15)
-    assert.eq((-100*m) // (7*m), -15) # NB: different from Go/Java
-    assert.eq((-100*m) // (-7*m), 14) # NB: different from Go/Java
-    assert.eq((98*m) // (7*m), 14)
-    assert.eq((98*m) // (-7*m), -14)
-    assert.eq((-98*m) // (7*m), -14)
-    assert.eq((-98*m) // (-7*m), 14)
+    for m in [1, maxint32]:  # Test small/big ranges
+        assert.eq((100 * m) // (7 * m), 14)
+        assert.eq((100 * m) // (-7 * m), -15)
+        assert.eq((-100 * m) // (7 * m), -15)  # NB: different from Go/Java
+        assert.eq((-100 * m) // (-7 * m), 14)  # NB: different from Go/Java
+        assert.eq((98 * m) // (7 * m), 14)
+        assert.eq((98 * m) // (-7 * m), -14)
+        assert.eq((-98 * m) // (7 * m), -14)
+        assert.eq((-98 * m) // (-7 * m), 14)
 
 division()
 
 # remainder
 def remainder():
-  for m in [1, maxint32]: # Test small/big ranges
-    assert.eq((100*m) % (7*m), 2*m)
-    assert.eq((100*m) % (-7*m), -5*m) # NB: different from Go/Java
-    assert.eq((-100*m) % (7*m), 5*m) # NB: different from Go/Java
-    assert.eq((-100*m) % (-7*m), -2*m)
-    assert.eq((98*m) % (7*m), 0)
-    assert.eq((98*m) % (-7*m), 0)
-    assert.eq((-98*m) % (7*m), 0)
-    assert.eq((-98*m) % (-7*m), 0)
+    for m in [1, maxint32]:  # Test small/big ranges
+        assert.eq((100 * m) % (7 * m), 2 * m)
+        assert.eq((100 * m) % (-7 * m), -5 * m)  # NB: different from Go/Java
+        assert.eq((-100 * m) % (7 * m), 5 * m)  # NB: different from Go/Java
+        assert.eq((-100 * m) % (-7 * m), -2 * m)
+        assert.eq((98 * m) % (7 * m), 0)
+        assert.eq((98 * m) % (-7 * m), 0)
+        assert.eq((-98 * m) % (7 * m), 0)
+        assert.eq((-98 * m) % (-7 * m), 0)
 
 remainder()
 
 # compound assignment
 def compound():
-  x = 1
-  x += 1
-  assert.eq(x, 2)
-  x -= 3
-  assert.eq(x, -1)
-  x *= 39
-  assert.eq(x, -39)
-  x //= 4
-  assert.eq(x, -10)
-  x /= -2
-  assert.eq(x, 5)
-  x %= 3
-  assert.eq(x, 2)
-  # use resolve.AllowBitwise to enable the ops:
-  x = 2
-  x &= 1
-  assert.eq(x, 0)
-  x |= 2
-  assert.eq(x, 2)
-  x ^= 3
-  assert.eq(x, 1)
-  x <<= 2
-  assert.eq(x, 4)
-  x >>=2
-  assert.eq(x, 1)
+    x = 1
+    x += 1
+    assert.eq(x, 2)
+    x -= 3
+    assert.eq(x, -1)
+    x *= 39
+    assert.eq(x, -39)
+    x //= 4
+    assert.eq(x, -10)
+    x /= -2
+    assert.eq(x, 5)
+    x %= 3
+    assert.eq(x, 2)
+
+    # use resolve.AllowBitwise to enable the ops:
+    x = 2
+    x &= 1
+    assert.eq(x, 0)
+    x |= 2
+    assert.eq(x, 2)
+    x ^= 3
+    assert.eq(x, 1)
+    x <<= 2
+    assert.eq(x, 4)
+    x >>= 2
+    assert.eq(x, 1)
 
 compound()
 
@@ -94,56 +93,58 @@
 # See float.star for float-to-int conversions.
 # We follow Python 3 here, but I can't see the method in its madness.
 # int from bool/int/float
-assert.fails(int, 'missing argument')  # int()
+assert.fails(int, "missing argument")  # int()
 assert.eq(int(False), 0)
 assert.eq(int(True), 1)
 assert.eq(int(3), 3)
 assert.eq(int(3.1), 3)
-assert.fails(lambda: int(3, base=10), "non-string with explicit base")
+assert.fails(lambda: int(3, base = 10), "non-string with explicit base")
 assert.fails(lambda: int(True, 10), "non-string with explicit base")
+
 # int from string, base implicitly 10
 assert.eq(int("100000000000000000000"), 10000000000 * 10000000000)
 assert.eq(int("-100000000000000000000"), -10000000000 * 10000000000)
 assert.eq(int("123"), 123)
 assert.eq(int("-123"), -123)
-assert.eq(int("0123"), 123) # not octal
+assert.eq(int("0123"), 123)  # not octal
 assert.eq(int("-0123"), -123)
 assert.fails(lambda: int("0x12"), "invalid literal with base 10")
 assert.fails(lambda: int("-0x12"), "invalid literal with base 10")
 assert.fails(lambda: int("0o123"), "invalid literal.*base 10")
 assert.fails(lambda: int("-0o123"), "invalid literal.*base 10")
+
 # int from string, explicit base
 assert.eq(int("0"), 0)
 assert.eq(int("00"), 0)
-assert.eq(int("0", base=10), 0)
-assert.eq(int("00", base=10), 0)
-assert.eq(int("0", base=8), 0)
-assert.eq(int("00", base=8), 0)
+assert.eq(int("0", base = 10), 0)
+assert.eq(int("00", base = 10), 0)
+assert.eq(int("0", base = 8), 0)
+assert.eq(int("00", base = 8), 0)
 assert.eq(int("-0"), 0)
 assert.eq(int("-00"), 0)
-assert.eq(int("-0", base=10), 0)
-assert.eq(int("-00", base=10), 0)
-assert.eq(int("-0", base=8), 0)
-assert.eq(int("-00", base=8), 0)
+assert.eq(int("-0", base = 10), 0)
+assert.eq(int("-00", base = 10), 0)
+assert.eq(int("-0", base = 8), 0)
+assert.eq(int("-00", base = 8), 0)
 assert.eq(int("+0"), 0)
 assert.eq(int("+00"), 0)
-assert.eq(int("+0", base=10), 0)
-assert.eq(int("+00", base=10), 0)
-assert.eq(int("+0", base=8), 0)
-assert.eq(int("+00", base=8), 0)
-assert.eq(int("11", base=9), 10)
-assert.eq(int("-11", base=9), -10)
-assert.eq(int("10011", base=2), 19)
-assert.eq(int("-10011", base=2), -19)
+assert.eq(int("+0", base = 10), 0)
+assert.eq(int("+00", base = 10), 0)
+assert.eq(int("+0", base = 8), 0)
+assert.eq(int("+00", base = 8), 0)
+assert.eq(int("11", base = 9), 10)
+assert.eq(int("-11", base = 9), -10)
+assert.eq(int("10011", base = 2), 19)
+assert.eq(int("-10011", base = 2), -19)
 assert.eq(int("123", 8), 83)
 assert.eq(int("-123", 8), -83)
-assert.eq(int("0123", 8), 83) # redundant zeros permitted
+assert.eq(int("0123", 8), 83)  # redundant zeros permitted
 assert.eq(int("-0123", 8), -83)
 assert.eq(int("00123", 8), 83)
 assert.eq(int("-00123", 8), -83)
 assert.eq(int("0o123", 8), 83)
 assert.eq(int("-0o123", 8), -83)
-assert.eq(int("123", 7), 66) # 1*7*7 + 2*7 + 3
+assert.eq(int("123", 7), 66)  # 1*7*7 + 2*7 + 3
 assert.eq(int("-123", 7), -66)
 assert.eq(int("12", 16), 18)
 assert.eq(int("-12", 16), -18)
@@ -160,6 +161,7 @@
 assert.fails(lambda: int("0o123", 16), "invalid literal.*base 16")
 assert.fails(lambda: int("-0o123", 16), "invalid literal.*base 16")
 assert.fails(lambda: int("0x110", 2), "invalid literal.*base 2")
+
 # int from string, auto detect base
 assert.eq(int("123", 0), 123)
 assert.eq(int("+123", 0), +123)
@@ -170,10 +172,12 @@
 assert.eq(int("0o123", 0), 83)
 assert.eq(int("+0o123", 0), +83)
 assert.eq(int("-0o123", 0), -83)
-assert.fails(lambda: int("0123", 0), "invalid literal.*base 0") # valid in Python 2.7
+assert.fails(lambda: int("0123", 0), "invalid literal.*base 0")  # valid in Python 2.7
 assert.fails(lambda: int("-0123", 0), "invalid literal.*base 0")
+
 # github.com/google/starlark-go/issues/108
 assert.fails(lambda: int("0Oxa", 8), "invalid literal with base 8: 0Oxa")
+
 # follow-on bugs to issue 108
 assert.fails(lambda: int("--4"), "invalid literal with base 10: --4")
 assert.fails(lambda: int("++4"), "invalid literal with base 10: \\+\\+4")
@@ -185,14 +189,14 @@
 # use resolve.AllowBitwise to enable the ops.
 # TODO(adonovan): this is not yet in the Starlark spec,
 # but there is consensus that it should be.
-assert.eq(1|2, 3)
-assert.eq(3|6, 7)
-assert.eq((1|2) & (2|4), 2)
+assert.eq(1 | 2, 3)
+assert.eq(3 | 6, 7)
+assert.eq((1 | 2) & (2 | 4), 2)
 assert.eq(1 ^ 2, 3)
 assert.eq(2 ^ 2, 0)
-assert.eq(1 | 0 ^ 1, 1) # check | and ^ operators precedence
+assert.eq(1 | 0 ^ 1, 1)  # check | and ^ operators precedence
 assert.eq(~1, -2)
-assert.eq(~-2, 1)
+assert.eq(~(-2), 1)
 assert.eq(~0, -1)
 assert.eq(1 << 2, 4)
 assert.eq(2 >> 1, 1)
@@ -202,30 +206,30 @@
 # comparisons
 # TODO(adonovan): test: < > == != etc
 def comparisons():
-  for m in [1, maxint32/2, maxint32]: # Test small/big ranges
-    assert.lt(-2*m, -1*m)
-    assert.lt(-1*m, 0*m)
-    assert.lt(0*m, 1*m)
-    assert.lt(1*m, 2*m)
-    assert.true(2*m >= 2*m)
-    assert.true(2*m > 1*m)
-    assert.true(1*m >= 1*m)
-    assert.true(1*m > 0*m)
-    assert.true(0*m >= 0*m)
-    assert.true(0*m > -1*m)
-    assert.true(-1*m >= -1*m)
-    assert.true(-1*m > -2*m)
+    for m in [1, maxint32 / 2, maxint32]:  # Test small/big ranges
+        assert.lt(-2 * m, -1 * m)
+        assert.lt(-1 * m, 0 * m)
+        assert.lt(0 * m, 1 * m)
+        assert.lt(1 * m, 2 * m)
+        assert.true(2 * m >= 2 * m)
+        assert.true(2 * m > 1 * m)
+        assert.true(1 * m >= 1 * m)
+        assert.true(1 * m > 0 * m)
+        assert.true(0 * m >= 0 * m)
+        assert.true(0 * m > -1 * m)
+        assert.true(-1 * m >= -1 * m)
+        assert.true(-1 * m > -2 * m)
 
 comparisons()
 
 # precision
 assert.eq(str(maxint64), "9223372036854775807")
-assert.eq(str(maxint64+1), "9223372036854775808")
+assert.eq(str(maxint64 + 1), "9223372036854775808")
 assert.eq(str(minint64), "-9223372036854775808")
-assert.eq(str(minint64-1), "-9223372036854775809")
+assert.eq(str(minint64 - 1), "-9223372036854775809")
 assert.eq(str(minint64 * minint64), "85070591730234615865843651857942052864")
-assert.eq(str(maxint32+1), "2147483648")
-assert.eq(str(minint32-1), "-2147483649")
+assert.eq(str(maxint32 + 1), "2147483648")
+assert.eq(str(minint32 - 1), "-2147483649")
 assert.eq(str(minint32 * minint32), "4611686018427387904")
 assert.eq(str(minint32 | maxint32), "-1")
 assert.eq(str(minint32 & minint32), "-2147483648")
@@ -235,11 +239,11 @@
 # string formatting
 assert.eq("%o %x %d" % (0o755, 0xDEADBEEF, 42), "755 deadbeef 42")
 nums = [-95, -1, 0, +1, +95]
-assert.eq(' '.join(["%o" % x for x in nums]), "-137 -1 0 1 137")
-assert.eq(' '.join(["%d" % x for x in nums]), "-95 -1 0 1 95")
-assert.eq(' '.join(["%i" % x for x in nums]), "-95 -1 0 1 95")
-assert.eq(' '.join(["%x" % x for x in nums]), "-5f -1 0 1 5f")
-assert.eq(' '.join(["%X" % x for x in nums]), "-5F -1 0 1 5F")
+assert.eq(" ".join(["%o" % x for x in nums]), "-137 -1 0 1 137")
+assert.eq(" ".join(["%d" % x for x in nums]), "-95 -1 0 1 95")
+assert.eq(" ".join(["%i" % x for x in nums]), "-95 -1 0 1 95")
+assert.eq(" ".join(["%x" % x for x in nums]), "-5f -1 0 1 5f")
+assert.eq(" ".join(["%X" % x for x in nums]), "-5F -1 0 1 5F")
 assert.eq("%o %x %d" % (123, 123, 123), "173 7b 123")
-assert.eq("%o %x %d" % (123.1, 123.1, 123.1), "173 7b 123") # non-int operands are acceptable
+assert.eq("%o %x %d" % (123.1, 123.1, 123.1), "173 7b 123")  # non-int operands are acceptable
 assert.fails(lambda: "%d" % True, "cannot convert bool to int")
diff --git a/starlark/testdata/json.star b/starlark/testdata/json.star
index 615d70b..37c9070 100644
--- a/starlark/testdata/json.star
+++ b/starlark/testdata/json.star
@@ -1,5 +1,4 @@
 # Tests of json module.
-# option:float
 
 load("assert.star", "assert")
 load("json.star", "json")
@@ -28,7 +27,7 @@
 def encode_error(expr, error):
     assert.fails(lambda: json.encode(expr), error)
 
-encode_error(float("NaN"), "json.encode: cannot encode non-finite float NaN")
+encode_error(float("NaN"), "json.encode: cannot encode non-finite float nan")
 encode_error({1: "two"}, "dict has int key, want string")
 encode_error(len, "cannot encode builtin_function_or_method as JSON")
 encode_error(struct(x=[1, {"x": len}]), # nested failure
diff --git a/starlark/testdata/misc.star b/starlark/testdata/misc.star
index 0cb973a..84ef84c 100644
--- a/starlark/testdata/misc.star
+++ b/starlark/testdata/misc.star
@@ -36,8 +36,6 @@
 #   tuple slice
 #   interpolate with %c, %%
 
-# option:float
-
 load("assert.star", "assert")
 
 # Ordered comparisons require values of the same type.
diff --git a/starlark/testdata/string.star b/starlark/testdata/string.star
index 1d78dd7..859f645 100644
--- a/starlark/testdata/string.star
+++ b/starlark/testdata/string.star
@@ -1,10 +1,10 @@
 # Tests of Starlark 'string'
-# option:float option:set
+# option:set
 
 load("assert.star", "assert")
 
 # raw string literals:
-assert.eq(r'a\bc', "a\\bc")
+assert.eq(r"a\bc", "a\\bc")
 
 # truth
 assert.true("abc")
@@ -12,7 +12,7 @@
 assert.true(not "")
 
 # str + str
-assert.eq("a"+"b"+"c", "abc")
+assert.eq("a" + "b" + "c", "abc")
 
 # str * int,  int * str
 assert.eq("abc" * 0, "")
@@ -24,26 +24,26 @@
 assert.eq(1 * "abc", "abc")
 assert.eq(5 * "abc", "abcabcabcabcabc")
 assert.fails(lambda: 1.0 * "abc", "unknown.*float \\* str")
-assert.fails(lambda : "abc" * (1000000 * 1000000), "repeat count 1000000000000 too large")
-assert.fails(lambda : "abc" * 1000000 * 1000000, "excessive repeat \\(3000000 \\* 1000000 elements")
+assert.fails(lambda: "abc" * (1000000 * 1000000), "repeat count 1000000000000 too large")
+assert.fails(lambda: "abc" * 1000000 * 1000000, "excessive repeat \\(3000000 \\* 1000000 elements")
 
 # len
 assert.eq(len("Hello, 世界!"), 14)
-assert.eq(len("𐐷"), 4) # U+10437 has a 4-byte UTF-8 encoding (and a 2-code UTF-16 encoding)
+assert.eq(len("𐐷"), 4)  # U+10437 has a 4-byte UTF-8 encoding (and a 2-code UTF-16 encoding)
 
 # chr & ord
-assert.eq(chr(65), "A")       # 1-byte UTF-8 encoding
-assert.eq(chr(1049), "Й")     # 2-byte UTF-8 encoding
-assert.eq(chr(0x1F63F), "😿") # 4-byte UTF-8 encoding
+assert.eq(chr(65), "A")  # 1-byte UTF-8 encoding
+assert.eq(chr(1049), "Й")  # 2-byte UTF-8 encoding
+assert.eq(chr(0x1F63F), "😿")  # 4-byte UTF-8 encoding
 assert.fails(lambda: chr(-1), "Unicode code point -1 out of range \\(<0\\)")
 assert.fails(lambda: chr(0x110000), "Unicode code point U\\+110000 out of range \\(>0x10FFFF\\)")
 assert.eq(ord("A"), 65)
 assert.eq(ord("Й"), 1049)
 assert.eq(ord("😿"), 0x1F63F)
-assert.eq(ord("Й"[1:]), 0xFFFD) # = Unicode replacement character
+assert.eq(ord("Й"[1:]), 0xFFFD)  # = Unicode replacement character
 assert.fails(lambda: ord("abc"), "string encodes 3 Unicode code points, want 1")
 assert.fails(lambda: ord(""), "string encodes 0 Unicode code points, want 1")
-assert.fails(lambda: ord("😿"[1:]), "string encodes 3 Unicode code points, want 1") # 3 x 0xFFFD
+assert.fails(lambda: ord("😿"[1:]), "string encodes 3 Unicode code points, want 1")  # 3 x 0xFFFD
 
 # string.codepoint_ords
 assert.eq(type("abcЙ😿".codepoint_ords()), "codepoints")
@@ -62,17 +62,21 @@
 # string.elem_ords
 assert.eq(type("abcЙ😿".elem_ords()), "elems")
 assert.eq(str("abcЙ😿".elem_ords()), '"abcЙ😿".elem_ords()')
-assert.eq(list("abcЙ😿".elem_ords()), [97, 98, 99,  208, 153, 240, 159, 152, 191])
-assert.eq(list(("A" + "😿Z"[1:]).elem_ords()),  [65, 159, 152, 191, 90])
+assert.eq(list("abcЙ😿".elem_ords()), [97, 98, 99, 208, 153, 240, 159, 152, 191])
+assert.eq(list(("A" + "😿Z"[1:]).elem_ords()), [65, 159, 152, 191, 90])
 assert.eq(list("".elem_ords()), [])
 
 # string.elems
 assert.eq(type("abcЙ😿".elems()), "elems")
 assert.eq(str("abcЙ😿".elems()), '"abcЙ😿".elems()')
-assert.eq(list("abcЙ😿".elems()),
-          ["a", "b", "c", "\xd0", "\x99", "\xf0", "\x9f", "\x98", "\xbf"])
-assert.eq(list(("A" + "😿Z"[1:]).elems()),
-          ["A", "\x9f", "\x98", "\xbf", "Z"])
+assert.eq(
+    list("abcЙ😿".elems()),
+    ["a", "b", "c", "\xd0", "\x99", "\xf0", "\x9f", "\x98", "\xbf"],
+)
+assert.eq(
+    list(("A" + "😿Z"[1:]).elems()),
+    ["A", "\x9f", "\x98", "\xbf", "Z"],
+)
 assert.eq(list("".elems()), [])
 
 # indexing, x[i]
@@ -90,7 +94,10 @@
 
 # x[i] = ...
 x2 = "abc"
-def f(): x2[1] = 'B'
+
+def f():
+    x2[1] = "B"
+
 assert.fails(f, "string.*does not support.*assignment")
 
 # slicing, x[i:j]
@@ -115,6 +122,7 @@
 assert.eq("abc"[:4], "abc")
 assert.eq("abc"[1:2], "b")
 assert.eq("abc"[2:1], "")
+
 # non-unit strides
 assert.eq("abcd"[0:4:1], "abcd")
 assert.eq("abcd"[::2], "ac")
@@ -139,7 +147,7 @@
 assert.fails(lambda: "" in 1, "unknown binary op: string in int")
 
 # ==, !=
-assert.eq("hello", "he"+"llo")
+assert.eq("hello", "he" + "llo")
 assert.ne("hello", "Hello")
 
 # hash must follow java.lang.String.hashCode.
@@ -157,12 +165,13 @@
 
 # string % tuple formatting
 assert.eq("A %d %x Z" % (123, 456), "A 123 1c8 Z")
-assert.eq("A %(foo)d %(bar)s Z" % {"foo": 123, "bar":"hi"}, "A 123 hi Z")
-assert.eq("%s %r" % ("hi", "hi"), 'hi "hi"') # TODO(adonovan): use ''-quotation
+assert.eq("A %(foo)d %(bar)s Z" % {"foo": 123, "bar": "hi"}, "A 123 hi Z")
+assert.eq("%s %r" % ("hi", "hi"), 'hi "hi"')  # TODO(adonovan): use ''-quotation
 assert.eq("%%d %d" % 1, "%d 1")
 assert.fails(lambda: "%d %d" % 1, "not enough arguments for format string")
 assert.fails(lambda: "%d %d" % (1, 2, 3), "too many arguments for format string")
 assert.fails(lambda: "" % 1, "too many arguments for format string")
+
 # %c
 assert.eq("%c" % 65, "A")
 assert.eq("%c" % 0x3b1, "α")
@@ -181,31 +190,31 @@
 assert.eq("a{{b".format(), "a{b")
 assert.eq("a}}b".format(), "a}b")
 assert.eq("a{{b}}c".format(), "a{b}c")
-assert.eq("a{x}b{y}c{}".format(1, x=2, y=3), "a2b3c1")
-assert.fails(lambda: "a{z}b".format(x=1), "keyword z not found")
+assert.eq("a{x}b{y}c{}".format(1, x = 2, y = 3), "a2b3c1")
+assert.fails(lambda: "a{z}b".format(x = 1), "keyword z not found")
 assert.fails(lambda: "{-1}".format(1), "keyword -1 not found")
 assert.fails(lambda: "{-0}".format(1), "keyword -0 not found")
 assert.fails(lambda: "{+0}".format(1), "keyword \\+0 not found")
-assert.fails(lambda: "{+1}".format(1), "keyword \\+1 not found") # starlark-go/issues/114
+assert.fails(lambda: "{+1}".format(1), "keyword \\+1 not found")  # starlark-go/issues/114
 assert.eq("{0000000000001}".format(0, 1), "1")
-assert.eq("{012}".format(*range(100)), "12") # decimal, despite leading zeros
-assert.fails(lambda: '{0,1} and {1}'.format(1, 2), "keyword 0,1 not found")
+assert.eq("{012}".format(*range(100)), "12")  # decimal, despite leading zeros
+assert.fails(lambda: "{0,1} and {1}".format(1, 2), "keyword 0,1 not found")
 assert.fails(lambda: "a{123}b".format(), "tuple index out of range")
 assert.fails(lambda: "a{}b{}c".format(1), "tuple index out of range")
-assert.eq("a{010}b".format(0,1,2,3,4,5,6,7,8,9,10), "a10b") # index is decimal
+assert.eq("a{010}b".format(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10), "a10b")  # index is decimal
 assert.fails(lambda: "a{}b{1}c".format(1, 2), "cannot switch from automatic field numbering to manual")
 assert.eq("a{!s}c".format("b"), "abc")
 assert.eq("a{!r}c".format("b"), r'a"b"c')
-assert.eq("a{x!r}c".format(x='b'), r'a"b"c')
-assert.fails(lambda: "{x!}".format(x=1), "unknown conversion")
-assert.fails(lambda: "{x!:}".format(x=1), "unknown conversion")
-assert.fails(lambda: '{a.b}'.format(1), "syntax x.y is not supported")
-assert.fails(lambda: '{a[0]}'.format(1), "syntax a\\[i\\] is not supported")
-assert.fails(lambda: '{ {} }'.format(1), "nested replacement fields not supported")
-assert.fails(lambda: '{{}'.format(1), "single '}' in format")
-assert.fails(lambda: '{}}'.format(1), "single '}' in format")
-assert.fails(lambda: '}}{'.format(1), "unmatched '{' in format")
-assert.fails(lambda: '}{{'.format(1), "single '}' in format")
+assert.eq("a{x!r}c".format(x = "b"), r'a"b"c')
+assert.fails(lambda: "{x!}".format(x = 1), "unknown conversion")
+assert.fails(lambda: "{x!:}".format(x = 1), "unknown conversion")
+assert.fails(lambda: "{a.b}".format(1), "syntax x.y is not supported")
+assert.fails(lambda: "{a[0]}".format(1), "syntax a\\[i\\] is not supported")
+assert.fails(lambda: "{ {} }".format(1), "nested replacement fields not supported")
+assert.fails(lambda: "{{}".format(1), "single '}' in format")
+assert.fails(lambda: "{}}".format(1), "single '}' in format")
+assert.fails(lambda: "}}{".format(1), "unmatched '{' in format")
+assert.fails(lambda: "}{{".format(1), "single '}' in format")
 
 # str.split, str.rsplit
 assert.eq("a.b.c.d".split("."), ["a", "b", "c", "d"])
@@ -242,44 +251,46 @@
 assert.eq(" a bc\n  def \t  ghi ".rsplit(None, 1), [" a bc\n  def", "ghi"])
 
 # Observe the algorithmic difference when splitting on spaces versus other delimiters.
-assert.eq('--aa--bb--cc--'.split('-', 0), ['--aa--bb--cc--'])  # contrast this
-assert.eq('  aa  bb  cc  '.split(None, 0), ['aa  bb  cc  '])   #  with this
-assert.eq('--aa--bb--cc--'.rsplit('-', 0), ['--aa--bb--cc--']) # ditto this
-assert.eq('  aa  bb  cc  '.rsplit(None, 0), ['  aa  bb  cc'])  #  and this
+assert.eq("--aa--bb--cc--".split("-", 0), ["--aa--bb--cc--"])  # contrast this
+assert.eq("  aa  bb  cc  ".split(None, 0), ["aa  bb  cc  "])  #  with this
+assert.eq("--aa--bb--cc--".rsplit("-", 0), ["--aa--bb--cc--"])  # ditto this
+assert.eq("  aa  bb  cc  ".rsplit(None, 0), ["  aa  bb  cc"])  #  and this
+
 #
-assert.eq('--aa--bb--cc--'.split('-', 1), ['', '-aa--bb--cc--'])
-assert.eq('--aa--bb--cc--'.rsplit('-', 1), ['--aa--bb--cc-', ''])
-assert.eq('  aa  bb  cc  '.split(None, 1), ['aa', 'bb  cc  '])
-assert.eq('  aa  bb  cc  '.rsplit(None, 1), ['  aa  bb',  'cc'])
+assert.eq("--aa--bb--cc--".split("-", 1), ["", "-aa--bb--cc--"])
+assert.eq("--aa--bb--cc--".rsplit("-", 1), ["--aa--bb--cc-", ""])
+assert.eq("  aa  bb  cc  ".split(None, 1), ["aa", "bb  cc  "])
+assert.eq("  aa  bb  cc  ".rsplit(None, 1), ["  aa  bb", "cc"])
+
 #
-assert.eq('--aa--bb--cc--'.split('-', -1), ['', '', 'aa', '', 'bb', '', 'cc', '', ''])
-assert.eq('--aa--bb--cc--'.rsplit('-', -1), ['', '', 'aa', '', 'bb', '', 'cc', '', ''])
-assert.eq('  aa  bb  cc  '.split(None, -1), ['aa', 'bb', 'cc'])
-assert.eq('  aa  bb  cc  '.rsplit(None, -1), ['aa', 'bb', 'cc'])
-assert.eq('  '.split(None), [])
-assert.eq('  '.rsplit(None), [])
+assert.eq("--aa--bb--cc--".split("-", -1), ["", "", "aa", "", "bb", "", "cc", "", ""])
+assert.eq("--aa--bb--cc--".rsplit("-", -1), ["", "", "aa", "", "bb", "", "cc", "", ""])
+assert.eq("  aa  bb  cc  ".split(None, -1), ["aa", "bb", "cc"])
+assert.eq("  aa  bb  cc  ".rsplit(None, -1), ["aa", "bb", "cc"])
+assert.eq("  ".split(None), [])
+assert.eq("  ".rsplit(None), [])
 
 assert.eq("localhost:80".rsplit(":", 1)[-1], "80")
 
 # str.splitlines
-assert.eq('\nabc\ndef'.splitlines(), ['', 'abc', 'def'])
-assert.eq('\nabc\ndef'.splitlines(True), ['\n', 'abc\n', 'def'])
-assert.eq('\nabc\ndef\n'.splitlines(), ['', 'abc', 'def'])
-assert.eq('\nabc\ndef\n'.splitlines(True), ['\n', 'abc\n', 'def\n'])
-assert.eq(''.splitlines(), []) #
-assert.eq(''.splitlines(True), []) #
-assert.eq('a'.splitlines(), ['a'])
-assert.eq('a'.splitlines(True), ['a'])
-assert.eq('\n'.splitlines(), [''])
-assert.eq('\n'.splitlines(True), ['\n'])
-assert.eq('a\n'.splitlines(), ['a'])
-assert.eq('a\n'.splitlines(True), ['a\n'])
-assert.eq('a\n\nb'.splitlines(), ['a', '', 'b'])
-assert.eq('a\n\nb'.splitlines(True), ['a\n', '\n', 'b'])
-assert.eq('a\nb\nc'.splitlines(), ['a', 'b', 'c'])
-assert.eq('a\nb\nc'.splitlines(True), ['a\n', 'b\n', 'c'])
-assert.eq('a\nb\nc\n'.splitlines(), ['a', 'b', 'c'])
-assert.eq('a\nb\nc\n'.splitlines(True), ['a\n', 'b\n', 'c\n'])
+assert.eq("\nabc\ndef".splitlines(), ["", "abc", "def"])
+assert.eq("\nabc\ndef".splitlines(True), ["\n", "abc\n", "def"])
+assert.eq("\nabc\ndef\n".splitlines(), ["", "abc", "def"])
+assert.eq("\nabc\ndef\n".splitlines(True), ["\n", "abc\n", "def\n"])
+assert.eq("".splitlines(), [])  #
+assert.eq("".splitlines(True), [])  #
+assert.eq("a".splitlines(), ["a"])
+assert.eq("a".splitlines(True), ["a"])
+assert.eq("\n".splitlines(), [""])
+assert.eq("\n".splitlines(True), ["\n"])
+assert.eq("a\n".splitlines(), ["a"])
+assert.eq("a\n".splitlines(True), ["a\n"])
+assert.eq("a\n\nb".splitlines(), ["a", "", "b"])
+assert.eq("a\n\nb".splitlines(True), ["a\n", "\n", "b"])
+assert.eq("a\nb\nc".splitlines(), ["a", "b", "c"])
+assert.eq("a\nb\nc".splitlines(True), ["a\n", "b\n", "c"])
+assert.eq("a\nb\nc\n".splitlines(), ["a", "b", "c"])
+assert.eq("a\nb\nc\n".splitlines(True), ["a\n", "b\n", "c\n"])
 
 # str.{,l,r}strip
 assert.eq(" \tfoo\n ".strip(), "foo")
@@ -305,23 +316,26 @@
 assert.true("foo".startswith("fo"))
 assert.true(not "foo".startswith("x"))
 assert.fails(lambda: "foo".startswith(1), "got int.*want string")
+
 #
-assert.true('abc'.startswith(('a', 'A')))
-assert.true('ABC'.startswith(('a', 'A')))
-assert.true(not 'ABC'.startswith(('b', 'B')))
-assert.fails(lambda: '123'.startswith((1, 2)), 'got int, for element 0')
-assert.fails(lambda: '123'.startswith(['3']), 'got list')
+assert.true("abc".startswith(("a", "A")))
+assert.true("ABC".startswith(("a", "A")))
+assert.true(not "ABC".startswith(("b", "B")))
+assert.fails(lambda: "123".startswith((1, 2)), "got int, for element 0")
+assert.fails(lambda: "123".startswith(["3"]), "got list")
+
 #
-assert.true('abc'.endswith(('c', 'C')))
-assert.true('ABC'.endswith(('c', 'C')))
-assert.true(not 'ABC'.endswith(('b', 'B')))
-assert.fails(lambda: '123'.endswith((1, 2)), 'got int, for element 0')
-assert.fails(lambda: '123'.endswith(['3']), 'got list')
+assert.true("abc".endswith(("c", "C")))
+assert.true("ABC".endswith(("c", "C")))
+assert.true(not "ABC".endswith(("b", "B")))
+assert.fails(lambda: "123".endswith((1, 2)), "got int, for element 0")
+assert.fails(lambda: "123".endswith(["3"]), "got list")
+
 # start/end
-assert.true('abc'.startswith('bc', 1))
-assert.true(not 'abc'.startswith('b', 999))
-assert.true('abc'.endswith('ab', None, -1))
-assert.true(not 'abc'.endswith('b', None, -999))
+assert.true("abc".startswith("bc", 1))
+assert.true(not "abc".startswith("b", 999))
+assert.true("abc".endswith("ab", None, -1))
+assert.true(not "abc".endswith("b", None, -999))
 
 # str.replace
 assert.eq("banana".replace("a", "o", 1), "bonana")
@@ -346,71 +360,77 @@
 assert.fails(lambda: "foo/bar/wiz".partition(""), "empty separator")
 assert.fails(lambda: "foo/bar/wiz".rpartition(""), "empty separator")
 
-assert.eq('?'.join(["foo", "a/b/c.go".rpartition("/")[0]]), 'foo?a/b')
+assert.eq("?".join(["foo", "a/b/c.go".rpartition("/")[0]]), "foo?a/b")
 
 # str.is{alpha,...}
 def test_predicates():
-  predicates = ["alnum", "alpha", "digit", "lower", "space", "title", "upper"]
-  table = {
-      "Hello, World!": "title",
-      "hello, world!": "lower",
-      "base64": "alnum lower",
-      "HAL-9000": "upper",
-      "Catch-22": "title",
-      "": "",
-      "\n\t\r": "space",
-      "abc": "alnum alpha lower",
-      "ABC": "alnum alpha upper",
-      "123": "alnum digit",
-      "DŽLJ": "alnum alpha upper",
-      "DžLj": "alnum alpha",
-      "Dž Lj": "title",
-      "džlj": "alnum alpha lower",
-  }
-  for str, want in table.items():
-    got = ' '.join([name for name in predicates if getattr(str, "is"+name)()])
-    if got != want:
-      assert.fail("%r matched [%s], want [%s]" % (str, got, want))
+    predicates = ["alnum", "alpha", "digit", "lower", "space", "title", "upper"]
+    table = {
+        "Hello, World!": "title",
+        "hello, world!": "lower",
+        "base64": "alnum lower",
+        "HAL-9000": "upper",
+        "Catch-22": "title",
+        "": "",
+        "\n\t\r": "space",
+        "abc": "alnum alpha lower",
+        "ABC": "alnum alpha upper",
+        "123": "alnum digit",
+        "DŽLJ": "alnum alpha upper",
+        "DžLj": "alnum alpha",
+        "Dž Lj": "title",
+        "džlj": "alnum alpha lower",
+    }
+    for str, want in table.items():
+        got = " ".join([name for name in predicates if getattr(str, "is" + name)()])
+        if got != want:
+            assert.fail("%r matched [%s], want [%s]" % (str, got, want))
+
 test_predicates()
 
 # Strings are not iterable.
 # ok
-assert.eq(len("abc"), 3)                       # len
-assert.true("a" in "abc")                      # str in str
-assert.eq("abc"[1], "b")                       # indexing
+assert.eq(len("abc"), 3)  # len
+assert.true("a" in "abc")  # str in str
+assert.eq("abc"[1], "b")  # indexing
+
 # not ok
 def for_string():
-  for x in "abc":
-    pass
-def args(*args): return args
-assert.fails(lambda: args(*"abc"), "must be iterable, not string") # varargs
-assert.fails(lambda: list("abc"), "got string, want iterable") # list(str)
-assert.fails(lambda: tuple("abc"), "got string, want iterable") # tuple(str)
-assert.fails(lambda: set("abc"), "got string, want iterable") # set(str)
+    for x in "abc":
+        pass
+
+def args(*args):
+    return args
+
+assert.fails(lambda: args(*"abc"), "must be iterable, not string")  # varargs
+assert.fails(lambda: list("abc"), "got string, want iterable")  # list(str)
+assert.fails(lambda: tuple("abc"), "got string, want iterable")  # tuple(str)
+assert.fails(lambda: set("abc"), "got string, want iterable")  # set(str)
 assert.fails(lambda: set() | "abc", "unknown binary op: set | string")  # set union
-assert.fails(lambda: enumerate("ab"), "got string, want iterable") # enumerate
-assert.fails(lambda: sorted("abc"), "got string, want iterable") # sorted
-assert.fails(lambda: [].extend("bc"), "got string, want iterable") # list.extend
-assert.fails(lambda: ",".join("abc"), "got string, want iterable") # string.join
-assert.fails(lambda: dict(["ab"]), "not iterable .*string") # dict
+assert.fails(lambda: enumerate("ab"), "got string, want iterable")  # enumerate
+assert.fails(lambda: sorted("abc"), "got string, want iterable")  # sorted
+assert.fails(lambda: [].extend("bc"), "got string, want iterable")  # list.extend
+assert.fails(lambda: ",".join("abc"), "got string, want iterable")  # string.join
+assert.fails(lambda: dict(["ab"]), "not iterable .*string")  # dict
+
 # The Java implementation does not correctly reject the following cases:
 # (See Google Issue b/34385336)
-assert.fails(for_string, "string value is not iterable") # for loop
-assert.fails(lambda: [x for x in "abc"], "string value is not iterable") # comprehension
-assert.fails(lambda: all("abc"), "got string, want iterable") # all
-assert.fails(lambda: any("abc"), "got string, want iterable") # any
-assert.fails(lambda: reversed("abc"), "got string, want iterable") # reversed
-assert.fails(lambda: zip("ab", "cd"), "not iterable: string") # zip
+assert.fails(for_string, "string value is not iterable")  # for loop
+assert.fails(lambda: [x for x in "abc"], "string value is not iterable")  # comprehension
+assert.fails(lambda: all("abc"), "got string, want iterable")  # all
+assert.fails(lambda: any("abc"), "got string, want iterable")  # any
+assert.fails(lambda: reversed("abc"), "got string, want iterable")  # reversed
+assert.fails(lambda: zip("ab", "cd"), "not iterable: string")  # zip
 
 # str.join
-assert.eq(','.join([]), '')
-assert.eq(','.join(["a"]), 'a')
-assert.eq(','.join(["a", "b"]), 'a,b')
-assert.eq(','.join(["a", "b", "c"]), 'a,b,c')
-assert.eq(','.join(("a", "b", "c")), 'a,b,c')
-assert.eq(''.join(("a", "b", "c")), 'abc')
-assert.fails(lambda: ''.join(None), 'got NoneType, want iterable')
-assert.fails(lambda: ''.join(["one", 2]), 'join: in list, want string, got int')
+assert.eq(",".join([]), "")
+assert.eq(",".join(["a"]), "a")
+assert.eq(",".join(["a", "b"]), "a,b")
+assert.eq(",".join(["a", "b", "c"]), "a,b,c")
+assert.eq(",".join(("a", "b", "c")), "a,b,c")
+assert.eq("".join(("a", "b", "c")), "abc")
+assert.fails(lambda: "".join(None), "got NoneType, want iterable")
+assert.fails(lambda: "".join(["one", 2]), "join: in list, want string, got int")
 
 # TODO(adonovan): tests for: {,r}index
 
diff --git a/starlark/value.go b/starlark/value.go
index eb0e84e..8d1b88a 100644
--- a/starlark/value.go
+++ b/starlark/value.go
@@ -385,10 +385,47 @@
 // Float is the type of a Starlark float.
 type Float float64
 
-func (f Float) String() string { return strconv.FormatFloat(float64(f), 'g', 6, 64) }
-func (f Float) Type() string   { return "float" }
-func (f Float) Freeze()        {} // immutable
-func (f Float) Truth() Bool    { return f != 0.0 }
+func (f Float) String() string {
+	var buf strings.Builder
+	f.format(&buf, 'g')
+	return buf.String()
+}
+
+func (f Float) format(buf *strings.Builder, conv byte) {
+	ff := float64(f)
+	if !isFinite(ff) {
+		if math.IsInf(ff, +1) {
+			buf.WriteString("+inf")
+		} else if math.IsInf(ff, -1) {
+			buf.WriteString("-inf")
+		} else {
+			buf.WriteString("nan")
+		}
+		return
+	}
+
+	// %g is the default format used by str.
+	// It uses the minimum precision to avoid ambiguity,
+	// and always includes a '.' or an 'e' so that the value
+	// is self-evidently a float, not an int.
+	if conv == 'g' || conv == 'G' {
+		s := strconv.FormatFloat(ff, conv, -1, 64)
+		buf.WriteString(s)
+		// Ensure result always has a decimal point if no exponent.
+		// "123" -> "123.0"
+		if strings.IndexByte(s, conv-'g'+'e') < 0 && strings.IndexByte(s, '.') < 0 {
+			buf.WriteString(".0")
+		}
+		return
+	}
+
+	// %[eEfF] use 6-digit precision
+	buf.WriteString(strconv.FormatFloat(ff, conv, 6, 64))
+}
+
+func (f Float) Type() string { return "float" }
+func (f Float) Freeze()      {} // immutable
+func (f Float) Truth() Bool  { return f != 0.0 }
 func (f Float) Hash() (uint32, error) {
 	// Equal float and int values must yield the same hash.
 	// TODO(adonovan): opt: if f is non-integral, and thus not equal
@@ -409,27 +446,34 @@
 
 func (x Float) CompareSameType(op syntax.Token, y_ Value, depth int) (bool, error) {
 	y := y_.(Float)
-	switch op {
-	case syntax.EQL:
-		return x == y, nil
-	case syntax.NEQ:
-		return x != y, nil
-	case syntax.LE:
-		return x <= y, nil
-	case syntax.LT:
-		return x < y, nil
-	case syntax.GE:
-		return x >= y, nil
-	case syntax.GT:
-		return x > y, nil
+	return threeway(op, floatCmp(x, y)), nil
+}
+
+// floatCmp performs a three-valued comparison on floats,
+// which are totally ordered with NaN > +Inf.
+func floatCmp(x, y Float) int {
+	if x > y {
+		return +1
+	} else if x < y {
+		return -1
+	} else if x == y {
+		return 0
 	}
-	panic(op)
+
+	// At least one operand is NaN.
+	if x == x {
+		return -1 // y is NaN
+	} else if y == y {
+		return +1 // x is NaN
+	}
+	return 0 // both NaN
 }
 
 func (f Float) rational() *big.Rat { return new(big.Rat).SetFloat64(float64(f)) }
 
 // AsFloat returns the float64 value closest to x.
-// The f result is undefined if x is not a float or int.
+// The f result is undefined if x is not a float or Int.
+// The result may be infinite if x is a very large Int.
 func AsFloat(x Value) (f float64, ok bool) {
 	switch x := x.(type) {
 	case Float:
@@ -1199,11 +1243,10 @@
 	switch x := x.(type) {
 	case Int:
 		if y, ok := y.(Float); ok {
-			if y != y {
-				return false, nil // y is NaN
-			}
 			var cmp int
-			if !math.IsInf(float64(y), 0) {
+			if y != y {
+				cmp = -1 // y is NaN
+			} else if !math.IsInf(float64(y), 0) {
 				cmp = x.rational().Cmp(y.rational()) // y is finite
 			} else if y > 0 {
 				cmp = -1 // y is +Inf
@@ -1214,16 +1257,15 @@
 		}
 	case Float:
 		if y, ok := y.(Int); ok {
-			if x != x {
-				return false, nil // x is NaN
-			}
 			var cmp int
-			if !math.IsInf(float64(x), 0) {
+			if x != x {
+				cmp = +1 // x is NaN
+			} else if !math.IsInf(float64(x), 0) {
 				cmp = x.rational().Cmp(y.rational()) // x is finite
 			} else if x > 0 {
-				cmp = -1 // x is +Inf
+				cmp = +1 // x is +Inf
 			} else {
-				cmp = +1 // x is -Inf
+				cmp = -1 // x is -Inf
 			}
 			return threeway(op, cmp), nil
 		}
diff --git a/starlarkstruct/struct_test.go b/starlarkstruct/struct_test.go
index 8e6a93d..5de7020 100644
--- a/starlarkstruct/struct_test.go
+++ b/starlarkstruct/struct_test.go
@@ -19,7 +19,6 @@
 	// The tests make extensive use of these not-yet-standard features.
 	resolve.AllowLambda = true
 	resolve.AllowNestedDef = true
-	resolve.AllowFloat = true
 	resolve.AllowSet = true
 }