aboutsummaryrefslogtreecommitdiff
path: root/starlark/value.go
diff options
context:
space:
mode:
Diffstat (limited to 'starlark/value.go')
-rw-r--r--starlark/value.go100
1 files changed, 71 insertions, 29 deletions
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 @@ func (x Bool) CompareSameType(op syntax.Token, y_ Value, depth int) (bool, error
// 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 isFinite(f float64) bool {
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 @@ func CompareDepth(op syntax.Token, x, y Value, depth int) (bool, error) {
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 @@ func CompareDepth(op syntax.Token, x, y Value, depth int) (bool, error) {
}
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
}