blob: 1d0b416f7e7ff40a9c040fe8877a8ae5b8b72929 [file] [log] [blame]
// Copyright 2020 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package net.starlark.java.eval;
import java.math.BigInteger;
import net.starlark.java.annot.StarlarkBuiltin;
/** The Starlark float data type. */
@StarlarkBuiltin(
name = "float",
category = "core",
doc = "The type of floating-point numbers in Starlark.")
public final class StarlarkFloat implements StarlarkValue, Comparable<StarlarkFloat> {
private final double v;
private StarlarkFloat(double v) {
this.v = v;
}
/** Returns the Starlark float value that represents x. */
public static StarlarkFloat of(double v) {
return new StarlarkFloat(v);
}
/** Returns the value of this float. */
public double toDouble() {
return v;
}
@Override
public String toString() {
return format(v, 'g');
}
@Override
public void repr(Printer printer) {
printer.append(toString());
}
@Override
public boolean isImmutable() {
return true;
}
@Override
public boolean truth() {
return this.v != 0.0;
}
/**
* Defines a total order over float values. Positive and negative zero values compare equal. NaN
* compares equal to itself and greater than +Inf.
*/
@Override
public int compareTo(StarlarkFloat that) {
double x = this.v;
double y = that.v;
if (x > y) {
return +1;
} else if (x < y) {
return -1;
} else if (x == y) {
return 0; // 0.0 == -0.0
}
// At least one operand is NaN.
// Canonicalize NaNs using doubleToLongBits and compare bits.
long xbits = Double.doubleToLongBits(x);
long ybits = Double.doubleToLongBits(y);
return Long.compare(xbits, ybits); // NaN > non-NaN
}
@Override
public int hashCode() {
// Equal float and int values must yield the same hash.
if (Double.isFinite(v) && v == Math.rint(v)) {
return StarlarkInt.ofFiniteDouble(v).hashCode();
}
// For non-integral values we can use a cheaper hash.
// Hashing the bits is consistent with equals
// because v is neither 0.0 nor -0.0.
long bits = Double.doubleToLongBits(v); // canonicalizes NaNs
return (int) (bits ^ (bits >>> 32));
}
@Override
public boolean equals(Object that) {
return (that instanceof StarlarkFloat && equal(this.v, ((StarlarkFloat) that).v))
|| (that instanceof StarlarkInt && StarlarkInt.intEqualsFloat((StarlarkInt) that, this));
}
// equal is an equivalence relation consistent with hashCode and compareTo.
private static boolean equal(double x, double y) {
return x == y || (Double.isNaN(x) && Double.isNaN(y));
}
// Performs printf-style string conversion of a double value v.
// conv is one of [efgEFG].
static String format(double v, char conv) {
if (!Double.isFinite(v)) {
if (v == Double.POSITIVE_INFINITY) {
return "+inf";
} else if (v == Double.NEGATIVE_INFINITY) {
return "-inf";
} else {
return "nan";
}
}
String s;
switch (conv) {
case 'e':
s = String.format("%e", v);
break;
case 'E':
s = String.format("%E", v);
break;
case 'f':
case 'F': // an alias
s = String.format("%f", v);
break;
case 'g':
s = String.format("%.17g", v); // use DBL_DECIMAL_DIG places
break;
case 'G':
s = String.format("%.17G", v);
break;
default:
throw new IllegalArgumentException("unsupported conversion: " + conv);
}
// %g is the default format used by str.
// It always includes a '.' or an 'e', to make clear that
// the value is a float, not an int.
//
// TODO(adonovan): round the value to the minimal precision required
// to avoid ambiguity. This requires computing the decimal digit
// strings of the adjacent floating-point values and then taking the
// shortest prefix sufficient to distinguish v from them, or using a
// more sophisticated algorithm such as Florian Loitsch's Grisu3 or
// Ulf Adams' Ryu. (Is there an easy way to compute the required
// precision without materializing the digits? If so we could delegate
// to format("%*g", prec, v).)
//
// For now, we just clean up the output of Java's %.17g implementation,
// which is unambiguous, but may yield unnecessarily long digit strings
// such as 1000000000000.0.
if (conv == 'g' || conv == 'G') {
int e = s.indexOf(conv == 'g' ? 'e' : 'E');
if (e < 0) {
int dot = s.indexOf('.');
if (dot < 0) {
// Ensure result always has a decimal point if no exponent.
// "123" -> "123.0"
s += ".0";
} else {
// Remove trailing zeros after decimal point.
// "1.110" => "1.11"
// "1.000" => "1.0"
int i;
for (i = s.length() - 1; i > dot + 1 && s.charAt(i) == '0'; i--) {}
s = s.substring(0, i + 1);
}
} else {
// Remove trailing zeros from mantissa.
// "1.23000e+45" => "1.23e+45"
// "1.00000e+45" => "1e+45"
int i;
for (i = e - 1; s.charAt(i) == '0'; i--) {}
if (s.charAt(i) == '.') {
i--;
}
if (i < e - 1) {
s =
new StringBuilder(i + 1 + s.length() - e)
.append(s, 0, i + 1) // "1.23"
.append(s, e, s.length()) // "e+45"
.toString();
}
}
}
return s;
}
/** Returns x // y (floor of division). */
static StarlarkFloat floordiv(double x, double y) throws EvalException {
if (y == 0.0) {
throw Starlark.errorf("integer division by zero");
}
return StarlarkFloat.of(Math.floor(x / y));
}
/** Returns x / y (floating-point division). */
static StarlarkFloat div(double x, double y) throws EvalException {
if (y == 0.0) {
throw Starlark.errorf("floating-point division by zero");
}
return StarlarkFloat.of(x / y);
}
/** Returns x % y (floating-point remainder). */
static StarlarkFloat mod(double x, double y) throws EvalException {
if (y == 0.0) {
throw Starlark.errorf("floating-point modulo by zero");
}
// In Starlark, the sign of the result is the sign of the divisor.
double z = x % y;
if ((x < 0) != (y < 0) && z != 0) {
z += y;
}
return StarlarkFloat.of(z);
}
/**
* Returns the Starlark int value closest to x, truncating towards zero.
*
* @throws IllegalArgumentException if x is not finite (NaN or ±Inf).
*/
static StarlarkInt finiteDoubleToIntExact(double x) {
// small value?
if (Long.MIN_VALUE <= x && x <= Long.MAX_VALUE) {
return StarlarkInt.of((long) x);
}
// Shift must be positive, because we just handled all small values.
int shift = getShift(x);
if (shift <= 0) {
throw new IllegalStateException("non-positive shift");
}
// Shift mantissa by exponent.
long mantissa = getMantissa(x);
return StarlarkInt.of(BigInteger.valueOf(mantissa).shiftLeft(shift));
}
private static final int EXPONENT_MASK = (1 << 11) - 1;
// Returns the effective signed mantissa of x.
// Precondition: x is finite.
static long getMantissa(double x) {
long bits = Double.doubleToRawLongBits(x);
long mantissa = bits & ((1L << 52) - 1);
int exp = ((int) (bits >>> 52)) & EXPONENT_MASK;
switch (exp) {
case 0: // denormal
break;
case EXPONENT_MASK:
throw new IllegalArgumentException("not finite: " + x);
default: // normal
mantissa |= 1L << 52;
break;
}
return x < 0 ? -mantissa : mantissa;
}
// Returns the effective left (+) or right (-) shift required of the value returned by
// getMantissa(x).
// Precondition: x is finite.
static int getShift(double x) {
long bits = Double.doubleToRawLongBits(x);
int exp = ((int) (bits >>> 52)) & EXPONENT_MASK;
switch (exp) {
case 0: // denormal
exp -= 1022;
break;
case EXPONENT_MASK:
throw new IllegalArgumentException("not finite: " + x);
default: // normal
exp -= 1023;
break;
}
return exp - 52;
}
}