diff --git a/std-bits/base/src/main/java/org/enso/base/polyglot/NumericConverter.java b/std-bits/base/src/main/java/org/enso/base/polyglot/NumericConverter.java new file mode 100644 index 000000000000..d1af2066aa7f --- /dev/null +++ b/std-bits/base/src/main/java/org/enso/base/polyglot/NumericConverter.java @@ -0,0 +1,108 @@ +package org.enso.base.polyglot; + +import java.math.BigDecimal; + +/** + * The numeric converter deals with conversions of Java numeric types to the two main types + * supported by Enso - Long for integers and Double for decimals. Any other types are coerced to one + * of these types. + * + *
It provides two concepts - coercion - which allows to coerce an integer type to a decimal, but + * will not convert a decimal to an integer even if it has 0 fractional part. Then there is + * conversion which allows to convert a decimal with 0 fractional part to an integer. Conversion + * should be used when we care about the original type of the object (i.e. we want any decimals to + * require decimal storage even if they have 0 fractional part). Conversion is to be used when we + * want to be consistent with Enso's equality semantics where 2 == 2.0. + */ +public class NumericConverter { + /** + * Coerces a number (possibly an integer) to a Double. + * + *
Will throw an exception if the object is not a number. + */ + public static double coerceToDouble(Object o) { + return switch (o) { + case Double x -> x; + case BigDecimal x -> x.doubleValue(); + case Float x -> x.doubleValue(); + default -> (double) coerceToLong(o); + }; + } + + /** + * Coerces a number to an Integer. + * + *
Will throw an exception if the object is not an integer. + * + *
Decimal values are not accepted. + */ + public static long coerceToLong(Object o) { + return switch (o) { + case Long x -> x; + case Integer x -> x.longValue(); + case Short x -> x.longValue(); + case Byte x -> x.longValue(); + default -> throw new UnsupportedOperationException(); + }; + } + + /** Returns true if the object is any supported number. */ + public static boolean isCoercibleToDouble(Object o) { + return o instanceof Double + || o instanceof BigDecimal + || o instanceof Float + || isCoercibleToLong(o); + } + + /** + * Returns true if the object is any supported integer. + * + *
Returns false for decimals with 0 fractional part - the type itself must be an integer type. + */ + public static boolean isCoercibleToLong(Object o) { + return o instanceof Long || o instanceof Integer || o instanceof Short || o instanceof Byte; + } + + /** + * Tries converting the value to a Double. + * + *
It will return null if the object represented a non-numeric value. + */ + public static Double tryConvertingToDouble(Object o) { + return switch (o) { + case Double x -> x; + case BigDecimal x -> x.doubleValue(); + case Float x -> x.doubleValue(); + case Long x -> x.doubleValue(); + case Integer x -> x.doubleValue(); + case Short x -> x.doubleValue(); + case Byte x -> x.doubleValue(); + case null, default -> null; + }; + } + + /** + * Tries converting the value to a Long. + * + *
Decimal number types are accepted, only if their fractional part is 0. It will return null + * if the object represented a non-integer value. + */ + public static Long tryConvertingToLong(Object o) { + return switch (o) { + case Long x -> x; + case Integer x -> x.longValue(); + case Short x -> x.longValue(); + case Byte x -> x.longValue(); + case Double x -> x % 1.0 == 0.0 ? x.longValue() : null; + case Float x -> x % 1.0f == 0.0f ? x.longValue() : null; + case BigDecimal x -> { + try { + yield x.longValueExact(); + } catch (ArithmeticException e) { + yield null; + } + } + case null, default -> null; + }; + } +} diff --git a/std-bits/base/src/main/java/org/enso/base/Polyglot_Utils.java b/std-bits/base/src/main/java/org/enso/base/polyglot/Polyglot_Utils.java similarity index 56% rename from std-bits/base/src/main/java/org/enso/base/Polyglot_Utils.java rename to std-bits/base/src/main/java/org/enso/base/polyglot/Polyglot_Utils.java index 23abd642bdbe..14d0592ce762 100644 --- a/std-bits/base/src/main/java/org/enso/base/Polyglot_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/polyglot/Polyglot_Utils.java @@ -1,11 +1,14 @@ -package org.enso.base; - -import org.graalvm.polyglot.Value; +package org.enso.base.polyglot; import java.time.LocalDate; import java.time.LocalDateTime; +import org.graalvm.polyglot.Value; public class Polyglot_Utils { + /** + * Converts a polyglot Value ensuring that various date/time types are converted to the correct + * type. + */ public static Object convertPolyglotValue(Value item) { if (item.isDate()) { LocalDate d = item.asDate(); @@ -26,12 +29,14 @@ public static Object convertPolyglotValue(Value item) { return item.as(Object.class); } - /** A helper functions for situations where we cannot use the Value conversion directly. - *
- * Mostly happens due to the issue: https://github.com/oracle/graal/issues/4967 - * Once that issue is resolved, we should probably remove this helper. - *
- * In that case we take a generic Object, knowing that the values of interest to us will be passed as Value anyway - so we can check that and fire the conversion if needed. + /** + * A helper functions for situations where we cannot use the Value conversion directly. + * + *
Mostly happens due to the issue: https://github.com/oracle/graal/issues/4967 Once that issue + * is resolved, we should probably remove this helper. + * + *
In that case we take a generic Object, knowing that the values of interest to us will be
+ * passed as Value anyway - so we can check that and fire the conversion if needed.
*/
public static Object convertPolyglotValue(Object item) {
if (item instanceof Value v) {
diff --git a/std-bits/table/src/main/java/org/enso/table/data/NumericConverter.java b/std-bits/table/src/main/java/org/enso/table/data/NumericConverter.java
deleted file mode 100644
index 8c1fc2fd27f2..000000000000
--- a/std-bits/table/src/main/java/org/enso/table/data/NumericConverter.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.enso.table.data;
-
-import java.math.BigDecimal;
-
-public class NumericConverter {
- public static double toDouble(Object o) {
- return switch (o) {
- case Double x -> x;
- case BigDecimal x -> x.doubleValue();
- default -> (double) toLong(o);
- };
- }
-
- public static long toLong(Object o) {
- return switch (o) {
- case Long x -> x;
- case Integer x -> x.longValue();
- case Short x -> x.longValue();
- case Byte x -> x.longValue();
- default -> throw new UnsupportedOperationException();
- };
- }
-
- public static boolean isCoercibleToDouble(Object o) {
- return o instanceof Double || o instanceof BigDecimal || isCoercibleToLong(o);
- }
-
- public static boolean isCoercibleToLong(Object o) {
- return o instanceof Long || o instanceof Integer || o instanceof Short || o instanceof Byte;
- }
-
- public static Double tryToDouble(Object o) {
- return switch (o) {
- case Double x -> x;
- case BigDecimal x -> x.doubleValue();
- case Long x -> x.doubleValue();
- case Integer x -> x.doubleValue();
- case Short x -> x.doubleValue();
- case Byte x -> x.doubleValue();
- case null, default -> null;
- };
- }
-
- public static Long tryToLong(Object o) {
- return switch (o) {
- case Long x -> x;
- case Integer x -> x.longValue();
- case Short x -> x.longValue();
- case Byte x -> x.longValue();
- case null, default -> null;
- };
- }
-}
diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/object/InferredBuilder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/object/InferredBuilder.java
index 68b3bce03008..52cac4487e86 100644
--- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/object/InferredBuilder.java
+++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/object/InferredBuilder.java
@@ -1,5 +1,6 @@
package org.enso.table.data.column.builder.object;
+import org.enso.base.polyglot.NumericConverter;
import org.enso.table.data.column.storage.Storage;
import java.math.BigDecimal;
@@ -81,10 +82,10 @@ private void initBuilderFor(Object o) {
int initialCapacity = Math.max(initialSize, currentSize);
if (o instanceof Boolean) {
currentBuilder = new BoolBuilder();
- } else if (o instanceof Double || o instanceof BigDecimal) {
- currentBuilder = NumericBuilder.createDoubleBuilder(initialCapacity);
- } else if (o instanceof Long) {
+ } else if (NumericConverter.isCoercibleToLong(o)) {
currentBuilder = NumericBuilder.createLongBuilder(initialCapacity);
+ } else if (NumericConverter.isCoercibleToDouble(o)) {
+ currentBuilder = NumericBuilder.createDoubleBuilder(initialCapacity);
} else if (o instanceof LocalDate) {
currentBuilder = new DateBuilder(initialCapacity);
} else if (o instanceof LocalTime) {
@@ -106,11 +107,16 @@ private record RetypeInfo(Class> clazz, int type) {}
new RetypeInfo(Boolean.class, Storage.Type.BOOL),
new RetypeInfo(Long.class, Storage.Type.LONG),
new RetypeInfo(Double.class, Storage.Type.DOUBLE),
+ new RetypeInfo(String.class, Storage.Type.STRING),
new RetypeInfo(BigDecimal.class, Storage.Type.DOUBLE),
new RetypeInfo(LocalDate.class, Storage.Type.DATE),
new RetypeInfo(LocalTime.class, Storage.Type.TIME_OF_DAY),
new RetypeInfo(ZonedDateTime.class, Storage.Type.DATE_TIME),
- new RetypeInfo(String.class, Storage.Type.STRING));
+ new RetypeInfo(Float.class, Storage.Type.DOUBLE),
+ new RetypeInfo(Integer.class, Storage.Type.LONG),
+ new RetypeInfo(Short.class, Storage.Type.LONG),
+ new RetypeInfo(Byte.class, Storage.Type.LONG)
+ );
private void retypeAndAppend(Object o) {
for (RetypeInfo info : retypePairs) {
diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/object/NumericBuilder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/object/NumericBuilder.java
index dac4fe9885e3..ff9835713478 100644
--- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/object/NumericBuilder.java
+++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/object/NumericBuilder.java
@@ -1,11 +1,10 @@
package org.enso.table.data.column.builder.object;
-import org.enso.table.data.NumericConverter;
+import org.enso.base.polyglot.NumericConverter;
import org.enso.table.data.column.storage.DoubleStorage;
import org.enso.table.data.column.storage.LongStorage;
import org.enso.table.data.column.storage.Storage;
-import java.math.BigDecimal;
import java.util.Arrays;
import java.util.BitSet;
@@ -70,10 +69,10 @@ public void appendNoGrow(Object o) {
if (o == null) {
isMissing.set(currentSize++);
} else if (isDouble) {
- double value = NumericConverter.toDouble(o);
+ double value = NumericConverter.coerceToDouble(o);
data[currentSize++] = Double.doubleToRawLongBits(value);
} else {
- data[currentSize++] = NumericConverter.toLong(o);
+ data[currentSize++] = NumericConverter.coerceToLong(o);
}
}
diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/aggregate/FunctionAggregator.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/aggregate/FunctionAggregator.java
index 048bdeba739a..a50f08c35d9a 100644
--- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/aggregate/FunctionAggregator.java
+++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/aggregate/FunctionAggregator.java
@@ -1,6 +1,6 @@
package org.enso.table.data.column.operation.aggregate;
-import org.enso.base.Polyglot_Utils;
+import org.enso.base.polyglot.Polyglot_Utils;
import org.enso.table.data.column.builder.object.InferredBuilder;
import org.enso.table.data.column.storage.Storage;
import org.graalvm.polyglot.Value;
diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/SpecializedIsInOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/SpecializedIsInOp.java
index fc10eceec680..bc3c056dd514 100644
--- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/SpecializedIsInOp.java
+++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/SpecializedIsInOp.java
@@ -1,37 +1,68 @@
package org.enso.table.data.column.operation.map;
-import org.enso.base.Polyglot_Utils;
-import org.enso.table.data.NumericConverter;
-import org.enso.table.data.column.storage.BoolStorage;
-import org.enso.table.data.column.storage.Storage;
-import org.graalvm.polyglot.Value;
-
import java.util.BitSet;
import java.util.HashSet;
import java.util.List;
import java.util.function.Function;
+import org.enso.base.polyglot.Polyglot_Utils;
+import org.enso.table.data.column.storage.BoolStorage;
+import org.enso.table.data.column.storage.Storage;
+/**
+ * A specialized implementation for the IS_IN operation for builtin types, relying on hashing. Since
+ * for some columns we know what types of objects can be stored, we can filter out any objects that
+ * do not match that type and then rely on a consistent definition of hashcode for these builtin
+ * types (which is not available in general for custom objects).
+ */
public class SpecializedIsInOp It indicates whether the vector contained a null value and contains a hashmap of the vector
+ * elements for faster contains checks.
+ */
public record CompactRepresentation(HashSet