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/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
 		}