diff --git a/distribution/lib/Standard/AWS/0.0.0-dev/src/Database/Redshift/Internal/Redshift_Dialect.enso b/distribution/lib/Standard/AWS/0.0.0-dev/src/Database/Redshift/Internal/Redshift_Dialect.enso index a7a5b76db889..14e4823e18a8 100644 --- a/distribution/lib/Standard/AWS/0.0.0-dev/src/Database/Redshift/Internal/Redshift_Dialect.enso +++ b/distribution/lib/Standard/AWS/0.0.0-dev/src/Database/Redshift/Internal/Redshift_Dialect.enso @@ -1,9 +1,9 @@ from Standard.Base import all -from Standard.Table import Aggregate_Column -from Standard.Table import Value_Type +from Standard.Table import Aggregate_Column, Value_Type import Standard.Database.Connection.Connection.Connection +import Standard.Database.Data.Column.Column import Standard.Database.Data.Dialect import Standard.Database.Data.SQL.Builder import Standard.Database.Data.SQL_Statement.SQL_Statement @@ -168,3 +168,10 @@ type Redshift_Dialect fetch_primary_key : Connection -> Text -> Vector Text ! Nothing fetch_primary_key self connection table_name = Dialect.default_fetch_primary_key connection table_name + + ## PRIVATE + value_type_for_upload_of_existing_column : Column -> Value_Type + value_type_for_upload_of_existing_column self column = + ## TODO special behaviour for big integer columns should be added here, once we start testing this dialect again + See: https://docs.aws.amazon.com/redshift/latest/dg/r_Numeric_types201.html#r_Numeric_types201-decimal-or-numeric-type + column.value_type diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Dialect.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Dialect.enso index c4a3e93948c1..02e45f15594e 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Dialect.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Dialect.enso @@ -5,6 +5,7 @@ import Standard.Table.Internal.Problem_Builder.Problem_Builder from Standard.Table import Aggregate_Column, Join_Kind, Value_Type import project.Connection.Connection.Connection +import project.Data.Column.Column import project.Data.SQL.Builder import project.Data.SQL_Statement.SQL_Statement import project.Data.SQL_Type.SQL_Type @@ -231,6 +232,17 @@ type Dialect _ = [replace_params, action] Unimplemented.throw "This is an interface only." + ## PRIVATE + Determines the value type to use when uploading the given column to the + Database. + + This will usually just be `column.value_type`, but it allows the database + to do custom fallback handling for datatypes that are not supported. + value_type_for_upload_of_existing_column : Column -> Value_Type + value_type_for_upload_of_existing_column self column = + _ = column + Unimplemented.throw "This is an interface only." + ## PRIVATE The dialect of SQLite databases. diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso index a52ebbc6dd4e..c8dc2f54dc27 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Data/Table.enso @@ -2027,7 +2027,15 @@ type Table False -> sql = preprocessed.to_sql column_type_suggestions = preprocessed.internal_columns.map .sql_type_reference - self.connection.read_statement sql column_type_suggestions + materialized_table = self.connection.read_statement sql column_type_suggestions + + expected_types = self.columns.map .value_type + actual_types = materialized_table.columns.map .value_type + expected_types.zip actual_types . fold materialized_table acc-> types_pair-> + expected_type = types_pair.first + actual_type = types_pair.second + if expected_type == actual_type then acc else + Warning.attach (Inexact_Type_Coercion.Warning expected_type actual_type) acc ## PRIVATE Creates a query corresponding to this table. diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Column_Fetcher.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Column_Fetcher.enso index 15754f394240..2ee25853bfe9 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Column_Fetcher.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Column_Fetcher.enso @@ -1,4 +1,5 @@ from Standard.Base import all +import Standard.Base.Errors.Illegal_Argument.Illegal_Argument import Standard.Table.Data.Column.Column as Materialized_Column import Standard.Table.Data.Type.Value_Type.Bits @@ -83,6 +84,18 @@ long_fetcher bits = Builder.Value append (seal_java_builder java_builder) Column_Fetcher.Value fetch_value make_builder +## PRIVATE +big_integer_fetcher : Column_Fetcher +big_integer_fetcher = + fetch_value rs i = + big_decimal = rs.getBigDecimal i + if rs.wasNull then Nothing else + big_decimal.toBigIntegerExact + make_builder initial_size = + java_builder = Java_Exports.make_biginteger_builder initial_size + make_builder_from_java_object_builder java_builder + Column_Fetcher.Value fetch_value make_builder + ## PRIVATE text_fetcher : Value_Type -> Column_Fetcher text_fetcher value_type = @@ -145,6 +158,14 @@ default_fetcher_for_value_type value_type = Value_Type.Time -> time_fetcher # We currently don't distinguish timestamps without a timezone on the Enso value side. Value_Type.Date_Time _ -> date_time_fetcher + # If we can determine that scale = 0 + Value_Type.Decimal _ scale -> + is_guaranteed_integer = scale.is_nothing.not && scale <= 0 + case is_guaranteed_integer of + True -> big_integer_fetcher + # If we cannot guarantee that the column is integer, we will fall back to Float values, since there is no BigDecimal implementation yet. + # TODO I think we should add a warning somewhere + False -> double_fetcher _ -> fallback_fetcher ## PRIVATE diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Dialect.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Dialect.enso index ac5b031f5700..30c7366a9f2e 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Dialect.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Dialect.enso @@ -4,6 +4,7 @@ import Standard.Base.Errors.Illegal_State.Illegal_State import Standard.Base.Errors.Unimplemented.Unimplemented import Standard.Table.Data.Aggregate_Column.Aggregate_Column +import Standard.Table.Data.Column.Column as Materialized_Column import Standard.Table.Internal.Problem_Builder.Problem_Builder import Standard.Table.Internal.Vector_Builder.Vector_Builder from Standard.Table import Value_Type @@ -252,6 +253,29 @@ type Postgres_Dialect if_replace_params_supports self replace_params ~action = if supported_replace_params.contains replace_params then action else replace_params.throw_unsupported + + ## PRIVATE + value_type_for_upload_of_existing_column : Column -> Value_Type + value_type_for_upload_of_existing_column self column = case column of + # Return the type as-is for database columns. + _ : Column -> column.value_type + _ : Materialized_Column -> + base_type = column.value_type + case base_type of + Value_Type.Decimal precision scale -> + # We cannot have a specified scale and no precision, so special handling is needed for this: + case precision.is_nothing && scale.is_nothing.not of + True -> + needed_precision = column.java_column.getStorage.getMaxPrecisionStored + new_type = case needed_precision <= 1000 of + # If the precision is small enough that our number will fit, we create a column with maximum supported precision. + True -> Value_Type.Decimal 1000 scale + # If the needed precision is too big, we cannot set it, so we set the precision to unlimited. This loses scale. + False -> Value_Type.Decimal Nothing Nothing + Warning.attach (Inexact_Type_Coercion.Warning base_type new_type) new_type + False -> base_type + _ -> base_type + ## PRIVATE make_internal_generator_dialect = cases = [["LOWER", Base_Generator.make_function "LOWER"], ["UPPER", Base_Generator.make_function "UPPER"]] diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Type_Mapping.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Type_Mapping.enso index 3ddc4db109f5..f283e9d8bfa6 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Type_Mapping.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Postgres/Postgres_Type_Mapping.enso @@ -34,8 +34,11 @@ type Postgres_Type_Mapping SQL_Type.Value Types.REAL "float4" Value_Type.Float Bits.Bits_64 -> SQL_Type.Value Types.DOUBLE "float8" - Value_Type.Decimal precision scale -> - SQL_Type.Value Types.DECIMAL "decimal" precision scale + Value_Type.Decimal precision scale -> case precision of + # If precision is not set, scale is also lost because SQL is unable to express a scale without a precision. + Nothing -> SQL_Type.Value Types.DECIMAL "decimal" Nothing Nothing + # Scale can be set or not, if precision is given, so no check needed. + _ -> SQL_Type.Value Types.DECIMAL "decimal" precision scale Value_Type.Char size variable -> case variable of True -> diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQL_Type_Mapping.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQL_Type_Mapping.enso index 5d80cad5348d..c01c0fff4abc 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQL_Type_Mapping.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQL_Type_Mapping.enso @@ -1,4 +1,5 @@ from Standard.Base import all +import Standard.Base.Errors.Illegal_Argument.Illegal_Argument import Standard.Base.Errors.Unimplemented.Unimplemented import Standard.Table.Data.Type.Value_Type.Value_Type @@ -90,7 +91,11 @@ type SQL_Type_Mapping ## PRIVATE default_sql_type_to_text sql_type = - suffix = if sql_type.precision.is_nothing then "" else - if sql_type.scale.is_nothing then "(" + sql_type.precision.to_text + ")" else - " (" + sql_type.precision.to_text + "," + sql_type.scale.to_text + ")" + suffix = case sql_type.precision of + Nothing -> + if sql_type.scale.is_nothing.not then Error.throw (Illegal_Argument.Error "It is not possible to specify a scale but no precision in SQL, but got "+sql_type.to_text) else + "" + _ : Integer -> + if sql_type.scale.is_nothing then "(" + sql_type.precision.to_text + ")" else + " (" + sql_type.precision.to_text + "," + sql_type.scale.to_text + ")" sql_type.name.trim + suffix diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQLite/SQLite_Dialect.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQLite/SQLite_Dialect.enso index 79acb0ff4718..137839243224 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQLite/SQLite_Dialect.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/SQLite/SQLite_Dialect.enso @@ -3,9 +3,8 @@ import Standard.Base.Errors.Illegal_Argument.Illegal_Argument import Standard.Base.Errors.Illegal_State.Illegal_State import Standard.Base.Runtime.Ref.Ref -import Standard.Table.Data.Aggregate_Column.Aggregate_Column import Standard.Table.Internal.Problem_Builder.Problem_Builder -from Standard.Table import Value_Type +from Standard.Table import Value_Type, Aggregate_Column from Standard.Table.Data.Aggregate_Column.Aggregate_Column import all import project.Connection.Connection.Connection @@ -269,6 +268,10 @@ type SQLite_Dialect if_replace_params_supports self replace_params ~action = if supported_replace_params.contains replace_params then action else replace_params.throw_unsupported + ## PRIVATE + value_type_for_upload_of_existing_column : Column -> Value_Type + value_type_for_upload_of_existing_column self column = column.value_type + ## PRIVATE make_internal_generator_dialect = text = [starts_with, contains, ends_with, make_case_sensitive, ["REPLACE", replace]]+concat_ops+trim_ops diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Statement_Setter.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Statement_Setter.enso index ad956f73ee19..da6e7da878f8 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Statement_Setter.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Statement_Setter.enso @@ -1,9 +1,11 @@ from Standard.Base import all import Standard.Base.Errors.Illegal_State.Illegal_State +polyglot java import java.math.BigDecimal as Java_Big_Decimal polyglot java import java.sql.PreparedStatement polyglot java import java.sql.Types as Java_Types +polyglot java import org.enso.base.polyglot.NumericConverter polyglot java import org.enso.database.JDBCUtils type Statement_Setter @@ -31,7 +33,11 @@ type Statement_Setter fill_hole_default stmt i value = case value of Nothing -> stmt.setNull i Java_Types.NULL _ : Boolean -> stmt.setBoolean i value - _ : Integer -> stmt.setLong i value + _ : Integer -> case NumericConverter.isBigInteger value of + True -> + big_decimal = NumericConverter.bigIntegerAsBigDecimal value + stmt.setBigDecimal i big_decimal + False -> stmt.setLong i value _ : Decimal -> stmt.setDouble i value _ : Text -> stmt.setString i value _ : Date_Time -> JDBCUtils.setZonedDateTime stmt i value diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Upload_Table.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Upload_Table.enso index b0306ead10f6..af873d713dda 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Upload_Table.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Upload_Table.enso @@ -59,7 +59,7 @@ create_table_implementation connection table_name structure primary_key temporar Does not check if the table already exists - so if it does, it may fail with `SQL_Error`. The caller should perform the check for better error handling. internal_create_table_structure connection table_name structure primary_key temporary on_problems = - aligned_structure = align_structure structure + aligned_structure = align_structure connection structure resolved_primary_key = resolve_primary_key aligned_structure primary_key validate_structure connection.base_connection.column_naming_helper aligned_structure <| create_table_statement = prepare_create_table_statement connection table_name aligned_structure resolved_primary_key temporary on_problems @@ -217,21 +217,26 @@ raise_duplicated_primary_key_error source_table primary_key original_panic = ## PRIVATE align_structure : Database_Table | In_Memory_Table | Vector Column_Description -> Vector Column_Description -align_structure table_or_columns = case table_or_columns of - vector : Vector -> if vector.is_empty then Error.throw (Illegal_Argument.Error "A table with no columns cannot be created. The `structure` must consist of at list one column description.") else +align_structure connection table_or_columns = case table_or_columns of + vector : Vector -> align_vector_structure vector + table : Database_Table -> structure_from_existing_table connection table + table : In_Memory_Table -> structure_from_existing_table connection table + +## PRIVATE +align_vector_structure vector = + if vector.is_empty then Error.throw (Illegal_Argument.Error "A table with no columns cannot be created. The `structure` must consist of at list one column description.") else vector.map def-> case def of _ : Column_Description -> def _ : Function -> Error.throw (Illegal_Argument.Error "The structure should be a vector of Column_Description. Maybe some arguments of Column_Description are missing?") _ -> Error.throw (Illegal_Argument.Error "The structure must be an existing Table or vector of Column_Description.") - table : Database_Table -> structure_from_existing_table table - table : In_Memory_Table -> structure_from_existing_table table ## PRIVATE -structure_from_existing_table table = +structure_from_existing_table connection table = table.columns.map column-> - Column_Description.Value column.name column.value_type + value_type = connection.dialect.value_type_for_upload_of_existing_column column + Column_Description.Value column.name value_type ## PRIVATE Verifies that the provided structure is valid, and runs the provided action @@ -255,9 +260,10 @@ validate_structure column_naming_helper structure ~action = Returns the name of the first column in the provided table structure. It also verifies that the structure is correct. Used to provide the default value for `primary_key` in `create_table`. -first_column_name_in_structure structure = - aligned = align_structure structure - aligned.first.name +first_column_name_in_structure structure = case structure of + vector : Vector -> align_vector_structure vector . first . name + table : Database_Table -> table.column_names.first + table : In_Memory_Table -> table.column_names.first ## PRIVATE Creates a statement that will create a table with structure determined by the diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso index 162dd375280d..9fac83cbd802 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column.enso @@ -60,7 +60,7 @@ type Column example_from_vector = Column.from_vector "My Column" [1, 2, 3, 4, 5] from_vector : Text -> Vector -> Value_Type | Auto -> Column ! Invalid_Value_Type - from_vector name items value_type=Auto = + from_vector (name : Text) (items : Vector) (value_type : Auto | Value_Type = Auto) = ## If the type does not accept date-time-like values, we can skip the additional logic for polyglot conversions that would normally be used, which is quite costly - so if we can guarantee it is unnecessary, @@ -1118,12 +1118,12 @@ type Column common_type.if_not_error <| storage = self.java_column.getStorage storage_type = Storage.from_value_type_strict common_type - new_st = case default of + new_st = Java_Problems.unpack_value_with_aggregated_problems Problem_Behavior.Report_Warning <| case default of Column.Value java_col -> other_storage = java_col.getStorage storage.fillMissingFrom other_storage storage_type _ -> - storage.fillMissing default + storage.fillMissing default storage_type col = Java_Column.new self.name new_st Column.Value col @@ -1768,7 +1768,7 @@ type Column cast self value_type on_problems=Problem_Behavior.Report_Warning = Cast_Helpers.check_cast_compatibility self.value_type value_type <| target_storage_type = Storage.from_value_type value_type on_problems - cast_problem_builder = Cast_Helpers.new_java_problem_builder self.name value_type + cast_problem_builder = Cast_Helpers.new_java_problem_builder self.name target_storage_type new_storage = self.java_column.getStorage.cast target_storage_type cast_problem_builder.to_java problems = cast_problem_builder.get_problems on_problems.attach_problems_before problems <| @@ -1937,7 +1937,7 @@ type Column example_at = Examples.integer_column.at 0 at : Integer -> (Any | Nothing) ! Index_Out_Of_Bounds - at self index = + at self (index : Integer) = valid_index = (index >= 0) && (index < self.length) if valid_index.not then Error.throw (Index_Out_Of_Bounds.Error index self.length) else storage = self.java_column.getStorage @@ -2204,9 +2204,10 @@ type Column run_vectorized_many_op : Column -> Text -> (Any -> Any -> Any) -> Vector -> Text|Nothing -> Boolean -> Column run_vectorized_many_op column name fallback_fn operands new_name=Nothing skip_nulls=False = effective_operands = Vector.unify_vector_or_element operands + all_operands = [column]+effective_operands effective_new_name = new_name.if_nothing <| - naming_helper.function_name name [column]+effective_operands - common_type = Value_Type_Helpers.find_common_type_for_arguments effective_operands + naming_helper.function_name name all_operands + common_type = Value_Type_Helpers.find_common_type_for_arguments all_operands common_type.if_not_error <| problem_builder = MapOperationProblemBuilder.new effective_new_name storage_type = resolve_storage_type common_type diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Enso_Types.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Enso_Types.enso index 4df505a2dda9..3671b510b6b0 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Enso_Types.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Enso_Types.enso @@ -5,6 +5,7 @@ import project.Data.Type.Value_Type.Bits import project.Data.Type.Value_Type.Value_Type polyglot java import org.enso.table.data.column.storage.type.IntegerType +polyglot java import org.enso.base.polyglot.NumericConverter ## PRIVATE Finds the most specific `Value_Type` that can be used to hold the given @@ -22,13 +23,16 @@ most_specific_value_type value use_smallest=False = _ : Date -> Value_Type.Date _ : Time_Of_Day -> Value_Type.Time _ : Date_Time -> Value_Type.Date_Time - i : Integer -> case use_smallest of - False -> Value_Type.Integer Bits.Bits_64 - True -> - storage_type = IntegerType.smallestFitting i - value_type = Storage.to_value_type storage_type - # We do a small rewrite here - for integers we always return the Integer type, even if the value is small enough to fit in a Byte. - if value_type == Value_Type.Byte then Value_Type.Integer Bits.Bits_16 else value_type + i : Integer -> + case NumericConverter.isBigInteger i of + False -> case use_smallest of + False -> Value_Type.Integer Bits.Bits_64 + True -> + storage_type = IntegerType.smallestFitting i + value_type = Storage.to_value_type storage_type + # We do a small rewrite here - for integers we always return the Integer type, even if the value is small enough to fit in a Byte. + if value_type == Value_Type.Byte then Value_Type.Integer Bits.Bits_16 else value_type + True -> Value_Type.Decimal precision=Nothing scale=0 text : Text -> case use_smallest of False -> Value_Type.Char size=Nothing variable_length=True True -> Value_Type.Char size=text.length variable_length=False diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Storage.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Storage.enso index a5754f8e11a0..2be7986adc04 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Storage.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Storage.enso @@ -10,6 +10,7 @@ from Standard.Table.Errors import Inexact_Type_Coercion polyglot java import org.enso.table.data.column.builder.Builder polyglot java import org.enso.table.data.column.storage.type.AnyObjectType polyglot java import org.enso.table.data.column.storage.type.Bits as Java_Bits +polyglot java import org.enso.table.data.column.storage.type.BigIntegerType polyglot java import org.enso.table.data.column.storage.type.BooleanType polyglot java import org.enso.table.data.column.storage.type.DateTimeType polyglot java import org.enso.table.data.column.storage.type.DateType @@ -37,6 +38,7 @@ to_value_type storage_type = case storage_type of _ : DateType -> Value_Type.Date _ : DateTimeType -> Value_Type.Date_Time with_timezone=True _ : TimeOfDayType -> Value_Type.Time + _ : BigIntegerType -> Value_Type.Decimal scale=0 _ : AnyObjectType -> Value_Type.Mixed ## PRIVATE @@ -58,6 +60,7 @@ closest_storage_type value_type = case value_type of Value_Type.Date_Time _ -> DateTimeType.INSTANCE Value_Type.Time -> TimeOfDayType.INSTANCE Value_Type.Mixed -> AnyObjectType.INSTANCE + Value_Type.Decimal _ _ -> BigIntegerType.INSTANCE _ -> Error.throw (Illegal_Argument.Error "Columns of type "+value_type.to_display_text+" are currently not supported in the in-memory backend.") diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Value_Type_Helpers.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Value_Type_Helpers.enso index 2c68964f2932..724d61649533 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Value_Type_Helpers.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Type/Value_Type_Helpers.enso @@ -17,20 +17,32 @@ reconcile_types current new = case current of Value_Type.Byte -> Value_Type.Integer size # If we unify integers with floats, we select the default Float 64 regardless of the input sizes. Value_Type.Float _ -> Value_Type.Float + Value_Type.Decimal _ _ -> new _ -> Value_Type.Mixed Value_Type.Float size -> case new of Value_Type.Float new_size -> Value_Type.Float (max_size size new_size) # If we unify integers with floats, we select the default Float 64 regardless of the input sizes. - Value_Type.Integer _ -> Value_Type.Float - Value_Type.Byte -> Value_Type.Float - _ -> Value_Type.Mixed + Value_Type.Integer _ -> Value_Type.Float + Value_Type.Byte -> Value_Type.Float + Value_Type.Decimal _ _ -> Value_Type.Float + _ -> Value_Type.Mixed Value_Type.Byte -> case new of Value_Type.Byte -> Value_Type.Byte Value_Type.Integer size -> Value_Type.Integer size Value_Type.Float _ -> Value_Type.Float + Value_Type.Decimal _ _ -> new _ -> Value_Type.Mixed + Value_Type.Decimal precision scale -> case new of + Value_Type.Decimal new_precision new_scale -> + if (precision == new_precision) && (scale == new_scale) then new else + # TODO at some point we may want a more clever merging of precision and scale, for now we don't use them too much anyway so we just default to Nothing if they do not agree + Value_Type.Decimal + Value_Type.Integer _ -> Value_Type.Decimal precision scale + Value_Type.Byte -> Value_Type.Decimal precision scale + Value_Type.Float _ -> Value_Type.Float + _ -> Value_Type.Mixed Value_Type.Boolean -> case new of Value_Type.Boolean -> Value_Type.Boolean _ -> Value_Type.Mixed diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Cast_Helpers.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Cast_Helpers.enso index ae54c328c32c..889bf12dba3a 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Cast_Helpers.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Cast_Helpers.enso @@ -8,6 +8,7 @@ import project.Internal.Parse_Values_Helper from project.Errors import Conversion_Failure polyglot java import org.enso.table.data.column.operation.cast.CastProblemBuilder +polyglot java import org.enso.table.data.column.storage.type.StorageType ## PRIVATE Checks if one type can be cast into another and returns a dataflow error @@ -48,7 +49,6 @@ type Cast_Problem_Builder Java_Problems.parse_aggregated_problems self.to_java.getAggregatedProblems ## PRIVATE -new_java_problem_builder : Text -> Value_Type -> Cast_Problem_Builder -new_java_problem_builder column_name target_type = - storage_type = Storage.from_value_type_strict target_type - Cast_Problem_Builder.Value (CastProblemBuilder.new column_name storage_type) +new_java_problem_builder : Text -> StorageType -> Cast_Problem_Builder +new_java_problem_builder column_name target_storage_type = + Cast_Problem_Builder.Value (CastProblemBuilder.new column_name target_storage_type) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Java_Exports.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Java_Exports.enso index fe827bc545f6..3cd58f80d9b6 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Java_Exports.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Java_Exports.enso @@ -4,6 +4,7 @@ import project.Data.Type.Storage import project.Data.Type.Value_Type.Bits import project.Data.Type.Value_Type.Value_Type +polyglot java import org.enso.table.data.column.builder.BigIntegerBuilder polyglot java import org.enso.table.data.column.builder.BoolBuilder polyglot java import org.enso.table.data.column.builder.DateBuilder polyglot java import org.enso.table.data.column.builder.DateTimeBuilder @@ -27,6 +28,10 @@ make_long_builder initial_size bits=Bits.Bits_64 = integer_type = Storage.from_value_type_strict (Value_Type.Integer bits) NumericBuilder.createLongBuilder initial_size integer_type +## PRIVATE +make_biginteger_builder : Integer -> BigIntegerBuilder +make_biginteger_builder initial_size = BigIntegerBuilder.new initial_size + ## PRIVATE make_string_builder : Integer -> Value_Type -> StringBuilder make_string_builder initial_size value_type=Value_Type.Char = diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Java_Problems.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Java_Problems.enso index db0574fe72ae..d9bcd31dabc8 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Java_Problems.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Java_Problems.enso @@ -3,7 +3,7 @@ import Standard.Base.Errors.Common.Arithmetic_Error import Standard.Base.Errors.Illegal_Argument.Illegal_Argument import project.Data.Type.Storage -from project.Errors import Additional_Invalid_Rows, Additional_Warnings, Arithmetic_Overflow, Conversion_Failure, Duplicate_Output_Column_Names, Floating_Point_Equality, Invalid_Aggregation, Invalid_Column_Names, Invalid_Row, Loss_Of_Integer_Precision, Unquoted_Characters_In_Output, Unquoted_Delimiter +from project.Errors import all polyglot java import org.enso.table.data.column.builder.LossOfIntegerPrecision polyglot java import org.enso.table.data.column.operation.cast.ConversionFailure 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 index d4d7f0028008..c36f845af944 100644 --- 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 @@ -1,6 +1,9 @@ package org.enso.base.polyglot; +import org.graalvm.polyglot.Value; + import java.math.BigDecimal; +import java.math.BigInteger; /** * The numeric converter deals with conversions of Java numeric types to the two main types @@ -25,6 +28,7 @@ public static double coerceToDouble(Object o) { case Double x -> x; case BigDecimal x -> x.doubleValue(); case Float x -> x.doubleValue(); + case BigInteger x -> x.doubleValue(); default -> (double) coerceToLong(o); }; } @@ -46,9 +50,18 @@ public static long coerceToLong(Object o) { }; } + public static BigInteger coerceToBigInteger(Object o) { + if (o instanceof BigInteger bigInteger) { + return bigInteger; + } else { + long longValue = coerceToLong(o); + return BigInteger.valueOf(longValue); + } + } + /** Returns true if the object is any supported number. */ public static boolean isCoercibleToDouble(Object o) { - return isDecimalLike(o)|| isCoercibleToLong(o); + return isDecimalLike(o)|| isCoercibleToLong(o) || o instanceof BigInteger; } public static boolean isDecimalLike(Object o) { @@ -66,6 +79,10 @@ public static boolean isCoercibleToLong(Object o) { return o instanceof Long || o instanceof Integer || o instanceof Short || o instanceof Byte; } + public static boolean isCoercibleToBigInteger(Object o) { + return o instanceof BigInteger || isCoercibleToLong(o); + } + /** * Tries converting the value to a Double. * @@ -75,6 +92,7 @@ public static Double tryConvertingToDouble(Object o) { return switch (o) { case Double x -> x; case BigDecimal x -> x.doubleValue(); + case BigInteger x -> x.doubleValue(); case Float x -> x.doubleValue(); case Long x -> x.doubleValue(); case Integer x -> x.doubleValue(); @@ -105,7 +123,23 @@ public static Long tryConvertingToLong(Object o) { yield null; } } + case BigInteger x -> { + try { + yield x.longValueExact(); + } catch (ArithmeticException e) { + yield null; + } + } case null, default -> null; }; } + + public static boolean isBigInteger(Value v) { + return v.fitsInBigInteger() && !v.fitsInLong(); + } + + /** A workaround for #7790 */ + public static BigDecimal bigIntegerAsBigDecimal(BigInteger x) { + return new BigDecimal(x); + } } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/BigIntegerBuilder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/BigIntegerBuilder.java new file mode 100644 index 000000000000..abaf10902432 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/BigIntegerBuilder.java @@ -0,0 +1,98 @@ +package org.enso.table.data.column.builder; + +import java.math.BigInteger; +import java.util.Arrays; +import org.enso.base.polyglot.NumericConverter; +import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.numeric.BigIntegerStorage; +import org.enso.table.data.column.storage.type.AnyObjectType; +import org.enso.table.data.column.storage.type.BigIntegerType; +import org.enso.table.data.column.storage.type.FloatType; +import org.enso.table.data.column.storage.type.StorageType; +import org.enso.table.error.ValueTypeMismatchException; + +// For now the BigInteger builder is just a stub, reusing the ObjectBuilder and adding a warning. +public class BigIntegerBuilder extends TypedBuilderImpl { + @Override + protected BigInteger[] newArray(int size) { + return new BigInteger[size]; + } + + public BigIntegerBuilder(int size) { + super(size); + } + + @Override + public void writeTo(Object[] items) { + super.writeTo(items); + } + + @Override + public boolean canRetypeTo(StorageType type) { + return type instanceof FloatType || type instanceof AnyObjectType; + } + + @Override + public TypedBuilder retypeTo(StorageType type) { + if (type instanceof FloatType) { + DoubleBuilder res = NumericBuilder.createDoubleBuilder(currentSize); + for (int i = 0; i < currentSize; i++) { + if (data[i] == null) { + res.appendNulls(1); + } else { + res.appendBigInteger(data[i]); + } + } + return res; + } else if (type instanceof AnyObjectType) { + Object[] widenedData = Arrays.copyOf(data, data.length, Object[].class); + ObjectBuilder res = new MixedBuilder(widenedData); + res.setCurrentSize(currentSize); + res.setPreExistingProblems(getProblems()); + return res; + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + protected Storage doSeal() { + return new BigIntegerStorage(data, currentSize); + } + + @Override + public StorageType getType() { + return BigIntegerType.INSTANCE; + } + + @Override + public boolean accepts(Object o) { + return NumericConverter.isCoercibleToBigInteger(o); + } + + @Override + public void appendNoGrow(Object o) { + if (o == null) { + data[currentSize++] = null; + } else { + try { + data[currentSize++] = NumericConverter.coerceToBigInteger(o); + } catch (UnsupportedOperationException e) { + throw new ValueTypeMismatchException(BigIntegerType.INSTANCE, o); + } + } + } + + public void appendRawNoGrow(BigInteger value) { + data[currentSize++] = value; + } + + public static BigIntegerBuilder retypeFromLongBuilder(LongBuilder longBuilder) { + int n = longBuilder.currentSize; + BigIntegerBuilder res = new BigIntegerBuilder(n); + for (int i = 0; i < n; i++) { + res.appendNoGrow(BigInteger.valueOf(longBuilder.data[i])); + } + return res; + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/Builder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/Builder.java index 7c8f0e5e7486..b7ca2e7ecbc8 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/Builder.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/Builder.java @@ -6,6 +6,7 @@ import org.enso.table.data.column.storage.type.FloatType; import org.enso.table.data.column.storage.type.IntegerType; import org.enso.table.problems.AggregatedProblems; +import org.enso.table.problems.WithAggregatedProblems; /** A builder for creating columns dynamically. */ public abstract class Builder { @@ -26,6 +27,7 @@ public static Builder getForType(StorageType type, int size) { }; case IntegerType integerType -> NumericBuilder.createLongBuilder(size, integerType); case TextType textType -> new StringBuilder(size, textType); + case BigIntegerType x -> new BigIntegerBuilder(size); case null -> new InferredBuilder(size); }; assert builder.getType().equals(type); @@ -88,4 +90,8 @@ public static Builder getForType(StorageType type, int size) { public AggregatedProblems getProblems() { return AggregatedProblems.of(); } + + public WithAggregatedProblems> sealWithProblems() { + return new WithAggregatedProblems<>(seal(), getProblems()); + } } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/DoubleBuilder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/DoubleBuilder.java index 9246992bdfb0..d76139f32e51 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/DoubleBuilder.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/DoubleBuilder.java @@ -14,6 +14,8 @@ import org.enso.table.problems.AggregatedProblems; import org.enso.table.util.BitSets; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.BitSet; import java.util.Objects; @@ -89,6 +91,9 @@ public void appendNoGrow(Object o) { long value = NumericConverter.coerceToLong(o); double converted = convertIntegerToDouble(value); data[currentSize++] = Double.doubleToRawLongBits(converted); + } else if (o instanceof BigInteger bigInteger) { + double converted = convertBigIntegerToDouble(bigInteger); + data[currentSize++] = Double.doubleToRawLongBits(converted); } else { throw new ValueTypeMismatchException(getType(), o); } @@ -177,6 +182,15 @@ public void appendLong(long integer) { appendRawNoGrow(Double.doubleToRawLongBits(converted)); } + public void appendBigInteger(BigInteger integer) { + if (currentSize >= this.data.length) { + grow(); + } + + double converted = convertBigIntegerToDouble(integer); + appendRawNoGrow(Double.doubleToRawLongBits(converted)); + } + @Override public Storage seal() { return new DoubleStorage(data, currentSize, isMissing); @@ -200,6 +214,20 @@ private double convertIntegerToDouble(long integer) { return floatingPointValue; } + private double convertBigIntegerToDouble(BigInteger bigInteger) { + double floatingPointValue = bigInteger.doubleValue(); + BigInteger reconstructed = BigDecimal.valueOf(floatingPointValue).toBigInteger(); + boolean isLosingPrecision = !bigInteger.equals(reconstructed); + if (isLosingPrecision) { + if (precisionLoss == null) { + precisionLoss = new LossOfIntegerPrecision(bigInteger, floatingPointValue); + } else { + precisionLoss.incrementAffectedRows(); + } + } + return floatingPointValue; + } + private LossOfIntegerPrecision precisionLoss = null; @Override diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/InferredBuilder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/InferredBuilder.java index b64bb78d6077..1a69840f3b77 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/InferredBuilder.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/InferredBuilder.java @@ -1,11 +1,13 @@ package org.enso.table.data.column.builder; +import java.math.BigInteger; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.List; import org.enso.base.polyglot.NumericConverter; import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.type.BigIntegerType; import org.enso.table.data.column.storage.type.BooleanType; import org.enso.table.data.column.storage.type.DateTimeType; import org.enso.table.data.column.storage.type.DateType; @@ -99,16 +101,18 @@ private void initBuilderFor(Object o) { } else if (NumericConverter.isCoercibleToLong(o)) { // In inferred builder, we always default to 64-bits. currentBuilder = NumericBuilder.createLongBuilder(initialCapacity, IntegerType.INT_64); - } else if (NumericConverter.isCoercibleToDouble(o)) { + } else if (NumericConverter.isDecimalLike(o)) { currentBuilder = NumericBuilder.createDoubleBuilder(initialCapacity); + } else if (o instanceof String) { + currentBuilder = new StringBuilder(initialCapacity, TextType.VARIABLE_LENGTH); + } else if (o instanceof BigInteger) { + currentBuilder = new BigIntegerBuilder(initialCapacity); } else if (o instanceof LocalDate) { currentBuilder = new DateBuilder(initialCapacity); } else if (o instanceof LocalTime) { currentBuilder = new TimeOfDayBuilder(initialCapacity); } else if (o instanceof ZonedDateTime) { currentBuilder = new DateTimeBuilder(initialCapacity); - } else if (o instanceof String) { - currentBuilder = new StringBuilder(initialCapacity, TextType.VARIABLE_LENGTH); } else { currentBuilder = new MixedBuilder(initialCapacity); } @@ -134,7 +138,8 @@ private record RetypeInfo(Class clazz, StorageType type) {} // not apply only if a specific type is requested (so not in inferred builder). new RetypeInfo(Integer.class, IntegerType.INT_64), new RetypeInfo(Short.class, IntegerType.INT_64), - new RetypeInfo(Byte.class, IntegerType.INT_64)); + new RetypeInfo(Byte.class, IntegerType.INT_64), + new RetypeInfo(BigInteger.class, BigIntegerType.INSTANCE)); private void retypeAndAppend(Object o) { for (RetypeInfo info : retypePairs) { diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/LongBuilder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/LongBuilder.java index 02e89ecac787..ab762314ee55 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/LongBuilder.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/LongBuilder.java @@ -6,6 +6,7 @@ import org.enso.table.data.column.storage.numeric.AbstractLongStorage; import org.enso.table.data.column.storage.numeric.LongStorage; import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.type.BigIntegerType; import org.enso.table.data.column.storage.type.BooleanType; import org.enso.table.data.column.storage.type.FloatType; import org.enso.table.data.column.storage.type.IntegerType; @@ -46,12 +47,14 @@ public void writeTo(Object[] items) { @Override public boolean canRetypeTo(StorageType type) { - return Objects.equals(type, FloatType.FLOAT_64); + return Objects.equals(type, FloatType.FLOAT_64) || Objects.equals(type, BigIntegerType.INSTANCE); } @Override public TypedBuilder retypeTo(StorageType type) { - if (Objects.equals(type, FloatType.FLOAT_64)) { + if (Objects.equals(type, BigIntegerType.INSTANCE)) { + return BigIntegerBuilder.retypeFromLongBuilder(this); + } else if (Objects.equals(type, FloatType.FLOAT_64)) { return DoubleBuilder.retypeFromLongBuilder(this); } else { throw new UnsupportedOperationException(); diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/LossOfIntegerPrecision.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/LossOfIntegerPrecision.java index 3926d168b462..cb5a5a1f30cd 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/LossOfIntegerPrecision.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/LossOfIntegerPrecision.java @@ -4,17 +4,17 @@ /** Indicates that an integer being converted to double cannot be represented precisely. */ public class LossOfIntegerPrecision implements Problem { - private final long exampleValue; + private final Number exampleValue; private final double exampleValueConverted; private long affectedRows; - public LossOfIntegerPrecision(long exampleValue, double exampleValueConverted) { + public LossOfIntegerPrecision(Number exampleValue, double exampleValueConverted) { this.exampleValue = exampleValue; this.exampleValueConverted = exampleValueConverted; this.affectedRows = 1; } - public long getExampleValue() { + public Number getExampleValue() { return exampleValue; } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/StorageConverter.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/StorageConverter.java index 8e18d323957e..fc8b1363b5e2 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/StorageConverter.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/StorageConverter.java @@ -25,6 +25,7 @@ static StorageConverter fromStorageType(StorageType storageType) { case IntegerType integerType -> new ToIntegerStorageConverter(integerType); case TextType textType -> new ToTextStorageConverter(textType); case TimeOfDayType timeOfDayType -> new ToTimeOfDayStorageConverter(); + case BigIntegerType bigIntegerType -> new ToBigIntegerConverter(); }; } } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToBigIntegerConverter.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToBigIntegerConverter.java new file mode 100644 index 000000000000..0fe0b5481150 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToBigIntegerConverter.java @@ -0,0 +1,100 @@ +package org.enso.table.data.column.operation.cast; + +import org.enso.table.data.column.builder.BigIntegerBuilder; +import org.enso.table.data.column.storage.BoolStorage; +import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.numeric.AbstractLongStorage; +import org.enso.table.data.column.storage.numeric.BigIntegerStorage; +import org.enso.table.data.column.storage.numeric.DoubleStorage; +import org.enso.table.data.column.storage.type.AnyObjectType; + +import java.math.BigDecimal; +import java.math.BigInteger; + +public class ToBigIntegerConverter implements StorageConverter { + @Override + public Storage cast(Storage storage, CastProblemBuilder problemBuilder) { + if (storage instanceof BigIntegerStorage bigIntegerStorage) { + return bigIntegerStorage; + } else if (storage instanceof AbstractLongStorage longStorage) { + return convertLongStorage(longStorage, problemBuilder); + } else if (storage instanceof DoubleStorage doubleStorage) { + return convertDoubleStorage(doubleStorage, problemBuilder); + } else if (storage instanceof BoolStorage boolStorage) { + return convertBoolStorage(boolStorage, problemBuilder); + } else if (storage.getType() instanceof AnyObjectType) { + return castFromMixed(storage, problemBuilder); + }else { + throw new IllegalStateException("No known strategy for casting storage " + storage + " to BigInteger."); + } + } + + private Storage convertDoubleStorage(DoubleStorage doubleStorage, CastProblemBuilder problemBuilder) { + int n = doubleStorage.size(); + BigIntegerBuilder builder = new BigIntegerBuilder(n); + for (int i = 0; i < n; i++) { + if (doubleStorage.isNa(i)) { + builder.appendNulls(1); + } else { + double x = doubleStorage.getItemAsDouble(i); + BigInteger bigInteger = BigDecimal.valueOf(x).toBigInteger(); + builder.appendRawNoGrow(bigInteger); + } + } + return builder.seal(); + } + + private Storage convertLongStorage(AbstractLongStorage longStorage, CastProblemBuilder problemBuilder) { + int n = longStorage.size(); + BigIntegerBuilder builder = new BigIntegerBuilder(n); + for (int i = 0; i < n; i++) { + if (longStorage.isNa(i)) { + builder.appendNulls(1); + } else { + long x = longStorage.getItem(i); + BigInteger bigInteger = BigInteger.valueOf(x); + builder.appendRawNoGrow(bigInteger); + } + } + return builder.seal(); + } + + private Storage convertBoolStorage(BoolStorage boolStorage, CastProblemBuilder problemBuilder) { + int n = boolStorage.size(); + BigIntegerBuilder builder = new BigIntegerBuilder(n); + for (int i = 0; i < n; i++) { + if (boolStorage.isNa(i)) { + builder.appendNulls(1); + } else { + boolean x = boolStorage.getItem(i); + BigInteger bigInteger = booleanAsBigInteger(x); + builder.appendRawNoGrow(bigInteger); + } + } + return builder.seal(); + } + + private Storage castFromMixed(Storage storage, CastProblemBuilder problemBuilder) { + int n = storage.size(); + BigIntegerBuilder builder = new BigIntegerBuilder(n); + for (int i = 0; i < n; i++) { + Object o = storage.getItemBoxed(i); + switch (o) { + case null -> builder.appendNulls(1); + case Boolean b -> builder.appendRawNoGrow(booleanAsBigInteger(b)); + case Long l -> builder.appendRawNoGrow(BigInteger.valueOf(l)); + case Double d -> builder.appendRawNoGrow(BigDecimal.valueOf(d).toBigInteger()); + case BigInteger bigInteger -> builder.appendRawNoGrow(bigInteger); + default -> { + problemBuilder.reportConversionFailure(o); + builder.appendNulls(1); + } + } + } + return builder.seal(); + } + + public static BigInteger booleanAsBigInteger(boolean value) { + return value ? BigInteger.ONE : BigInteger.ZERO; + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToFloatStorageConverter.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToFloatStorageConverter.java index b2d425599fc4..e7852144003f 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToFloatStorageConverter.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToFloatStorageConverter.java @@ -5,13 +5,18 @@ import org.enso.table.data.column.builder.NumericBuilder; import org.enso.table.data.column.storage.BoolStorage; import org.enso.table.data.column.storage.numeric.AbstractLongStorage; +import org.enso.table.data.column.storage.numeric.BigIntegerStorage; import org.enso.table.data.column.storage.numeric.DoubleStorage; import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.numeric.LongStorage; import org.enso.table.data.column.storage.type.AnyObjectType; import org.enso.table.data.column.storage.type.Bits; import org.enso.table.data.column.storage.type.FloatType; import org.graalvm.polyglot.Context; +import java.math.BigInteger; +import java.util.BitSet; + public class ToFloatStorageConverter implements StorageConverter { public ToFloatStorageConverter(FloatType targetType) { if (targetType.bits() != Bits.BITS_64) { @@ -26,6 +31,8 @@ public Storage cast(Storage storage, CastProblemBuilder problemBuilde return convertLongStorage(longStorage, problemBuilder); } else if (storage instanceof BoolStorage boolStorage) { return convertBoolStorage(boolStorage, problemBuilder); + } else if (storage instanceof BigIntegerStorage bigIntegerStorage) { + return convertBigIntegerStorage(bigIntegerStorage, problemBuilder); } else if (storage.getType() instanceof AnyObjectType) { return castFromMixed(storage, problemBuilder); } else { @@ -48,6 +55,8 @@ public Storage castFromMixed(Storage mixedStorage, CastProblemBuilder } else if (NumericConverter.isDecimalLike(o)) { double x = NumericConverter.coerceToDouble(o); builder.appendDouble(x); + } else if (o instanceof BigInteger bigInteger) { + builder.appendBigInteger(bigInteger); } else { problemBuilder.reportConversionFailure(o); builder.appendNulls(1); @@ -95,4 +104,23 @@ private Storage convertBoolStorage(BoolStorage boolStorage, CastProblemB public static double booleanAsDouble(boolean value) { return value ? 1.0 : 0.0; } + + private Storage convertBigIntegerStorage(Storage storage, CastProblemBuilder problemBuilder) { + int n = storage.size(); + DoubleBuilder builder = NumericBuilder.createDoubleBuilder(n); + Context context = Context.getCurrent(); + for (int i = 0; i < n; i++) { + BigInteger value = storage.getItemBoxed(i); + if (value == null) { + builder.appendNulls(1); + } else { + builder.appendBigInteger(value); + } + + context.safepoint(); + } + + problemBuilder.aggregateOtherProblems(builder.getProblems()); + return builder.seal(); + } } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToIntegerStorageConverter.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToIntegerStorageConverter.java index bce9566f8779..f51199f06b8c 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToIntegerStorageConverter.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToIntegerStorageConverter.java @@ -5,15 +5,16 @@ import org.enso.table.data.column.builder.NumericBuilder; import org.enso.table.data.column.storage.BoolStorage; import org.enso.table.data.column.storage.numeric.AbstractLongStorage; +import org.enso.table.data.column.storage.numeric.BigIntegerStorage; import org.enso.table.data.column.storage.numeric.DoubleStorage; import org.enso.table.data.column.storage.numeric.LongStorage; import org.enso.table.data.column.storage.Storage; import org.enso.table.data.column.storage.type.AnyObjectType; -import org.enso.table.data.column.storage.type.Bits; import org.enso.table.data.column.storage.type.IntegerType; import org.enso.table.util.BitSets; import org.graalvm.polyglot.Context; +import java.math.BigInteger; import java.util.BitSet; public class ToIntegerStorageConverter implements StorageConverter { @@ -34,6 +35,8 @@ public Storage cast(Storage storage, CastProblemBuilder problemBuilder) return convertDoubleStorage(doubleStorage, problemBuilder); } else if (storage instanceof BoolStorage boolStorage) { return convertBoolStorage(boolStorage, problemBuilder); + } else if (storage instanceof BigIntegerStorage bigIntegerStorage) { + return convertBigIntegerStorage(bigIntegerStorage, problemBuilder); } else if (storage.getType() instanceof AnyObjectType) { return castFromMixed(storage, problemBuilder); } else { @@ -67,6 +70,13 @@ public Storage castFromMixed(Storage mixedStorage, CastProblemBuilder p problemBuilder.reportNumberOutOfRange(x); builder.appendNulls(1); } + } else if (o instanceof BigInteger bigInteger) { + if (targetType.fits(bigInteger)) { + builder.appendLongUnchecked(bigInteger.longValue()); + } else { + problemBuilder.reportNumberOutOfRange(bigInteger); + builder.appendNulls(1); + } } else { problemBuilder.reportConversionFailure(o); builder.appendNulls(1); @@ -106,7 +116,7 @@ private Storage convertDoubleStorage(DoubleStorage doubleStorage, CastProb if (doubleStorage.isNa(i)) { builder.appendNulls(1); } else { - double value = doubleStorage.getItem(i); + double value = doubleStorage.getItemAsDouble(i); if (targetType.fits(value)) { long converted = (long) value; builder.appendLong(converted); @@ -152,6 +162,28 @@ private Storage convertLongStorage(AbstractLongStorage longStorage, CastPr } } + private Storage convertBigIntegerStorage(Storage storage, CastProblemBuilder problemBuilder) { + Context context = Context.getCurrent(); + int n = storage.size(); + long[] data = new long[n]; + BitSet isMissing = new BitSet(); + for (int i = 0; i < n; i++) { + BigInteger value = storage.getItemBoxed(i); + if (value == null) { + isMissing.set(i); + } else if (targetType.fits(value)) { + data[i] = value.longValue(); + } else { + isMissing.set(i); + problemBuilder.reportNumberOutOfRange(value); + } + + context.safepoint(); + } + + return new LongStorage(data, n, isMissing, targetType); + } + public static long booleanAsLong(boolean value) { return value ? 1L : 0L; } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToTextStorageConverter.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToTextStorageConverter.java index 443bf795f788..7323932bfd85 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToTextStorageConverter.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/ToTextStorageConverter.java @@ -1,6 +1,5 @@ package org.enso.table.data.column.operation.cast; -import org.enso.base.Text_Utils; import org.enso.polyglot.common_utils.Core_Date_Utils; import org.enso.table.data.column.builder.StringBuilder; import org.enso.table.data.column.storage.BoolStorage; @@ -140,7 +139,7 @@ private Storage castDoubleStorage(DoubleStorage doubleStorage, CastProbl if (doubleStorage.isNa(i)) { builder.appendNulls(1); } else { - double value = doubleStorage.getItem(i); + double value = doubleStorage.getItemAsDouble(i); builder.append(adapt(Double.toString(value), problemBuilder)); } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleBooleanOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleBooleanOp.java deleted file mode 100644 index 26aca6d9b525..000000000000 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleBooleanOp.java +++ /dev/null @@ -1,128 +0,0 @@ -package org.enso.table.data.column.operation.map.numeric; - -import org.enso.base.polyglot.NumericConverter; -import org.enso.table.data.column.operation.map.BinaryMapOperation; -import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; -import org.enso.table.data.column.storage.BoolStorage; -import org.enso.table.data.column.storage.numeric.AbstractLongStorage; -import org.enso.table.data.column.storage.numeric.DoubleStorage; -import org.enso.table.data.column.storage.numeric.LongStorage; -import org.enso.table.data.column.storage.Storage; -import org.enso.table.error.UnexpectedTypeException; -import org.graalvm.polyglot.Context; - -import java.util.BitSet; - -/** An operation expecting a numeric argument and returning a boolean. */ -public abstract class DoubleBooleanOp extends BinaryMapOperation { - public DoubleBooleanOp(String name) { - super(name); - } - - protected abstract boolean doDouble(double a, double b); - - protected boolean doObject(double a, Object o) { - throw new UnexpectedTypeException("a Number"); - } - - @Override - public BoolStorage runBinaryMap(DoubleStorage storage, Object arg, MapOperationProblemBuilder problemBuilder) { - Context context = Context.getCurrent(); - Double v = NumericConverter.tryConvertingToDouble(arg); - if (v != null) { - double x = v; - BitSet newVals = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i)) { - if (doDouble(storage.getItem(i), x)) { - newVals.set(i); - } - } - - context.safepoint(); - } - return new BoolStorage(newVals, storage.getIsMissing(), storage.size(), false); - } else { - BitSet newVals = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i)) { - if (doObject(storage.getItem(i), arg)) { - newVals.set(i); - } - } - - context.safepoint(); - } - return new BoolStorage(newVals, storage.getIsMissing(), storage.size(), false); - } - } - - @Override - public BoolStorage runZip(DoubleStorage storage, Storage arg, MapOperationProblemBuilder problemBuilder) { - Context context = Context.getCurrent(); - if (arg instanceof DoubleStorage v) { - BitSet newVals = new BitSet(); - BitSet newMissing = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i) && i < v.size() && !v.isNa(i)) { - if (doDouble(storage.getItem(i), v.getItem(i))) { - newVals.set(i); - } - } else { - newMissing.set(i); - } - - context.safepoint(); - } - return new BoolStorage(newVals, newMissing, storage.size(), false); - } else if (arg instanceof AbstractLongStorage v) { - BitSet newVals = new BitSet(); - BitSet newMissing = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i) && i < v.size() && !v.isNa(i)) { - double left = storage.getItem(i); - long right = v.getItem(i); - // We convert from long to double here. This may lose precision, but we do not report - // LossOfIntegerPrecision, because it is expected that numeric operations involving floating point columns - // are inherently imprecise. - double rightConverted = (double) right; - if (doDouble(left, rightConverted)) { - newVals.set(i); - } - } else { - newMissing.set(i); - } - - context.safepoint(); - } - return new BoolStorage(newVals, newMissing, storage.size(), false); - } else { - BitSet newVals = new BitSet(); - BitSet newMissing = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i) && i < arg.size() && !arg.isNa(i)) { - Double x = NumericConverter.tryConvertingToDouble(arg.getItemBoxed(i)); - if (x == null) { - if (doObject(storage.getItem(i), arg.getItemBoxed(i))) { - newVals.set(i); - } - } else { - if (doDouble(storage.getItem(i), x)) { - newVals.set(i); - } - } - } else { - newMissing.set(i); - } - - context.safepoint(); - } - return new BoolStorage(newVals, newMissing, storage.size(), false); - } - } - - @Override - public boolean reliesOnSpecializedStorage() { - return false; - } -} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleComparison.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleComparison.java deleted file mode 100644 index 7106b593bf9c..000000000000 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleComparison.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.enso.table.data.column.operation.map.numeric; - -import org.enso.base.CompareException; - -public abstract class DoubleComparison extends DoubleBooleanOp { - public DoubleComparison(String name) { - super(name); - } - - @Override - protected boolean doObject(double a, Object o) { - throw new CompareException(a, o); - } -} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleLongMapOpWithSpecialNumericHandling.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleLongMapOpWithSpecialNumericHandling.java index f907338ac45e..5f9418c69ecc 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleLongMapOpWithSpecialNumericHandling.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleLongMapOpWithSpecialNumericHandling.java @@ -24,7 +24,7 @@ public LongStorage runUnaryMap(DoubleStorage storage, MapOperationProblemBuilder for (int i = 0; i < storage.size(); i++) { if (!storage.isNa(i)) { - double item = storage.getItem(i); + double item = storage.getItemAsDouble(i); boolean special = Double.isNaN(item) || Double.isInfinite(item); if (!special) { out[i] = doOperation(item); diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleNumericOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleNumericOp.java deleted file mode 100644 index b3246fadc906..000000000000 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleNumericOp.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.enso.table.data.column.operation.map.numeric; - -import org.enso.table.data.column.operation.map.BinaryMapOperation; -import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; -import org.enso.table.data.column.storage.Storage; -import org.enso.table.data.column.storage.numeric.AbstractLongStorage; -import org.enso.table.data.column.storage.numeric.DoubleStorage; -import org.enso.table.error.UnexpectedTypeException; -import org.graalvm.polyglot.Context; - -import java.util.BitSet; - -/** An operation expecting a numeric argument and returning a number. */ -public abstract class DoubleNumericOp extends BinaryMapOperation { - - public DoubleNumericOp(String name) { - super(name); - } - - protected abstract double doDouble(double a, double b, int ix, MapOperationProblemBuilder problemBuilder); - - @Override - public Storage runBinaryMap(DoubleStorage storage, Object arg, MapOperationProblemBuilder problemBuilder) { - if (arg == null) { - return DoubleStorage.makeEmpty(storage.size()); - } - - double x; - if (arg instanceof Double) { - x = (Double) arg; - } else if (arg instanceof Long) { - x = (Long) arg; - } else { - throw new UnexpectedTypeException("a Number."); - } - - Context context = Context.getCurrent(); - long[] out = new long[storage.size()]; - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i)) { - out[i] = Double.doubleToRawLongBits(doDouble(storage.getItem(i), x, i, problemBuilder)); - } - - context.safepoint(); - } - return new DoubleStorage(out, storage.size(), storage.getIsMissing()); - } - - @Override - public Storage runZip(DoubleStorage storage, Storage arg, MapOperationProblemBuilder problemBuilder) { - Context context = Context.getCurrent(); - if (arg instanceof AbstractLongStorage v) { - long[] out = new long[storage.size()]; - BitSet newMissing = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i) && i < v.size() && !v.isNa(i)) { - out[i] = Double.doubleToRawLongBits(doDouble(storage.getItem(i), v.getItem(i), i, problemBuilder)); - } else { - newMissing.set(i); - } - - context.safepoint(); - } - return new DoubleStorage(out, storage.size(), newMissing); - } else if (arg instanceof DoubleStorage v) { - long[] out = new long[storage.size()]; - BitSet newMissing = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i) && i < v.size() && !v.isNa(i)) { - out[i] = Double.doubleToRawLongBits(doDouble(storage.getItem(i), v.getItem(i), i, problemBuilder)); - } else { - newMissing.set(i); - } - - context.safepoint(); - } - return new DoubleStorage(out, storage.size(), newMissing); - } else { - throw new UnexpectedTypeException("a Number."); - } - } -} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleRoundOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleRoundOp.java index 456ded42e76e..bad9bdef9215 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleRoundOp.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleRoundOp.java @@ -40,7 +40,7 @@ public Storage runTernaryMap(DoubleStorage storage, Object decimalPlacesObjec for (int i = 0; i < storage.size(); i++) { if (!storage.isNa(i)) { - double item = storage.getItem(i); + double item = storage.getItemAsDouble(i); boolean special = Double.isNaN(item) || Double.isInfinite(item); if (!special) { out[i] = (long) Core_Math_Utils.roundDouble(item, decimalPlaces, useBankers); @@ -62,7 +62,7 @@ public Storage runTernaryMap(DoubleStorage storage, Object decimalPlacesObjec for (int i = 0; i < storage.size(); i++) { if (!storage.isNa(i)) { - double item = storage.getItem(i); + double item = storage.getItemAsDouble(i); boolean special = Double.isNaN(item) || Double.isInfinite(item); if (!special) { doubleBuilder.appendDouble(Core_Math_Utils.roundDouble(item, decimalPlaces, useBankers)); diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongBooleanOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongBooleanOp.java deleted file mode 100644 index a1373094b58b..000000000000 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongBooleanOp.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.enso.table.data.column.operation.map.numeric; - -import org.enso.table.data.column.operation.map.BinaryMapOperation; -import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; -import org.enso.table.data.column.storage.BoolStorage; -import org.enso.table.data.column.storage.Storage; -import org.enso.table.data.column.storage.numeric.AbstractLongStorage; -import org.enso.table.data.column.storage.numeric.DoubleStorage; -import org.enso.table.error.UnexpectedTypeException; -import org.graalvm.polyglot.Context; - -import java.util.BitSet; - -/** - * An operation expecting a numeric argument and returning a boolean. - */ -public abstract class LongBooleanOp extends BinaryMapOperation { - public LongBooleanOp(String name) { - super(name); - } - - protected abstract boolean doLong(long a, long b); - - protected abstract boolean doDouble(long a, double b); - - protected boolean doObject(long a, Object b) { - throw new UnexpectedTypeException("a Number"); - } - - @Override - public BoolStorage runBinaryMap(AbstractLongStorage storage, Object arg, MapOperationProblemBuilder problemBuilder) { - Context context = Context.getCurrent(); - if (arg instanceof Long) { - long x = (Long) arg; - BitSet newVals = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i)) { - if (doLong(storage.getItem(i), x)) { - newVals.set(i); - } - } - - context.safepoint(); - } - return new BoolStorage(newVals, storage.getIsMissing(), storage.size(), false); - } else if (arg instanceof Double) { - double x = (Double) arg; - BitSet newVals = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i)) { - if (doDouble(storage.getItem(i), x)) { - newVals.set(i); - } - } - - context.safepoint(); - } - return new BoolStorage(newVals, storage.getIsMissing(), storage.size(), false); - } else { - BitSet newVals = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i)) { - if (doObject(storage.getItem(i), arg)) { - newVals.set(i); - } - } - - context.safepoint(); - } - return new BoolStorage(newVals, storage.getIsMissing(), storage.size(), false); - } - } - - @Override - public BoolStorage runZip(AbstractLongStorage storage, Storage arg, MapOperationProblemBuilder problemBuilder) { - Context context = Context.getCurrent(); - if (arg instanceof DoubleStorage v) { - BitSet newVals = new BitSet(); - BitSet newMissing = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i) && i < v.size() && !v.isNa(i)) { - if (doDouble(storage.getItem(i), v.getItem(i))) { - newVals.set(i); - } - } else { - newMissing.set(i); - } - - context.safepoint(); - } - return new BoolStorage(newVals, newMissing, storage.size(), false); - } else if (arg instanceof AbstractLongStorage v) { - BitSet newVals = new BitSet(); - BitSet newMissing = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i) && i < v.size() && !v.isNa(i)) { - if (doLong(storage.getItem(i), v.getItem(i))) { - newVals.set(i); - } - } else { - newMissing.set(i); - } - - context.safepoint(); - } - return new BoolStorage(newVals, newMissing, storage.size(), false); - } else { - BitSet newVals = new BitSet(); - BitSet newMissing = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i) && i < arg.size() && !arg.isNa(i)) { - Object v = arg.getItemBoxed(i); - if (v instanceof Long) { - if (doLong(storage.getItem(i), (Long) v)) { - newVals.set(i); - } - } else if (v instanceof Double) { - if (doDouble(storage.getItem(i), (Double) v)) { - newVals.set(i); - } - } else { - if (doObject(storage.getItem(i), v)) { - newVals.set(i); - } - } - } else { - newMissing.set(i); - } - - context.safepoint(); - } - return new BoolStorage(newVals, newMissing, storage.size(), false); - } - } - - @Override - public boolean reliesOnSpecializedStorage() { - return false; - } -} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongComparison.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongComparison.java deleted file mode 100644 index cf61c7ec7855..000000000000 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongComparison.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.enso.table.data.column.operation.map.numeric; - -import org.enso.base.CompareException; - -public abstract class LongComparison extends LongBooleanOp { - public LongComparison(String name) { - super(name); - } - - @Override - protected boolean doObject(long a, Object b) { - throw new CompareException(a, b); - } -} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongNumericOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongNumericOp.java deleted file mode 100644 index 6b773ad0788c..000000000000 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongNumericOp.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.enso.table.data.column.operation.map.numeric; - -import org.enso.table.data.column.operation.map.BinaryMapOperation; -import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; -import org.enso.table.data.column.storage.Storage; -import org.enso.table.data.column.storage.numeric.AbstractLongStorage; -import org.enso.table.data.column.storage.numeric.DoubleStorage; -import org.enso.table.data.column.storage.numeric.LongStorage; -import org.enso.table.data.column.storage.numeric.NumericStorage; -import org.enso.table.data.column.storage.type.IntegerType; -import org.enso.table.error.UnexpectedTypeException; -import org.enso.table.util.BitSets; -import org.graalvm.polyglot.Context; - -import java.util.BitSet; - -/** - * An operation expecting a numeric argument and returning a boolean. - */ -public abstract class LongNumericOp extends BinaryMapOperation { - private final boolean alwaysCastToDouble; - - // Regardless of input type, our operations return 64-bit integers. - private static final IntegerType INTEGER_RESULT_TYPE = IntegerType.INT_64; - - public LongNumericOp(String name, boolean alwaysCastToDouble) { - super(name); - this.alwaysCastToDouble = alwaysCastToDouble; - } - - public LongNumericOp(String name) { - this(name, false); - } - - public abstract double doDouble(long in, double arg, int ix, MapOperationProblemBuilder problemBuilder); - - public abstract Long doLong(long in, long arg, int ix, MapOperationProblemBuilder problemBuilder); - - @Override - public NumericStorage runBinaryMap(AbstractLongStorage storage, Object arg, MapOperationProblemBuilder problemBuilder) { - Context context = Context.getCurrent(); - if (arg == null) { - if (alwaysCastToDouble) { - return DoubleStorage.makeEmpty(storage.size()); - } else { - return LongStorage.makeEmpty(storage.size(), INTEGER_RESULT_TYPE); - } - } else if (!alwaysCastToDouble && arg instanceof Long x) { - BitSet newMissing = BitSets.makeDuplicate(storage.getIsMissing()); - long[] newVals = new long[storage.size()]; - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i)) { - Long newVal = doLong(storage.getItem(i), x, i, problemBuilder); - if (newVal == null) { - newMissing.set(i); - } else { - newVals[i] = newVal; - } - } - - context.safepoint(); - } - - return new LongStorage(newVals, newVals.length, newMissing, INTEGER_RESULT_TYPE); - } else if (arg instanceof Double || arg instanceof Long) { - double x = (arg instanceof Double) ? (Double) arg : (Long) arg; - long[] newVals = new long[storage.size()]; - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i)) { - newVals[i] = Double.doubleToRawLongBits(doDouble(storage.getItem(i), x, i, problemBuilder)); - } - - context.safepoint(); - } - return new DoubleStorage(newVals, newVals.length, storage.getIsMissing()); - } - throw new UnexpectedTypeException("a Number"); - } - - @Override - public NumericStorage runZip(AbstractLongStorage storage, Storage arg, MapOperationProblemBuilder problemBuilder) { - Context context = Context.getCurrent(); - if (arg instanceof AbstractLongStorage v) { - long[] out = new long[storage.size()]; - BitSet newMissing = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i) && i < v.size() && !v.isNa(i)) { - if (alwaysCastToDouble) { - out[i] = Double.doubleToRawLongBits(doDouble(storage.getItem(i), v.getItem(i), i, problemBuilder)); - } else { - Long newVal = doLong(storage.getItem(i), v.getItem(i), i, problemBuilder); - if (newVal == null) { - newMissing.set(i); - } else { - out[i] = newVal; - } - } - } else { - newMissing.set(i); - } - - context.safepoint(); - } - - return alwaysCastToDouble ? new DoubleStorage(out, storage.size(), newMissing) : new LongStorage(out, storage.size(), newMissing, INTEGER_RESULT_TYPE); - } else if (arg instanceof DoubleStorage v) { - long[] out = new long[storage.size()]; - BitSet newMissing = new BitSet(); - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i) && i < v.size() && !v.isNa(i)) { - out[i] = Double.doubleToRawLongBits(doDouble(storage.getItem(i), v.getItem(i), i, problemBuilder)); - } else { - newMissing.set(i); - } - - context.safepoint(); - } - return new DoubleStorage(out, storage.size(), newMissing); - } else { - throw new UnexpectedTypeException("a Number."); - } - } -} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/UnaryDoubleToLongOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/UnaryDoubleToLongOp.java deleted file mode 100644 index b939e16e5fc3..000000000000 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/UnaryDoubleToLongOp.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.enso.table.data.column.operation.map.numeric; - -import java.util.BitSet; -import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; -import org.enso.table.data.column.operation.map.UnaryMapOperation; -import org.enso.table.data.column.storage.numeric.DoubleStorage; -import org.enso.table.data.column.storage.numeric.LongStorage; -import org.enso.table.data.column.storage.type.IntegerType; -import org.graalvm.polyglot.Context; - -/** An operation that takes a single double argumebnt and returns a long. */ -public abstract class UnaryDoubleToLongOp extends UnaryMapOperation { - - public UnaryDoubleToLongOp(String name) { - super(name); - } - - protected abstract long doOperation(double value); - - @Override - protected LongStorage runUnaryMap( - DoubleStorage storage, MapOperationProblemBuilder problemBuilder) { - Context context = Context.getCurrent(); - BitSet newMissing = new BitSet(); - long[] newVals = new long[storage.size()]; - for (int i = 0; i < storage.size(); i++) { - if (!storage.isNa(i)) { - newVals[i] = doOperation(storage.getItem(i)); - } else { - newMissing.set(i); - } - - context.safepoint(); - } - - return new LongStorage(newVals, newVals.length, newMissing, IntegerType.INT_64); - } -} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/AddOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/AddOp.java new file mode 100644 index 000000000000..5296ec951616 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/AddOp.java @@ -0,0 +1,34 @@ +package org.enso.table.data.column.operation.map.numeric.arithmetic; + +import java.math.BigInteger; +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; +import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.type.IntegerType; + +public class AddOp> + extends NumericBinaryOpImplementation { + public AddOp() { + super(Storage.Maps.ADD); + } + + @Override + public double doDouble(double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { + return a + b; + } + + @Override + public Long doLong(long a, long b, int ix, MapOperationProblemBuilder problemBuilder) { + try { + return Math.addExact(a, b); + } catch (ArithmeticException e) { + problemBuilder.reportOverflow(IntegerType.INT_64, a, "+", b); + return null; + } + } + + @Override + public BigInteger doBigInteger( + BigInteger a, BigInteger b, int ix, MapOperationProblemBuilder problemBuilder) { + return a.add(b); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/DivideOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/DivideOp.java new file mode 100644 index 000000000000..e78915b6d590 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/DivideOp.java @@ -0,0 +1,19 @@ +package org.enso.table.data.column.operation.map.numeric.arithmetic; + +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; +import org.enso.table.data.column.storage.Storage; + +public class DivideOp> + extends NumericBinaryOpReturningDouble { + public DivideOp() { + super(Storage.Maps.DIV); + } + + @Override + public double doDouble(double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { + if (b == 0.0) { + problemBuilder.reportDivisionByZero(ix); + } + return a / b; + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/ModOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/ModOp.java new file mode 100644 index 000000000000..7edfcec7fd1f --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/ModOp.java @@ -0,0 +1,42 @@ +package org.enso.table.data.column.operation.map.numeric.arithmetic; + +import java.math.BigInteger; +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; +import org.enso.table.data.column.storage.Storage; + +public class ModOp> + extends NumericBinaryOpImplementation { + public ModOp() { + super(Storage.Maps.MOD); + } + + @Override + public double doDouble(double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { + if (b == 0.0) { + problemBuilder.reportDivisionByZero(ix); + } + + return a % b; + } + + @Override + public Long doLong(long a, long b, int ix, MapOperationProblemBuilder problemBuilder) { + if (b == 0) { + problemBuilder.reportDivisionByZero(ix); + return null; + } + + return a % b; + } + + @Override + public BigInteger doBigInteger( + BigInteger a, BigInteger b, int ix, MapOperationProblemBuilder problemBuilder) { + if (b.equals(BigInteger.ZERO)) { + problemBuilder.reportDivisionByZero(ix); + return null; + } + + return a.mod(b); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/MulOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/MulOp.java new file mode 100644 index 000000000000..be5961003357 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/MulOp.java @@ -0,0 +1,34 @@ +package org.enso.table.data.column.operation.map.numeric.arithmetic; + +import java.math.BigInteger; +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; +import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.type.IntegerType; + +public class MulOp> + extends NumericBinaryOpImplementation { + public MulOp() { + super(Storage.Maps.MUL); + } + + @Override + public double doDouble(double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { + return a * b; + } + + @Override + public Long doLong(long a, long b, int ix, MapOperationProblemBuilder problemBuilder) { + try { + return Math.multiplyExact(a, b); + } catch (ArithmeticException e) { + problemBuilder.reportOverflow(IntegerType.INT_64, a, "*", b); + return null; + } + } + + @Override + public BigInteger doBigInteger( + BigInteger a, BigInteger b, int ix, MapOperationProblemBuilder problemBuilder) { + return a.multiply(b); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/NumericBinaryOpDefinition.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/NumericBinaryOpDefinition.java new file mode 100644 index 000000000000..d4f93aeedfbd --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/NumericBinaryOpDefinition.java @@ -0,0 +1,13 @@ +package org.enso.table.data.column.operation.map.numeric.arithmetic; + +import java.math.BigInteger; +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; + +public interface NumericBinaryOpDefinition { + double doDouble(double a, double b, int ix, MapOperationProblemBuilder problemBuilder); + + Long doLong(long a, long b, int ix, MapOperationProblemBuilder problemBuilder); + + BigInteger doBigInteger( + BigInteger a, BigInteger b, int ix, MapOperationProblemBuilder problemBuilder); +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/NumericBinaryOpImplementation.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/NumericBinaryOpImplementation.java new file mode 100644 index 000000000000..353bfadfe8c5 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/NumericBinaryOpImplementation.java @@ -0,0 +1,269 @@ +package org.enso.table.data.column.operation.map.numeric.arithmetic; + +import org.enso.base.polyglot.NumericConverter; +import org.enso.table.data.column.operation.map.BinaryMapOperation; +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; +import org.enso.table.data.column.operation.map.numeric.helpers.BigIntegerArrayAdapter; +import org.enso.table.data.column.operation.map.numeric.helpers.DoubleArrayAdapter; +import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.numeric.AbstractLongStorage; +import org.enso.table.data.column.storage.numeric.BigIntegerStorage; +import org.enso.table.data.column.storage.numeric.DoubleStorage; +import org.enso.table.data.column.storage.numeric.LongStorage; +import org.enso.table.data.column.storage.type.IntegerType; +import org.enso.table.error.UnexpectedTypeException; +import org.graalvm.polyglot.Context; + +import java.math.BigInteger; +import java.util.BitSet; + +import static org.enso.table.data.column.operation.map.numeric.helpers.DoubleArrayAdapter.fromAnyStorage; + +/** + * An operation expecting a numeric argument and returning a numeric column. + */ +public abstract class NumericBinaryOpImplementation> extends BinaryMapOperation implements NumericBinaryOpDefinition { + + // The type to use for small integer results (regardless of the input bit size). + public static final IntegerType INTEGER_RESULT_TYPE = IntegerType.INT_64; + + public NumericBinaryOpImplementation(String name) { + super(name); + } + + @Override + public Storage runBinaryMap(I storage, Object arg, MapOperationProblemBuilder problemBuilder) { + if (arg == null) { + return allNullStorageOfSameType(storage); + } else { + if (arg instanceof BigInteger rhs) { + return switch (storage) { + case AbstractLongStorage s -> runBigIntegerMap(BigIntegerArrayAdapter.fromStorage(s), rhs, problemBuilder); + case BigIntegerStorage s -> runBigIntegerMap(BigIntegerArrayAdapter.fromStorage(s), rhs, problemBuilder); + case DoubleStorage s -> runDoubleMap(s, rhs.doubleValue(), problemBuilder); + case default -> + throw new IllegalStateException("Unsupported storage: " + storage.getClass().getCanonicalName()); + }; + } else if (NumericConverter.isCoercibleToLong(arg)) { + long argAsLong = NumericConverter.coerceToLong(arg); + return switch (storage) { + case AbstractLongStorage s -> runLongMap(s, argAsLong, problemBuilder); + case BigIntegerStorage s -> + runBigIntegerMap(BigIntegerArrayAdapter.fromStorage(s), BigInteger.valueOf(argAsLong), problemBuilder); + case DoubleStorage s -> runDoubleMap(s, (double) argAsLong, problemBuilder); + case default -> + throw new IllegalStateException("Unsupported storage: " + storage.getClass().getCanonicalName()); + }; + } else if (NumericConverter.isCoercibleToDouble(arg)) { + double doubleArg = NumericConverter.coerceToDouble(arg); + return switch (storage) { + case AbstractLongStorage s -> runDoubleMap(DoubleArrayAdapter.fromStorage(s), doubleArg, problemBuilder); + case BigIntegerStorage s -> runDoubleMap(DoubleArrayAdapter.fromStorage(s), doubleArg, problemBuilder); + case DoubleStorage s -> runDoubleMap(s, doubleArg, problemBuilder); + case default -> + throw new IllegalStateException("Unsupported storage: " + storage.getClass().getCanonicalName()); + }; + } else { + throw new UnexpectedTypeException("a Number."); + } + } + } + + @Override + public Storage runZip(I storage, Storage arg, MapOperationProblemBuilder problemBuilder) { + return switch (storage) { + case DoubleStorage lhs -> runDoubleZip(lhs, fromAnyStorage(arg), problemBuilder); + + case AbstractLongStorage lhs -> switch (arg) { + case AbstractLongStorage rhs -> runLongZip(lhs, rhs, problemBuilder); + case BigIntegerStorage rhs -> { + BigIntegerArrayAdapter left = BigIntegerArrayAdapter.fromStorage(lhs); + BigIntegerArrayAdapter right = BigIntegerArrayAdapter.fromStorage(rhs); + yield runBigIntegerZip(left, right, problemBuilder); + } + case DoubleStorage rhs -> runDoubleZip(DoubleArrayAdapter.fromStorage(lhs), rhs, problemBuilder); + case default -> throw new IllegalStateException("Unsupported storage: " + arg.getClass().getCanonicalName()); + }; + + case BigIntegerStorage lhs -> { + BigIntegerArrayAdapter left = BigIntegerArrayAdapter.fromStorage(lhs); + yield switch (arg) { + case AbstractLongStorage rhs -> { + BigIntegerArrayAdapter right = BigIntegerArrayAdapter.fromStorage(rhs); + yield runBigIntegerZip(left, right, problemBuilder); + } + case BigIntegerStorage rhs -> { + BigIntegerArrayAdapter right = BigIntegerArrayAdapter.fromStorage(rhs); + yield runBigIntegerZip(left, right, problemBuilder); + } + case DoubleStorage rhs -> runDoubleZip(DoubleArrayAdapter.fromStorage(lhs), rhs, problemBuilder); + case default -> throw new IllegalStateException("Unsupported storage: " + arg.getClass().getCanonicalName()); + }; + } + + case default -> throw new IllegalStateException("Unsupported storage: " + storage.getClass().getCanonicalName()); + }; + } + + protected DoubleStorage runDoubleZip(DoubleArrayAdapter a, DoubleArrayAdapter b, + MapOperationProblemBuilder problemBuilder) { + Context context = Context.getCurrent(); + int n = a.size(); + int m = Math.min(a.size(), b.size()); + long[] out = new long[n]; + BitSet newMissing = new BitSet(); + for (int i = 0; i < m; i++) { + if (a.isNa(i) || b.isNa(i)) { + newMissing.set(i); + } else { + double r = doDouble(a.getItemAsDouble(i), b.getItemAsDouble(i), i, problemBuilder); + out[i] = Double.doubleToRawLongBits(r); + } + + context.safepoint(); + } + + if (m < n) { + newMissing.set(m, n); + } + + return new DoubleStorage(out, n, newMissing); + } + + private static Storage allNullStorageOfSameType(Storage storage) { + return switch (storage) { + case AbstractLongStorage s -> LongStorage.makeEmpty(storage.size(), INTEGER_RESULT_TYPE); + case BigIntegerStorage s -> BigIntegerStorage.makeEmpty(storage.size()); + case DoubleStorage s -> DoubleStorage.makeEmpty(storage.size()); + case default -> throw new IllegalStateException("Unsupported storage: " + storage.getClass().getCanonicalName()); + }; + } + + protected DoubleStorage runDoubleMap(DoubleArrayAdapter a, Double b, MapOperationProblemBuilder problemBuilder) { + if (b == null) { + return DoubleStorage.makeEmpty(a.size()); + } + + double bNonNull = b; + Context context = Context.getCurrent(); + int n = a.size(); + long[] out = new long[n]; + BitSet newMissing = new BitSet(); + for (int i = 0; i < n; i++) { + if (a.isNa(i)) { + newMissing.set(i); + } else { + double r = doDouble(a.getItemAsDouble(i), bNonNull, i, problemBuilder); + out[i] = Double.doubleToRawLongBits(r); + } + + context.safepoint(); + } + + return new DoubleStorage(out, n, newMissing); + } + + protected LongStorage runLongZip(AbstractLongStorage a, AbstractLongStorage b, + MapOperationProblemBuilder problemBuilder) { + Context context = Context.getCurrent(); + int n = a.size(); + int m = Math.min(a.size(), b.size()); + long[] out = new long[n]; + BitSet newMissing = new BitSet(); + for (int i = 0; i < m; i++) { + if (a.isNa(i) || b.isNa(i)) { + newMissing.set(i); + } else { + Long r = doLong(a.getItem(i), b.getItem(i), i, problemBuilder); + if (r == null) { + newMissing.set(i); + } else { + out[i] = r; + } + } + + context.safepoint(); + } + + if (m < n) { + newMissing.set(m, n); + } + + return new LongStorage(out, n, newMissing, INTEGER_RESULT_TYPE); + } + + protected LongStorage runLongMap(AbstractLongStorage a, Long b, MapOperationProblemBuilder problemBuilder) { + if (b == null) { + return LongStorage.makeEmpty(a.size(), INTEGER_RESULT_TYPE); + } + + long bNonNull = b; + Context context = Context.getCurrent(); + int n = a.size(); + long[] out = new long[n]; + BitSet newMissing = new BitSet(); + for (int i = 0; i < n; i++) { + if (a.isNa(i)) { + newMissing.set(i); + } else { + Long r = doLong(a.getItem(i), bNonNull, i, problemBuilder); + if (r == null) { + newMissing.set(i); + } else { + out[i] = r; + } + } + + context.safepoint(); + } + + return new LongStorage(out, n, newMissing, INTEGER_RESULT_TYPE); + } + + protected BigIntegerStorage runBigIntegerZip(BigIntegerArrayAdapter a, BigIntegerArrayAdapter b, + MapOperationProblemBuilder problemBuilder) { + Context context = Context.getCurrent(); + int n = a.size(); + int m = Math.min(a.size(), b.size()); + BigInteger[] out = new BigInteger[n]; + BitSet newMissing = new BitSet(); + for (int i = 0; i < m; i++) { + BigInteger x = a.getItem(i); + BigInteger y = b.getItem(i); + if (x == null || y == null) { + newMissing.set(i); + } else { + BigInteger r = doBigInteger(x, y, i, problemBuilder); + out[i] = r; + } + + context.safepoint(); + } + + if (m < n) { + newMissing.set(m, n); + } + + return new BigIntegerStorage(out, n); + } + + protected BigIntegerStorage runBigIntegerMap(BigIntegerArrayAdapter a, BigInteger b, + MapOperationProblemBuilder problemBuilder) { + Context context = Context.getCurrent(); + int n = a.size(); + BigInteger[] out = new BigInteger[n]; + for (int i = 0; i < n; i++) { + BigInteger x = a.getItem(i); + if (x == null || b == null) { + out[i] = null; + } else { + BigInteger r = doBigInteger(x, b, i, problemBuilder); + out[i] = r; + } + + context.safepoint(); + } + + return new BigIntegerStorage(out, n); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/NumericBinaryOpReturningDouble.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/NumericBinaryOpReturningDouble.java new file mode 100644 index 000000000000..01d93e6ca753 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/NumericBinaryOpReturningDouble.java @@ -0,0 +1,46 @@ +package org.enso.table.data.column.operation.map.numeric.arithmetic; + +import org.enso.base.polyglot.NumericConverter; +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; +import org.enso.table.data.column.operation.map.numeric.helpers.DoubleArrayAdapter; +import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.numeric.DoubleStorage; + +import java.math.BigInteger; + +import static org.enso.table.data.column.operation.map.numeric.helpers.DoubleArrayAdapter.fromAnyStorage; + +public abstract class NumericBinaryOpReturningDouble> extends NumericBinaryOpImplementation { + public NumericBinaryOpReturningDouble(String name) { + super(name); + } + + @Override + public Storage runBinaryMap(I storage, Object arg, MapOperationProblemBuilder problemBuilder) { + if (arg == null) { + return DoubleStorage.makeEmpty(storage.size()); + } + + DoubleArrayAdapter lhs = fromAnyStorage(storage); + double rhs = (arg instanceof BigInteger bigInteger) ? bigInteger.doubleValue() : + NumericConverter.coerceToDouble(arg); + return runDoubleMap(lhs, rhs, problemBuilder); + } + + @Override + public Storage runZip(I storage, Storage arg, MapOperationProblemBuilder problemBuilder) { + DoubleArrayAdapter lhs = fromAnyStorage(storage); + DoubleArrayAdapter rhs = fromAnyStorage(arg); + return runDoubleZip(lhs, rhs, problemBuilder); + } + + @Override + public Long doLong(long a, long b, int ix, MapOperationProblemBuilder problemBuilder) { + throw new IllegalStateException("Impossible: should not reach here - a NumericOpReturningDouble should always use the doDouble branch."); + } + + @Override + public BigInteger doBigInteger(BigInteger a, BigInteger b, int ix, MapOperationProblemBuilder problemBuilder) { + throw new IllegalStateException("Impossible: should not reach here - a NumericOpReturningDouble should always use the doDouble branch."); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/PowerOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/PowerOp.java new file mode 100644 index 000000000000..62a7efa93775 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/PowerOp.java @@ -0,0 +1,16 @@ +package org.enso.table.data.column.operation.map.numeric.arithmetic; + +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; +import org.enso.table.data.column.storage.Storage; + +public class PowerOp> + extends NumericBinaryOpReturningDouble { + public PowerOp() { + super(Storage.Maps.POWER); + } + + @Override + public double doDouble(double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { + return Math.pow(a, b); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/SubOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/SubOp.java new file mode 100644 index 000000000000..c9a89d1cec63 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/arithmetic/SubOp.java @@ -0,0 +1,34 @@ +package org.enso.table.data.column.operation.map.numeric.arithmetic; + +import java.math.BigInteger; +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; +import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.type.IntegerType; + +public class SubOp> + extends NumericBinaryOpImplementation { + public SubOp() { + super(Storage.Maps.SUB); + } + + @Override + public double doDouble(double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { + return a - b; + } + + @Override + public Long doLong(long a, long b, int ix, MapOperationProblemBuilder problemBuilder) { + try { + return Math.subtractExact(a, b); + } catch (ArithmeticException e) { + problemBuilder.reportOverflow(IntegerType.INT_64, a, "-", b); + return null; + } + } + + @Override + public BigInteger doBigInteger( + BigInteger a, BigInteger b, int ix, MapOperationProblemBuilder problemBuilder) { + return a.subtract(b); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/EqualsComparison.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/EqualsComparison.java new file mode 100644 index 000000000000..4d5a6e76c8f5 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/EqualsComparison.java @@ -0,0 +1,48 @@ +package org.enso.table.data.column.operation.map.numeric.comparisons; + +import java.math.BigInteger; +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; +import org.enso.table.data.column.operation.map.numeric.helpers.DoubleArrayAdapter; +import org.enso.table.data.column.storage.BoolStorage; +import org.enso.table.data.column.storage.Storage; + +public class EqualsComparison> + extends NumericComparison { + public EqualsComparison() { + super(Storage.Maps.EQ); + } + + @Override + protected boolean doDouble(double a, double b) { + return a == b; + } + + @Override + protected BoolStorage runDoubleMap( + DoubleArrayAdapter lhs, double rhs, MapOperationProblemBuilder problemBuilder) { + problemBuilder.reportFloatingPointEquality(-1); + return super.runDoubleMap(lhs, rhs, problemBuilder); + } + + @Override + protected BoolStorage runDoubleZip( + DoubleArrayAdapter lhs, DoubleArrayAdapter rhs, MapOperationProblemBuilder problemBuilder) { + problemBuilder.reportFloatingPointEquality(-1); + return super.runDoubleZip(lhs, rhs, problemBuilder); + } + + @Override + protected boolean doLong(long a, long b) { + return a == b; + } + + @Override + protected boolean doBigInteger(BigInteger a, BigInteger b) { + return a.equals(b); + } + + @Override + protected boolean onOtherType(Object a, Object b) { + return false; + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/GreaterComparison.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/GreaterComparison.java new file mode 100644 index 000000000000..b7155432a9b3 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/GreaterComparison.java @@ -0,0 +1,26 @@ +package org.enso.table.data.column.operation.map.numeric.comparisons; + +import java.math.BigInteger; +import org.enso.table.data.column.storage.Storage; + +public class GreaterComparison> + extends NumericComparison { + public GreaterComparison() { + super(Storage.Maps.GT); + } + + @Override + protected boolean doDouble(double a, double b) { + return a > b; + } + + @Override + protected boolean doLong(long a, long b) { + return a > b; + } + + @Override + protected boolean doBigInteger(BigInteger a, BigInteger b) { + return a.compareTo(b) > 0; + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/GreaterOrEqualComparison.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/GreaterOrEqualComparison.java new file mode 100644 index 000000000000..9ab0ee3a849f --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/GreaterOrEqualComparison.java @@ -0,0 +1,26 @@ +package org.enso.table.data.column.operation.map.numeric.comparisons; + +import java.math.BigInteger; +import org.enso.table.data.column.storage.Storage; + +public class GreaterOrEqualComparison> + extends NumericComparison { + public GreaterOrEqualComparison() { + super(Storage.Maps.GTE); + } + + @Override + protected boolean doDouble(double a, double b) { + return a >= b; + } + + @Override + protected boolean doLong(long a, long b) { + return a >= b; + } + + @Override + protected boolean doBigInteger(BigInteger a, BigInteger b) { + return a.compareTo(b) >= 0; + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/LessComparison.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/LessComparison.java new file mode 100644 index 000000000000..1aa34a517aed --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/LessComparison.java @@ -0,0 +1,26 @@ +package org.enso.table.data.column.operation.map.numeric.comparisons; + +import java.math.BigInteger; +import org.enso.table.data.column.storage.Storage; + +public class LessComparison> + extends NumericComparison { + public LessComparison() { + super(Storage.Maps.LT); + } + + @Override + protected boolean doDouble(double a, double b) { + return a < b; + } + + @Override + protected boolean doLong(long a, long b) { + return a < b; + } + + @Override + protected boolean doBigInteger(BigInteger a, BigInteger b) { + return a.compareTo(b) < 0; + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/LessOrEqualComparison.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/LessOrEqualComparison.java new file mode 100644 index 000000000000..22c80c32e56b --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/LessOrEqualComparison.java @@ -0,0 +1,26 @@ +package org.enso.table.data.column.operation.map.numeric.comparisons; + +import java.math.BigInteger; +import org.enso.table.data.column.storage.Storage; + +public class LessOrEqualComparison> + extends NumericComparison { + public LessOrEqualComparison() { + super(Storage.Maps.LTE); + } + + @Override + protected boolean doDouble(double a, double b) { + return a <= b; + } + + @Override + protected boolean doLong(long a, long b) { + return a <= b; + } + + @Override + protected boolean doBigInteger(BigInteger a, BigInteger b) { + return a.compareTo(b) <= 0; + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/NumericComparison.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/NumericComparison.java new file mode 100644 index 000000000000..4b60f200e254 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/comparisons/NumericComparison.java @@ -0,0 +1,333 @@ +package org.enso.table.data.column.operation.map.numeric.comparisons; + +import org.enso.base.CompareException; +import org.enso.base.polyglot.NumericConverter; +import org.enso.table.data.column.operation.map.BinaryMapOperation; +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; +import org.enso.table.data.column.operation.map.numeric.helpers.BigIntegerArrayAdapter; +import org.enso.table.data.column.operation.map.numeric.helpers.DoubleArrayAdapter; +import org.enso.table.data.column.storage.BoolStorage; +import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.numeric.AbstractLongStorage; +import org.enso.table.data.column.storage.numeric.BigIntegerStorage; +import org.enso.table.data.column.storage.numeric.DoubleStorage; +import org.enso.table.data.column.storage.type.AnyObjectType; +import org.enso.table.util.BitSets; +import org.graalvm.polyglot.Context; + +import java.math.BigInteger; +import java.util.BitSet; + +import static org.enso.table.data.column.operation.map.numeric.helpers.DoubleArrayAdapter.fromAnyStorage; + +public abstract class NumericComparison> extends BinaryMapOperation { + + protected abstract boolean doDouble(double a, double b); + + protected abstract boolean doLong(long a, long b); + + protected abstract boolean doBigInteger(BigInteger a, BigInteger b); + + protected boolean onOtherType(Object a, Object b) { + throw new CompareException(a, b); + } + + public NumericComparison(String name) { + super(name); + } + + @Override + public BoolStorage runBinaryMap(I storage, Object arg, MapOperationProblemBuilder problemBuilder) { + if (arg == null) { + return BoolStorage.makeEmpty(storage.size()); + } else if (arg instanceof BigInteger bigInteger) { + return switch (storage) { + case AbstractLongStorage s -> + runBigIntegerMap(BigIntegerArrayAdapter.fromStorage(s), bigInteger, problemBuilder); + case BigIntegerStorage s -> runBigIntegerMap(BigIntegerArrayAdapter.fromStorage(s), bigInteger, problemBuilder); + case DoubleStorage s -> runDoubleMap(s, bigInteger.doubleValue(), problemBuilder); + default -> throw new IllegalStateException("Unsupported lhs storage: " + storage.getClass().getCanonicalName()); + }; + } else if (NumericConverter.isCoercibleToLong(arg)) { + long rhs = NumericConverter.coerceToLong(arg); + return switch (storage) { + case AbstractLongStorage s -> runLongMap(s, rhs, problemBuilder); + case BigIntegerStorage s -> + runBigIntegerMap(BigIntegerArrayAdapter.fromStorage(s), BigInteger.valueOf(rhs), problemBuilder); + case DoubleStorage s -> runDoubleMap(s, (double) rhs, problemBuilder); + default -> throw new IllegalStateException("Unsupported lhs storage: " + storage.getClass().getCanonicalName()); + }; + } else if (NumericConverter.isCoercibleToDouble(arg)) { + DoubleArrayAdapter lhs = DoubleArrayAdapter.fromAnyStorage(storage); + double rhs = NumericConverter.coerceToDouble(arg); + return runDoubleMap(lhs, rhs, problemBuilder); + } else { + int n = storage.size(); + BitSet missing = new BitSet(); + BitSet comparisonResults = new BitSet(); + Context context = Context.getCurrent(); + for (int i = 0; i < n; ++i) { + Object item = storage.getItemBoxed(i); + if (item == null) { + missing.set(i); + } else { + boolean r = onOtherType(item, arg); + if (r) { + comparisonResults.set(i); + } + } + + context.safepoint(); + } + + return new BoolStorage(comparisonResults, missing, n, false); + } + } + + protected BoolStorage runLongMap(AbstractLongStorage lhs, long rhs, MapOperationProblemBuilder problemBuilder) { + int n = lhs.size(); + BitSet comparisonResults = new BitSet(); + BitSet missing = BitSets.makeDuplicate(lhs.getIsMissing()); + Context context = Context.getCurrent(); + for (int i = 0; i < n; ++i) { + if (!lhs.isNa(i)) { + long item = lhs.getItem(i); + boolean r = doLong(item, rhs); + if (r) { + comparisonResults.set(i); + } + } + + context.safepoint(); + } + + return new BoolStorage(comparisonResults, missing, n, false); + } + + protected BoolStorage runDoubleMap(DoubleArrayAdapter lhs, double rhs, MapOperationProblemBuilder problemBuilder) { + int n = lhs.size(); + BitSet comparisonResults = new BitSet(); + BitSet missing = new BitSet(); + Context context = Context.getCurrent(); + for (int i = 0; i < n; ++i) { + if (lhs.isNa(i)) { + missing.set(i); + } else { + double item = lhs.getItemAsDouble(i); + boolean r = doDouble(item, rhs); + if (r) { + comparisonResults.set(i); + } + } + + context.safepoint(); + } + + return new BoolStorage(comparisonResults, missing, n, false); + } + + protected BoolStorage runBigIntegerMap(BigIntegerArrayAdapter lhs, BigInteger rhs, + MapOperationProblemBuilder problemBuilder) { + int n = lhs.size(); + BitSet comparisonResults = new BitSet(); + BitSet missing = new BitSet(); + Context context = Context.getCurrent(); + for (int i = 0; i < n; ++i) { + BigInteger item = lhs.getItem(i); + if (item == null) { + missing.set(i); + } else { + boolean r = doBigInteger(item, rhs); + if (r) { + comparisonResults.set(i); + } + } + + context.safepoint(); + } + + return new BoolStorage(comparisonResults, missing, n, false); + } + + @Override + public BoolStorage runZip(I storage, Storage arg, MapOperationProblemBuilder problemBuilder) { + return switch (storage) { + case DoubleStorage lhs -> { + if (arg.getType() instanceof AnyObjectType) { + yield runMixedZip(lhs, arg, problemBuilder); + } else { + yield runDoubleZip(lhs, fromAnyStorage(arg), problemBuilder); + } + } + + case AbstractLongStorage lhs -> switch (arg) { + case AbstractLongStorage rhs -> runLongZip(lhs, rhs, problemBuilder); + case BigIntegerStorage rhs -> { + BigIntegerArrayAdapter left = BigIntegerArrayAdapter.fromStorage(lhs); + BigIntegerArrayAdapter right = BigIntegerArrayAdapter.fromStorage(rhs); + yield runBigIntegerZip(left, right, problemBuilder); + } + case DoubleStorage rhs -> runDoubleZip(DoubleArrayAdapter.fromStorage(lhs), rhs, problemBuilder); + case default -> runMixedZip(lhs, arg, problemBuilder); + }; + + case BigIntegerStorage lhs -> { + BigIntegerArrayAdapter left = BigIntegerArrayAdapter.fromStorage(lhs); + yield switch (arg) { + case AbstractLongStorage rhs -> { + BigIntegerArrayAdapter right = BigIntegerArrayAdapter.fromStorage(rhs); + yield runBigIntegerZip(left, right, problemBuilder); + } + case BigIntegerStorage rhs -> { + BigIntegerArrayAdapter right = BigIntegerArrayAdapter.fromStorage(rhs); + yield runBigIntegerZip(left, right, problemBuilder); + } + case DoubleStorage rhs -> runDoubleZip(DoubleArrayAdapter.fromStorage(lhs), rhs, problemBuilder); + case default -> runMixedZip(lhs, arg, problemBuilder); + }; + } + + case default -> + throw new IllegalStateException("Unsupported lhs storage: " + storage.getClass().getCanonicalName()); + }; + } + + protected BoolStorage runLongZip(AbstractLongStorage lhs, AbstractLongStorage rhs, + MapOperationProblemBuilder problemBuilder) { + int n = lhs.size(); + int m = Math.min(lhs.size(), rhs.size()); + BitSet comparisonResults = new BitSet(); + BitSet missing = new BitSet(); + Context context = Context.getCurrent(); + for (int i = 0; i < m; ++i) { + if (lhs.isNa(i) || rhs.isNa(i)) { + missing.set(i); + } else { + long x = lhs.getItem(i); + long y = rhs.getItem(i); + boolean r = doLong(x, y); + if (r) { + comparisonResults.set(i); + } + } + + context.safepoint(); + } + + if (m < n) { + missing.set(m, n); + } + + return new BoolStorage(comparisonResults, missing, n, false); + } + + protected BoolStorage runDoubleZip(DoubleArrayAdapter lhs, DoubleArrayAdapter rhs, + MapOperationProblemBuilder problemBuilder) { + int n = lhs.size(); + int m = Math.min(lhs.size(), rhs.size()); + BitSet comparisonResults = new BitSet(); + BitSet missing = new BitSet(); + Context context = Context.getCurrent(); + for (int i = 0; i < m; ++i) { + if (lhs.isNa(i) || rhs.isNa(i)) { + missing.set(i); + } else { + double x = lhs.getItemAsDouble(i); + double y = rhs.getItemAsDouble(i); + boolean r = doDouble(x, y); + if (r) { + comparisonResults.set(i); + } + } + + context.safepoint(); + } + + if (m < n) { + missing.set(m, n); + } + + return new BoolStorage(comparisonResults, missing, n, false); + } + + protected BoolStorage runBigIntegerZip(BigIntegerArrayAdapter lhs, BigIntegerArrayAdapter rhs, + MapOperationProblemBuilder problemBuilder) { + int n = lhs.size(); + int m = Math.min(lhs.size(), rhs.size()); + BitSet comparisonResults = new BitSet(); + BitSet missing = new BitSet(); + Context context = Context.getCurrent(); + for (int i = 0; i < m; ++i) { + BigInteger x = lhs.getItem(i); + BigInteger y = rhs.getItem(i); + if (x == null || y == null) { + missing.set(i); + } else { + boolean r = doBigInteger(x, y); + if (r) { + comparisonResults.set(i); + } + } + + context.safepoint(); + } + + if (m < n) { + missing.set(m, n); + } + + return new BoolStorage(comparisonResults, missing, n, false); + } + + protected BoolStorage runMixedZip(Storage lhs, Storage rhs, MapOperationProblemBuilder problemBuilder) { + int n = lhs.size(); + int m = Math.min(lhs.size(), rhs.size()); + BitSet comparisonResults = new BitSet(); + BitSet missing = new BitSet(); + Context context = Context.getCurrent(); + for (int i = 0; i < m; ++i) { + Object x = lhs.getItemBoxed(i); + Object y = rhs.getItemBoxed(i); + if (x == null || y == null) { + missing.set(i); + } else { + boolean r = false; + // Any number is coercible to double, if the value is not coercible, it is not a supported number type. + if (NumericConverter.isCoercibleToDouble(x) && NumericConverter.isCoercibleToDouble(y)) { + + // If any of the values is decimal like, then decimal type is used for comparison. + if (NumericConverter.isDecimalLike(x) || NumericConverter.isDecimalLike(y)) { + double a = NumericConverter.coerceToDouble(x); + double b = NumericConverter.coerceToDouble(y); + r = doDouble(a, b); + } else { + if (x instanceof BigInteger || y instanceof BigInteger) { + BigInteger a = NumericConverter.coerceToBigInteger(x); + BigInteger b = NumericConverter.coerceToBigInteger(y); + r = doBigInteger(a, b); + } else { + long a = NumericConverter.coerceToLong(x); + long b = NumericConverter.coerceToLong(y); + r = doLong(a, b); + } + } + } else { + r = onOtherType(x, y); + } + + if (r) { + comparisonResults.set(i); + } + } + + context.safepoint(); + } + + if (m < n) { + missing.set(m, n); + } + + return new BoolStorage(comparisonResults, missing, n, false); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/helpers/BigIntegerArrayAdapter.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/helpers/BigIntegerArrayAdapter.java new file mode 100644 index 000000000000..62f2bbc919c2 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/helpers/BigIntegerArrayAdapter.java @@ -0,0 +1,60 @@ +package org.enso.table.data.column.operation.map.numeric.helpers; + +import java.math.BigInteger; +import org.enso.table.data.column.storage.numeric.AbstractLongStorage; +import org.enso.table.data.column.storage.numeric.BigIntegerStorage; + +public interface BigIntegerArrayAdapter { + BigInteger getItem(int i); + + int size(); + + static BigIntegerArrayAdapter fromStorage(BigIntegerStorage storage) { + return new BigIntegerStorageAsBigInteger(storage); + } + + static BigIntegerArrayAdapter fromStorage(AbstractLongStorage storage) { + return new LongStorageAsBigInteger(storage); + } + + class BigIntegerStorageAsBigInteger implements BigIntegerArrayAdapter { + private final BigIntegerStorage storage; + + private BigIntegerStorageAsBigInteger(BigIntegerStorage storage) { + this.storage = storage; + } + + @Override + public BigInteger getItem(int i) { + return storage.getItemBoxed(i); + } + + @Override + public int size() { + return storage.size(); + } + } + + class LongStorageAsBigInteger implements BigIntegerArrayAdapter { + private final AbstractLongStorage storage; + + private LongStorageAsBigInteger(AbstractLongStorage storage) { + this.storage = storage; + } + + @Override + public BigInteger getItem(int i) { + if (storage.isNa(i)) { + return null; + } else { + long x = storage.getItem(i); + return BigInteger.valueOf(x); + } + } + + @Override + public int size() { + return storage.size(); + } + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/helpers/DoubleArrayAdapter.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/helpers/DoubleArrayAdapter.java new file mode 100644 index 000000000000..7e4702068732 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/helpers/DoubleArrayAdapter.java @@ -0,0 +1,85 @@ +package org.enso.table.data.column.operation.map.numeric.helpers; + +import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.numeric.AbstractLongStorage; +import org.enso.table.data.column.storage.numeric.BigIntegerStorage; +import org.enso.table.data.column.storage.numeric.DoubleStorage; + +import java.math.BigInteger; + +public interface DoubleArrayAdapter { + double getItemAsDouble(int i); + + boolean isNa(int i); + + int size(); + + static DoubleArrayAdapter fromStorage(BigIntegerStorage storage) { + return new BigIntegerStorageAsDouble(storage); + } + + static DoubleArrayAdapter fromStorage(AbstractLongStorage storage) { + return new LongStorageAsDouble(storage); + } + + static DoubleArrayAdapter fromStorage(DoubleStorage storage) { + return storage; + } + + static DoubleArrayAdapter fromAnyStorage(Storage storage) { + return switch (storage) { + case DoubleStorage s -> fromStorage(s); + case AbstractLongStorage s -> fromStorage(s); + case BigIntegerStorage s -> fromStorage(s); + case default -> throw new IllegalStateException("Unsupported storage: " + storage.getClass().getCanonicalName()); + }; + } + + class LongStorageAsDouble implements DoubleArrayAdapter { + private final AbstractLongStorage storage; + + private LongStorageAsDouble(AbstractLongStorage storage) { + this.storage = storage; + } + + @Override + public double getItemAsDouble(int i) { + long x = storage.getItem(i); + return (double) x; + } + + @Override + public boolean isNa(int i) { + return storage.isNa(i); + } + + @Override + public int size() { + return storage.size(); + } + } + + class BigIntegerStorageAsDouble implements DoubleArrayAdapter { + private final BigIntegerStorage storage; + + private BigIntegerStorageAsDouble(BigIntegerStorage storage) { + this.storage = storage; + } + + @Override + public double getItemAsDouble(int i) { + BigInteger x = storage.getItem(i); + return x.doubleValue(); + } + + @Override + public boolean isNa(int i) { + return storage.getItem(i) == null; + } + + @Override + public int size() { + return storage.size(); + } + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/isin/BigIntegerIsInOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/isin/BigIntegerIsInOp.java new file mode 100644 index 000000000000..929641918671 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/isin/BigIntegerIsInOp.java @@ -0,0 +1,36 @@ +package org.enso.table.data.column.operation.map.numeric.isin; + +import org.enso.base.polyglot.NumericConverter; +import org.enso.table.data.column.operation.map.SpecializedIsInOp; +import org.enso.table.data.column.storage.Storage; +import org.enso.table.data.column.storage.numeric.AbstractLongStorage; +import org.enso.table.data.column.storage.numeric.BigIntegerStorage; +import org.graalvm.polyglot.Context; + +import java.math.BigInteger; +import java.util.HashSet; +import java.util.List; + +public class BigIntegerIsInOp> extends SpecializedIsInOp { + @Override + protected CompactRepresentation prepareList(List list) { + Context context = Context.getCurrent(); + HashSet set = new HashSet<>(); + boolean hasNulls = false; + for (Object o : list) { + hasNulls |= o == null; + + if (o instanceof BigInteger bigInteger) { + set.add(bigInteger); + } else { + Long x = NumericConverter.tryConvertingToLong(o); + if (x != null) { + set.add(BigInteger.valueOf(x)); + } + } + + context.safepoint(); + } + return new CompactRepresentation<>(set, hasNulls); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleIsInOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/isin/DoubleIsInOp.java similarity index 92% rename from std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleIsInOp.java rename to std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/isin/DoubleIsInOp.java index 65f4eddbd566..f7cf3c76cb48 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/DoubleIsInOp.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/isin/DoubleIsInOp.java @@ -1,4 +1,4 @@ -package org.enso.table.data.column.operation.map.numeric; +package org.enso.table.data.column.operation.map.numeric.isin; import java.util.HashSet; import java.util.List; diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongIsInOp.java b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/isin/LongIsInOp.java similarity index 92% rename from std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongIsInOp.java rename to std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/isin/LongIsInOp.java index 06102430c4ea..5c3d77196a1b 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/LongIsInOp.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/operation/map/numeric/isin/LongIsInOp.java @@ -1,4 +1,4 @@ -package org.enso.table.data.column.operation.map.numeric; +package org.enso.table.data.column.operation.map.numeric.isin; import java.util.HashSet; import java.util.List; diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/BoolStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/BoolStorage.java index 64292dac3fb6..eb4b05a5c3e2 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/BoolStorage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/BoolStorage.java @@ -15,8 +15,8 @@ import org.enso.table.data.mask.SliceRange; import org.enso.table.error.UnexpectedColumnTypeException; import org.enso.table.error.UnexpectedTypeException; +import org.enso.table.problems.AggregatedProblems; import org.enso.table.problems.WithAggregatedProblems; -import org.enso.table.problems.WithProblems; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; @@ -45,6 +45,10 @@ public static BoolStorage makeEmpty(int size) { return new BoolStorage(new BitSet(), isMissing, size, false); } + public static BoolStorage makeConstant(int size, boolean r) { + return new BoolStorage(new BitSet(), new BitSet(), size, r); + } + @Override public int size() { return size; @@ -119,22 +123,23 @@ public BitSet getIsMissing() { * accordingly. If `arg` is true, new values are `values || isMissing` and if `arg` is false, new * values are `values && (~isMissing)`. */ - private BoolStorage fillMissingBoolean(boolean arg) { + private WithAggregatedProblems> fillMissingBoolean(boolean arg) { final var newValues = (BitSet) values.clone(); if (arg) { newValues.or(isMissing); } else { newValues.andNot(isMissing); } - return new BoolStorage(newValues, new BitSet(), size, negated); + var storage = new BoolStorage(newValues, new BitSet(), size, negated); + return new WithAggregatedProblems<>(storage, AggregatedProblems.of()); } @Override - public Storage fillMissing(Value arg) { + public WithAggregatedProblems> fillMissing(Value arg, StorageType commonType) { if (arg.isBoolean()) { return fillMissingBoolean(arg.asBoolean()); } else { - return super.fillMissing(arg); + return super.fillMissing(arg, commonType); } } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/MixedStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/MixedStorage.java index 215f307f6104..0736c163c389 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/MixedStorage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/MixedStorage.java @@ -4,6 +4,7 @@ import org.enso.table.data.column.builder.MixedBuilder; import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; import org.enso.table.data.column.storage.type.AnyObjectType; +import org.enso.table.data.column.storage.type.BigIntegerType; import org.enso.table.data.column.storage.type.FloatType; import org.enso.table.data.column.storage.type.IntegerType; import org.enso.table.data.column.storage.type.StorageType; @@ -48,6 +49,26 @@ protected SpecializedStorage newInstance(Object[] data, int size) { return new MixedStorage(data, size); } + private boolean isNumeric(StorageType type) { + return type instanceof IntegerType + || type instanceof FloatType + || type instanceof BigIntegerType; + } + + private StorageType commonNumericType(StorageType a, StorageType b) { + assert isNumeric(a); + assert isNumeric(b); + if (a instanceof FloatType || b instanceof FloatType) { + return FloatType.FLOAT_64; + } else if (a instanceof BigIntegerType || b instanceof BigIntegerType) { + return BigIntegerType.INSTANCE; + } else { + assert a instanceof IntegerType; + assert b instanceof IntegerType; + return IntegerType.INT_64; + } + } + @Override public StorageType inferPreciseType() { if (inferredType == null) { @@ -63,11 +84,9 @@ public StorageType inferPreciseType() { var itemType = StorageType.forBoxedItem(item); if (currentType == null) { currentType = itemType; - } else if (currentType != itemType) { - // Allow mixed integer and float types in a column, returning a float. - if ((itemType instanceof IntegerType && currentType instanceof FloatType) - || (itemType instanceof FloatType && currentType instanceof IntegerType)) { - currentType = FloatType.FLOAT_64; + } else if (!currentType.equals(itemType)) { + if (isNumeric(currentType) && isNumeric(itemType)) { + currentType = commonNumericType(currentType, itemType); } else { currentType = AnyObjectType.INSTANCE; } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/Storage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/Storage.java index 990acd14ad15..98582fb80ea5 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/Storage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/Storage.java @@ -8,7 +8,6 @@ import org.enso.base.polyglot.Polyglot_Utils; import org.enso.table.data.column.builder.Builder; import org.enso.table.data.column.builder.InferredBuilder; -import org.enso.table.data.column.builder.MixedBuilder; import org.enso.table.data.column.operation.cast.CastProblemBuilder; import org.enso.table.data.column.operation.cast.StorageConverter; import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; @@ -17,6 +16,7 @@ import org.enso.table.data.column.storage.type.StorageType; import org.enso.table.data.mask.OrderMask; import org.enso.table.data.mask.SliceRange; +import org.enso.table.problems.WithAggregatedProblems; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; @@ -380,10 +380,12 @@ private void checkFallback(Object fallback, StorageType storageType, String oper * Return a new storage, where missing elements have been replaced by arg. * * @param arg the value to use for missing elements + * @param commonType the common type of this storage and the provided value * @return a new storage, with all missing elements replaced by arg */ - public Storage fillMissing(Value arg) { - return fillMissingHelper(arg, new MixedBuilder(size())); + public WithAggregatedProblems> fillMissing(Value arg, StorageType commonType) { + Builder builder = Builder.getForType(commonType, size()); + return fillMissingHelper(arg, builder); } /** @@ -393,7 +395,8 @@ public Storage fillMissing(Value arg) { * @param commonType a common type that should fit values from both storages * @return a new storage with missing values filled */ - public Storage fillMissingFrom(Storage other, StorageType commonType) { + public WithAggregatedProblems> fillMissingFrom( + Storage other, StorageType commonType) { var builder = Builder.getForType(commonType, size()); Context context = Context.getCurrent(); for (int i = 0; i < size(); i++) { @@ -405,10 +408,11 @@ public Storage fillMissingFrom(Storage other, StorageType commonType) { context.safepoint(); } - return builder.seal(); + + return builder.sealWithProblems(); } - protected final Storage fillMissingHelper(Value arg, Builder builder) { + protected final WithAggregatedProblems> fillMissingHelper(Value arg, Builder builder) { Object convertedFallback = Polyglot_Utils.convertPolyglotValue(arg); Context context = Context.getCurrent(); for (int i = 0; i < size(); i++) { @@ -421,7 +425,8 @@ protected final Storage fillMissingHelper(Value arg, Builder builder) { context.safepoint(); } - return builder.seal(); + + return builder.sealWithProblems(); } /** diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/StringStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/StringStorage.java index ed663868a33a..8f5be3a695d0 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/StringStorage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/StringStorage.java @@ -11,7 +11,9 @@ import org.enso.table.data.column.operation.map.text.StringBooleanOp; import org.enso.table.data.column.operation.map.text.StringIsInOp; import org.enso.table.data.column.operation.map.text.StringStringOp; +import org.enso.table.data.column.storage.type.StorageType; import org.enso.table.data.column.storage.type.TextType; +import org.enso.table.problems.WithAggregatedProblems; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; @@ -47,12 +49,12 @@ public TextType getType() { } @Override - public Storage fillMissing(Value arg) { + public WithAggregatedProblems> fillMissing(Value arg, StorageType commonType) { if (arg.isString()) { TextType newType = TextType.maxType(type, TextType.preciseTypeForValue(arg.asString())); return fillMissingHelper(arg, new StringBuilder(size(), newType)); } else { - return super.fillMissing(arg); + return super.fillMissing(arg, commonType); } } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/AbstractLongStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/AbstractLongStorage.java index bf0788443629..356b839acea5 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/AbstractLongStorage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/AbstractLongStorage.java @@ -6,16 +6,23 @@ import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; import org.enso.table.data.column.operation.map.MapOperationStorage; import org.enso.table.data.column.operation.map.UnaryMapOperation; -import org.enso.table.data.column.operation.map.numeric.LongBooleanOp; -import org.enso.table.data.column.operation.map.numeric.LongComparison; -import org.enso.table.data.column.operation.map.numeric.LongIsInOp; -import org.enso.table.data.column.operation.map.numeric.LongNumericOp; import org.enso.table.data.column.operation.map.numeric.LongRoundOp; import org.enso.table.data.column.operation.map.numeric.UnaryLongToLongOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.AddOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.DivideOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.ModOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.MulOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.PowerOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.SubOp; +import org.enso.table.data.column.operation.map.numeric.comparisons.EqualsComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.GreaterComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.GreaterOrEqualComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.LessComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.LessOrEqualComparison; +import org.enso.table.data.column.operation.map.numeric.isin.LongIsInOp; import org.enso.table.data.column.storage.BoolStorage; import org.enso.table.data.column.storage.Storage; import org.enso.table.data.column.storage.type.IntegerType; -import org.graalvm.polyglot.Context; public abstract class AbstractLongStorage extends NumericStorage { public abstract long getItem(int idx); @@ -72,100 +79,12 @@ public Builder createDefaultBuilderOfSameType(int capacity) { private static MapOperationStorage buildOps() { MapOperationStorage ops = new MapOperationStorage<>(); - ops.add( - new LongNumericOp(Storage.Maps.ADD) { - @Override - public double doDouble( - long in, double arg, int ix, MapOperationProblemBuilder problemBuilder) { - return in + arg; - } - - @Override - public Long doLong( - long in, long arg, int ix, MapOperationProblemBuilder problemBuilder) { - try { - return Math.addExact(in, arg); - } catch (ArithmeticException e) { - problemBuilder.reportOverflow(IntegerType.INT_64, in, "+", arg); - return null; - } - } - }) - .add( - new LongNumericOp(Storage.Maps.SUB) { - @Override - public double doDouble( - long in, double arg, int ix, MapOperationProblemBuilder problemBuilder) { - return in - arg; - } - - @Override - public Long doLong( - long in, long arg, int ix, MapOperationProblemBuilder problemBuilder) { - try { - return Math.subtractExact(in, arg); - } catch (ArithmeticException e) { - problemBuilder.reportOverflow(IntegerType.INT_64, in, "-", arg); - return null; - } - } - }) - .add( - new LongNumericOp(Storage.Maps.MUL) { - @Override - public double doDouble( - long in, double arg, int ix, MapOperationProblemBuilder problemBuilder) { - return in * arg; - } - - @Override - public Long doLong( - long in, long arg, int ix, MapOperationProblemBuilder problemBuilder) { - try { - return Math.multiplyExact(in, arg); - } catch (ArithmeticException e) { - problemBuilder.reportOverflow(IntegerType.INT_64, in, "*", arg); - return null; - } - } - }) - .add( - new LongNumericOp(Storage.Maps.MOD) { - @Override - public double doDouble( - long in, double arg, int ix, MapOperationProblemBuilder problemBuilder) { - if (arg == 0.0) { - problemBuilder.reportDivisionByZero(ix); - } - return in % arg; - } - - @Override - public Long doLong( - long in, long arg, int ix, MapOperationProblemBuilder problemBuilder) { - if (arg == 0) { - problemBuilder.reportDivisionByZero(ix); - return null; - } - - return in % arg; - } - }) - .add( - new LongNumericOp(Storage.Maps.POWER, true) { - @Override - public double doDouble( - long in, double arg, int ix, MapOperationProblemBuilder problemBuilder) { - return Math.pow(in, arg); - } - - @Override - public Long doLong( - long in, long arg, int ix, MapOperationProblemBuilder problemBuilder) { - throw new IllegalStateException( - "Internal error: Power operation should cast to double."); - } - }) + ops.add(new AddOp<>()) + .add(new SubOp<>()) + .add(new MulOp<>()) + .add(new DivideOp<>()) + .add(new ModOp<>()) + .add(new PowerOp<>()) .add( new UnaryLongToLongOp(Maps.TRUNCATE) { @Override @@ -188,128 +107,11 @@ protected long doOperation(long a) { } }) .add(new LongRoundOp(Maps.ROUND)) - .add( - new LongNumericOp(Storage.Maps.DIV, true) { - @Override - public double doDouble( - long in, double arg, int ix, MapOperationProblemBuilder problemBuilder) { - if (arg == 0.0) { - problemBuilder.reportDivisionByZero(ix); - } - return in / arg; - } - - @Override - public Long doLong( - long in, long arg, int ix, MapOperationProblemBuilder problemBuilder) { - throw new UnsupportedOperationException("Divide operation should cast to double."); - } - }) - .add( - new LongComparison(Storage.Maps.GT) { - @Override - protected boolean doLong(long a, long b) { - return a > b; - } - - @Override - protected boolean doDouble(long a, double b) { - return a > b; - } - }) - .add( - new LongComparison(Storage.Maps.GTE) { - @Override - protected boolean doLong(long a, long b) { - return a >= b; - } - - @Override - protected boolean doDouble(long a, double b) { - return a >= b; - } - }) - .add( - new LongComparison(Storage.Maps.LT) { - @Override - protected boolean doLong(long a, long b) { - return a < b; - } - - @Override - protected boolean doDouble(long a, double b) { - return a < b; - } - }) - .add( - new LongComparison(Storage.Maps.LTE) { - @Override - protected boolean doLong(long a, long b) { - return a <= b; - } - - @Override - protected boolean doDouble(long a, double b) { - return a <= b; - } - }) - .add( - new LongBooleanOp(Storage.Maps.EQ) { - @Override - public BoolStorage runBinaryMap( - AbstractLongStorage storage, - Object arg, - MapOperationProblemBuilder problemBuilder) { - if (arg instanceof Double) { - problemBuilder.reportFloatingPointEquality(-1); - } - return super.runBinaryMap(storage, arg, problemBuilder); - } - - @Override - public BoolStorage runZip( - AbstractLongStorage storage, - Storage arg, - MapOperationProblemBuilder problemBuilder) { - if (arg instanceof DoubleStorage) { - problemBuilder.reportFloatingPointEquality(-1); - } else if (!(arg instanceof AbstractLongStorage)) { - boolean hasFloats = false; - Context context = Context.getCurrent(); - for (int i = 0; i < storage.size(); i++) { - if (arg.isNa(i)) { - continue; - } - - if (arg.getItemBoxed(i) instanceof Double) { - hasFloats = true; - break; - } - - context.safepoint(); - } - if (hasFloats) { - problemBuilder.reportFloatingPointEquality(-1); - } - } - return super.runZip(storage, arg, problemBuilder); - } - - @Override - protected boolean doLong(long a, long b) { - return a == b; - } - - @Override - protected boolean doDouble(long a, double b) { - return a == b; - } - - @Override - protected boolean doObject(long x, Object o) { - return false; - } - }) + .add(new LessComparison<>()) + .add(new LessOrEqualComparison<>()) + .add(new EqualsComparison<>()) + .add(new GreaterOrEqualComparison<>()) + .add(new GreaterComparison<>()) .add( new UnaryMapOperation<>(Storage.Maps.IS_NOTHING) { @Override diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/BigIntegerStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/BigIntegerStorage.java new file mode 100644 index 000000000000..ad7e10ad0a59 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/BigIntegerStorage.java @@ -0,0 +1,99 @@ +package org.enso.table.data.column.storage.numeric; + +import java.math.BigDecimal; +import java.math.BigInteger; +import org.enso.table.data.column.builder.BigIntegerBuilder; +import org.enso.table.data.column.builder.Builder; +import org.enso.table.data.column.operation.map.MapOperationStorage; +import org.enso.table.data.column.operation.map.numeric.arithmetic.AddOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.DivideOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.ModOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.MulOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.PowerOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.SubOp; +import org.enso.table.data.column.operation.map.numeric.comparisons.EqualsComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.GreaterComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.GreaterOrEqualComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.LessComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.LessOrEqualComparison; +import org.enso.table.data.column.operation.map.numeric.isin.BigIntegerIsInOp; +import org.enso.table.data.column.storage.ObjectStorage; +import org.enso.table.data.column.storage.SpecializedStorage; +import org.enso.table.data.column.storage.type.BigIntegerType; +import org.enso.table.data.column.storage.type.StorageType; + +public class BigIntegerStorage extends SpecializedStorage { + /** + * @param data the underlying data + * @param size the number of items stored + */ + public BigIntegerStorage(BigInteger[] data, int size) { + super(data, size, makeOps()); + } + + protected static MapOperationStorage> makeOps() { + MapOperationStorage> ops = + ObjectStorage.buildObjectOps(); + return ops.add(new AddOp<>()) + .add(new SubOp<>()) + .add(new MulOp<>()) + .add(new DivideOp<>()) + .add(new ModOp<>()) + .add(new PowerOp<>()) + .add(new LessComparison<>()) + .add(new LessOrEqualComparison<>()) + .add(new EqualsComparison<>()) + .add(new GreaterOrEqualComparison<>()) + .add(new GreaterComparison<>()) + .add(new BigIntegerIsInOp<>()); + } + + public static BigIntegerStorage makeEmpty(int size) { + return new BigIntegerStorage(new BigInteger[size], size); + } + + @Override + protected SpecializedStorage newInstance(BigInteger[] data, int size) { + return new BigIntegerStorage(data, size); + } + + @Override + protected BigInteger[] newUnderlyingArray(int size) { + return new BigInteger[0]; + } + + @Override + public StorageType getType() { + return BigIntegerType.INSTANCE; + } + + @Override + public Builder createDefaultBuilderOfSameType(int capacity) { + return new BigIntegerBuilder(capacity); + } + + private long cachedMaxPrecisionStored = -1; + + public long getMaxPrecisionStored() { + if (cachedMaxPrecisionStored < 0) { + long maxPrecision = 0; + for (int i = 0; i < size; i++) { + BigInteger value = data[i]; + if (value == null) { + continue; + } + + BigDecimal asDecimal = new BigDecimal(value); + assert asDecimal.scale() == 0; + int precision = asDecimal.precision(); + if (precision > maxPrecision) { + maxPrecision = precision; + } + } + + cachedMaxPrecisionStored = maxPrecision; + } + + return cachedMaxPrecisionStored; + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/DoubleStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/DoubleStorage.java index 144388287364..564845a8413e 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/DoubleStorage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/DoubleStorage.java @@ -1,5 +1,6 @@ package org.enso.table.data.column.storage.numeric; +import java.math.BigInteger; import java.util.BitSet; import java.util.List; import org.enso.table.data.column.builder.Builder; @@ -7,12 +8,21 @@ import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; import org.enso.table.data.column.operation.map.MapOperationStorage; import org.enso.table.data.column.operation.map.UnaryMapOperation; -import org.enso.table.data.column.operation.map.numeric.DoubleBooleanOp; -import org.enso.table.data.column.operation.map.numeric.DoubleComparison; -import org.enso.table.data.column.operation.map.numeric.DoubleIsInOp; import org.enso.table.data.column.operation.map.numeric.DoubleLongMapOpWithSpecialNumericHandling; -import org.enso.table.data.column.operation.map.numeric.DoubleNumericOp; import org.enso.table.data.column.operation.map.numeric.DoubleRoundOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.AddOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.DivideOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.ModOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.MulOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.PowerOp; +import org.enso.table.data.column.operation.map.numeric.arithmetic.SubOp; +import org.enso.table.data.column.operation.map.numeric.comparisons.EqualsComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.GreaterComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.GreaterOrEqualComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.LessComparison; +import org.enso.table.data.column.operation.map.numeric.comparisons.LessOrEqualComparison; +import org.enso.table.data.column.operation.map.numeric.helpers.DoubleArrayAdapter; +import org.enso.table.data.column.operation.map.numeric.isin.DoubleIsInOp; import org.enso.table.data.column.storage.BoolStorage; import org.enso.table.data.column.storage.Storage; import org.enso.table.data.column.storage.type.FloatType; @@ -20,11 +30,12 @@ import org.enso.table.data.index.Index; import org.enso.table.data.mask.OrderMask; import org.enso.table.data.mask.SliceRange; +import org.enso.table.problems.WithAggregatedProblems; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; /** A column containing floating point numbers. */ -public final class DoubleStorage extends NumericStorage { +public final class DoubleStorage extends NumericStorage implements DoubleArrayAdapter { private final long[] data; private final BitSet isMissing; private final int size; @@ -95,6 +106,16 @@ public boolean isNa(long idx) { return isMissing.get((int) idx); } + @Override + public double getItemAsDouble(int i) { + return Double.longBitsToDouble(data[i]); + } + + @Override + public boolean isNa(int i) { + return isMissing.get(i); + } + @Override public boolean isBinaryOpVectorized(String op) { return ops.isSupportedBinary(op); @@ -123,7 +144,7 @@ public Storage runVectorizedZip( return ops.runZip(name, this, argument, problemBuilder); } - private Storage fillMissingDouble(double arg) { + private WithAggregatedProblems> fillMissingDouble(double arg) { final var builder = NumericBuilder.createDoubleBuilder(size()); long rawArg = Double.doubleToRawLongBits(arg); Context context = Context.getCurrent(); @@ -136,20 +157,54 @@ private Storage fillMissingDouble(double arg) { context.safepoint(); } - return builder.seal(); + return builder.sealWithProblems(); + } + + /** Special handling to ensure loss of precision is reported. */ + private WithAggregatedProblems> fillMissingBigInteger(BigInteger arg) { + final var builder = NumericBuilder.createDoubleBuilder(size()); + Context context = Context.getCurrent(); + for (int i = 0; i < size(); i++) { + if (isMissing.get(i)) { + builder.appendBigInteger(arg); + } else { + builder.appendRawNoGrow(data[i]); + } + + context.safepoint(); + } + return builder.sealWithProblems(); + } + + /** Special handling to ensure loss of precision is reported. */ + private WithAggregatedProblems> fillMissingLong(long arg) { + final var builder = NumericBuilder.createDoubleBuilder(size()); + Context context = Context.getCurrent(); + for (int i = 0; i < size(); i++) { + if (isMissing.get(i)) { + builder.appendLong(arg); + } else { + builder.appendRawNoGrow(data[i]); + } + + context.safepoint(); + } + return builder.sealWithProblems(); } @Override - public Storage fillMissing(Value arg) { + public WithAggregatedProblems> fillMissing(Value arg, StorageType commonType) { if (arg.isNumber()) { if (arg.fitsInLong()) { - return fillMissingDouble(arg.asLong()); + return fillMissingLong(arg.asLong()); + } else if (arg.fitsInBigInteger()) { + return fillMissingBigInteger(arg.asBigInteger()); } else if (arg.fitsInDouble()) { return fillMissingDouble(arg.asDouble()); } } - return super.fillMissing(arg); + return super.fillMissing(arg, commonType); } @Override @@ -221,60 +276,12 @@ public long[] getRawData() { private static MapOperationStorage buildOps() { MapOperationStorage ops = new MapOperationStorage<>(); - ops.add( - new DoubleNumericOp(Maps.ADD) { - @Override - protected double doDouble( - double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { - return a + b; - } - }) - .add( - new DoubleNumericOp(Maps.SUB) { - @Override - protected double doDouble( - double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { - return a - b; - } - }) - .add( - new DoubleNumericOp(Maps.MUL) { - @Override - protected double doDouble( - double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { - return a * b; - } - }) - .add( - new DoubleNumericOp(Maps.DIV) { - @Override - protected double doDouble( - double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { - if (b == 0.0) { - problemBuilder.reportDivisionByZero(ix); - } - return a / b; - } - }) - .add( - new DoubleNumericOp(Maps.MOD) { - @Override - protected double doDouble( - double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { - if (b == 0.0) { - problemBuilder.reportDivisionByZero(ix); - } - return a % b; - } - }) - .add( - new DoubleNumericOp(Maps.POWER) { - @Override - protected double doDouble( - double a, double b, int ix, MapOperationProblemBuilder problemBuilder) { - return Math.pow(a, b); - } - }) + ops.add(new AddOp<>()) + .add(new SubOp<>()) + .add(new MulOp<>()) + .add(new DivideOp<>()) + .add(new ModOp<>()) + .add(new PowerOp<>()) .add( new DoubleLongMapOpWithSpecialNumericHandling(Maps.TRUNCATE) { @Override @@ -297,66 +304,11 @@ protected long doOperation(double a) { } }) .add(new DoubleRoundOp(Maps.ROUND)) - .add( - new DoubleComparison(Maps.LT) { - @Override - protected boolean doDouble(double a, double b) { - return a < b; - } - }) - .add( - new DoubleComparison(Maps.LTE) { - @Override - protected boolean doDouble(double a, double b) { - return a <= b; - } - }) - .add( - new DoubleBooleanOp(Maps.EQ) { - @Override - public BoolStorage runBinaryMap( - DoubleStorage storage, Object arg, MapOperationProblemBuilder problemBuilder) { - if (arg != null) { - problemBuilder.reportFloatingPointEquality(-1); - } - return super.runBinaryMap(storage, arg, problemBuilder); - } - - @Override - public BoolStorage runZip( - DoubleStorage storage, - Storage arg, - MapOperationProblemBuilder problemBuilder) { - if (arg.countMissing() < arg.size()) { - problemBuilder.reportFloatingPointEquality(-1); - } - return super.runZip(storage, arg, problemBuilder); - } - - @Override - protected boolean doDouble(double a, double b) { - return a == b; - } - - @Override - protected boolean doObject(double a, Object o) { - return false; - } - }) - .add( - new DoubleComparison(Maps.GT) { - @Override - protected boolean doDouble(double a, double b) { - return a > b; - } - }) - .add( - new DoubleComparison(Maps.GTE) { - @Override - protected boolean doDouble(double a, double b) { - return a >= b; - } - }) + .add(new LessComparison<>()) + .add(new LessOrEqualComparison<>()) + .add(new EqualsComparison<>()) + .add(new GreaterOrEqualComparison<>()) + .add(new GreaterComparison<>()) .add( new UnaryMapOperation<>(Maps.IS_NOTHING) { @Override @@ -373,7 +325,7 @@ public BoolStorage runUnaryMap( BitSet nans = new BitSet(); Context context = Context.getCurrent(); for (int i = 0; i < storage.size; i++) { - if (!storage.isNa(i) && Double.isNaN(storage.getItem(i))) { + if (!storage.isNa(i) && Double.isNaN(storage.getItemAsDouble(i))) { nans.set(i); } @@ -390,7 +342,7 @@ public BoolStorage runUnaryMap( BitSet infintes = new BitSet(); Context context = Context.getCurrent(); for (int i = 0; i < storage.size; i++) { - if (!storage.isNa(i) && Double.isInfinite(storage.getItem(i))) { + if (!storage.isNa(i) && Double.isInfinite(storage.getItemAsDouble(i))) { infintes.set(i); } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/LongStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/LongStorage.java index daf5a18ac397..e8feb71a0ea3 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/LongStorage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/numeric/LongStorage.java @@ -1,14 +1,18 @@ package org.enso.table.data.column.storage.numeric; +import java.math.BigInteger; import java.util.BitSet; import java.util.List; import org.enso.base.polyglot.NumericConverter; +import org.enso.table.data.column.builder.BigIntegerBuilder; import org.enso.table.data.column.builder.NumericBuilder; import org.enso.table.data.column.storage.Storage; import org.enso.table.data.column.storage.type.IntegerType; +import org.enso.table.data.column.storage.type.StorageType; import org.enso.table.data.index.Index; import org.enso.table.data.mask.OrderMask; import org.enso.table.data.mask.SliceRange; +import org.enso.table.problems.WithAggregatedProblems; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; @@ -88,8 +92,8 @@ public boolean isNa(long idx) { return isMissing.get((int) idx); } - private Storage fillMissingDouble(double arg) { - final var builder = NumericBuilder.createDoubleBuilder(size()); + private WithAggregatedProblems> fillMissingDouble(double arg) { + final var builder = NumericBuilder.createDoubleBuilder(size); long rawArg = Double.doubleToRawLongBits(arg); Context context = Context.getCurrent(); for (int i = 0; i < size(); i++) { @@ -102,11 +106,12 @@ private Storage fillMissingDouble(double arg) { context.safepoint(); } - return builder.seal(); + + return builder.sealWithProblems(); } - private Storage fillMissingLong(long arg) { - final var builder = NumericBuilder.createLongBuilder(size(), IntegerType.INT_64); + private WithAggregatedProblems> fillMissingLong(long arg) { + final var builder = NumericBuilder.createLongBuilder(size, IntegerType.INT_64); Context context = Context.getCurrent(); for (int i = 0; i < size(); i++) { if (isMissing.get(i)) { @@ -117,20 +122,39 @@ private Storage fillMissingLong(long arg) { context.safepoint(); } - return builder.seal(); + + return builder.sealWithProblems(); + } + + private WithAggregatedProblems> fillMissingBigInteger(BigInteger bigInteger) { + final var builder = new BigIntegerBuilder(size); + Context context = Context.getCurrent(); + for (int i = 0; i < size(); i++) { + if (isMissing.get(i)) { + builder.appendRawNoGrow(bigInteger); + } else { + builder.appendRawNoGrow(BigInteger.valueOf(data[i])); + } + + context.safepoint(); + } + + return builder.sealWithProblems(); } @Override - public Storage fillMissing(Value arg) { + public WithAggregatedProblems> fillMissing(Value arg, StorageType commonType) { if (arg.isNumber()) { if (NumericConverter.isCoercibleToLong(arg.as(Object.class))) { return fillMissingLong(arg.asLong()); + } else if (NumericConverter.isBigInteger(arg)) { + return fillMissingBigInteger(arg.asBigInteger()); } else { return fillMissingDouble(arg.asDouble()); } } - return super.fillMissing(arg); + return super.fillMissing(arg, commonType); } @Override diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/type/BigIntegerType.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/type/BigIntegerType.java new file mode 100644 index 000000000000..f0d89d9ff40b --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/type/BigIntegerType.java @@ -0,0 +1,5 @@ +package org.enso.table.data.column.storage.type; + +public record BigIntegerType() implements StorageType { + public static final BigIntegerType INSTANCE = new BigIntegerType(); +} diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/type/IntegerType.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/type/IntegerType.java index 591540a1ad6f..0d92766ad608 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/type/IntegerType.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/type/IntegerType.java @@ -1,5 +1,7 @@ package org.enso.table.data.column.storage.type; +import java.math.BigInteger; + public record IntegerType(Bits bits) implements StorageType { public static final IntegerType INT_64 = new IntegerType(Bits.BITS_64); public static final IntegerType INT_32 = new IntegerType(Bits.BITS_32); @@ -44,6 +46,14 @@ public boolean fits(double value) { return value >= min && value <= max; } + public boolean fits(BigInteger value) { + if (value.bitLength() > 63) { + return false; + } else { + return fits(value.longValue()); + } + } + /** * Checks if this type can hold values of otherType - i.e. if otherType has the same or smaller number of bits. */ diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/type/StorageType.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/type/StorageType.java index 98a8d7097c5b..d8377ea21810 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/type/StorageType.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/type/StorageType.java @@ -2,6 +2,7 @@ import org.enso.base.polyglot.NumericConverter; +import java.math.BigInteger; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -10,7 +11,7 @@ /** * Represents an underlying internal storage type that can be mapped to the Value Type that is exposed to users. */ -public sealed interface StorageType permits AnyObjectType, BooleanType, DateType, DateTimeType, FloatType, IntegerType, TextType, TimeOfDayType { +public sealed interface StorageType permits AnyObjectType, BigIntegerType, BooleanType, DateTimeType, DateType, FloatType, IntegerType, TextType, TimeOfDayType { /** * @return the StorageType that represents a given boxed item. */ @@ -24,12 +25,13 @@ static StorageType forBoxedItem(Object item) { } return switch (item) { + case String s -> TextType.VARIABLE_LENGTH; + case BigInteger i -> BigIntegerType.INSTANCE; case Boolean b -> BooleanType.INSTANCE; case LocalDate d -> DateType.INSTANCE; case LocalTime t -> TimeOfDayType.INSTANCE; case LocalDateTime d -> DateTimeType.INSTANCE; case ZonedDateTime d -> DateTimeType.INSTANCE; - case String s -> TextType.VARIABLE_LENGTH; default -> null; }; } diff --git a/std-bits/table/src/main/java/org/enso/table/data/table/problems/UnsupportedFeature.java b/std-bits/table/src/main/java/org/enso/table/data/table/problems/UnsupportedFeature.java new file mode 100644 index 000000000000..e19a33dc0e1e --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/table/problems/UnsupportedFeature.java @@ -0,0 +1,20 @@ +package org.enso.table.data.table.problems; + +import org.enso.table.problems.Problem; + +public class UnsupportedFeature implements Problem { + private final String message; + + public UnsupportedFeature(String message) { + this.message = message; + } + + @Override + public String toString() { + return message; + } + + public String getMessage() { + return message; + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/write/ExcelWriter.java b/std-bits/table/src/main/java/org/enso/table/write/ExcelWriter.java index d4943e8d606b..45e0620188a1 100644 --- a/std-bits/table/src/main/java/org/enso/table/write/ExcelWriter.java +++ b/std-bits/table/src/main/java/org/enso/table/write/ExcelWriter.java @@ -314,7 +314,7 @@ private static void writeValueToCell(Cell cell, int j, Storage storage, Workb if (storage.isNa(j)) { cell.setBlank(); } else if (storage instanceof DoubleStorage doubleStorage) { - cell.setCellValue(doubleStorage.getItem(j)); + cell.setCellValue(doubleStorage.getItemAsDouble(j)); } else if (storage instanceof AbstractLongStorage longStorage) { cell.setCellValue(longStorage.getItem(j)); } else if (storage instanceof BoolStorage boolStorage) { diff --git a/test/Exploratory_Benchmarks/polyglot-sources/exploratory-benchmark-java-helpers/src/main/java/org/enso/exploratory_benchmark_helpers/LongNullHandling.java b/test/Exploratory_Benchmarks/polyglot-sources/exploratory-benchmark-java-helpers/src/main/java/org/enso/exploratory_benchmark_helpers/LongNullHandling.java new file mode 100644 index 000000000000..18b913982743 --- /dev/null +++ b/test/Exploratory_Benchmarks/polyglot-sources/exploratory-benchmark-java-helpers/src/main/java/org/enso/exploratory_benchmark_helpers/LongNullHandling.java @@ -0,0 +1,162 @@ +package org.enso.exploratory_benchmark_helpers; + +import java.util.BitSet; +import org.enso.table.data.column.operation.map.MapOperationProblemBuilder; +import org.enso.table.data.column.storage.numeric.LongStorage; +import org.enso.table.data.column.storage.type.IntegerType; + +public class LongNullHandling { + public interface Operation { + LongStorage run( + LongStorage storage, LongStorage arg, MapOperationProblemBuilder problemBuilder); + } + + public abstract static class NoNulls implements Operation { + + protected abstract long doLong( + long a, long b, int ix, MapOperationProblemBuilder problemBuilder); + + @Override + public LongStorage run( + LongStorage storage, LongStorage arg, MapOperationProblemBuilder problemBuilder) { + int n = storage.size(); + long[] newVals = new long[n]; + BitSet missing = new BitSet(); + for (int i = 0; i < n; i++) { + if (storage.isNa(i) || arg.isNa(i)) { + missing.set(i); + } else { + newVals[i] = doLong(storage.getItem(i), arg.getItem(i), i, problemBuilder); + } + } + return new LongStorage(newVals, n, missing, IntegerType.INT_64); + } + } + + public abstract static class BoxingNulls implements Operation { + + protected abstract Long doLong( + long a, long b, int ix, MapOperationProblemBuilder problemBuilder); + + @Override + public LongStorage run( + LongStorage storage, LongStorage arg, MapOperationProblemBuilder problemBuilder) { + int n = storage.size(); + long[] newVals = new long[n]; + BitSet missing = new BitSet(); + for (int i = 0; i < n; i++) { + if (storage.isNa(i) || arg.isNa(i)) { + missing.set(i); + } else { + Long x = doLong(storage.getItem(i), arg.getItem(i), i, problemBuilder); + if (x == null) { + missing.set(i); + } else { + newVals[i] = x; + } + } + } + return new LongStorage(newVals, n, missing, IntegerType.INT_64); + } + } + + public abstract static class ReportingNulls implements Operation { + static class NullityReporter { + private boolean wasLastNull = false; + + void willBeNull() { + wasLastNull = true; + } + } + + protected abstract long doLong( + long a, + long b, + int ix, + MapOperationProblemBuilder problemBuilder, + NullityReporter nullityReporter); + + @Override + public LongStorage run( + LongStorage storage, LongStorage arg, MapOperationProblemBuilder problemBuilder) { + int n = storage.size(); + long[] newVals = new long[n]; + BitSet missing = new BitSet(); + NullityReporter nullityReporter = new NullityReporter(); + for (int i = 0; i < n; i++) { + if (storage.isNa(i) || arg.isNa(i)) { + missing.set(i); + } else { + long x = doLong(storage.getItem(i), arg.getItem(i), i, problemBuilder, nullityReporter); + if (nullityReporter.wasLastNull) { + missing.set(i); + nullityReporter.wasLastNull = false; + } else { + newVals[i] = x; + } + } + } + return new LongStorage(newVals, n, missing, IntegerType.INT_64); + } + } + + public static LongStorage runNoNulls(LongStorage arg1, LongStorage arg2) { + MapOperationProblemBuilder problemBuilder = new MapOperationProblemBuilder(null); + NoNulls operation = + new NoNulls() { + @Override + protected long doLong(long a, long b, int ix, MapOperationProblemBuilder problemBuilder) { + if (b == 0) { + problemBuilder.reportDivisionByZero(ix); + return 0; + } else { + return a / b; + } + } + }; + + return operation.run(arg1, arg2, problemBuilder); + } + + public static LongStorage runBoxingNulls(LongStorage arg1, LongStorage arg2) { + MapOperationProblemBuilder problemBuilder = new MapOperationProblemBuilder(null); + BoxingNulls operation = + new BoxingNulls() { + @Override + protected Long doLong(long a, long b, int ix, MapOperationProblemBuilder problemBuilder) { + if (b == 0) { + problemBuilder.reportDivisionByZero(ix); + return null; + } else { + return a / b; + } + } + }; + + return operation.run(arg1, arg2, problemBuilder); + } + + public static LongStorage runReportingNulls(LongStorage arg1, LongStorage arg2) { + MapOperationProblemBuilder problemBuilder = new MapOperationProblemBuilder(null); + ReportingNulls operation = + new ReportingNulls() { + @Override + protected long doLong( + long a, + long b, + int ix, + MapOperationProblemBuilder problemBuilder, + NullityReporter nullityReporter) { + if (b == 0) { + problemBuilder.reportDivisionByZero(ix); + nullityReporter.willBeNull(); + return 0; + } else { + return a / b; + } + } + }; + + return operation.run(arg1, arg2, problemBuilder); + } +} diff --git a/test/Exploratory_Benchmarks/src/Table/Handling_Null_Return.enso b/test/Exploratory_Benchmarks/src/Table/Handling_Null_Return.enso new file mode 100644 index 000000000000..76fda21aa5e8 --- /dev/null +++ b/test/Exploratory_Benchmarks/src/Table/Handling_Null_Return.enso @@ -0,0 +1,46 @@ +from Standard.Base import all +from Standard.Table import Table +from Standard.Test import Bench + +polyglot java import org.enso.exploratory_benchmark_helpers.LongNullHandling + +options = Bench.options . set_warmup (Bench.phase_conf 2 3) . set_measure (Bench.phase_conf 2 3) + +type Data + Value ~table + + create num_rows = + table = + a = Vector.new num_rows i-> i % 10000 + b = Vector.new num_rows i-> i % 10 + + # To avoid division by zero + c = Vector.new num_rows i-> 1 + (i % 100) + Table.new [["A", a], ["B", b], ["C", c]] + + Data.Value table + + +collect_benches = Bench.build builder-> + num_rows = 1000000 + data = Data.create num_rows + + builder.group ("Handling_Null_Return_" + num_rows.to_text) options group_builder-> + get_storage name = + data.table.at name . java_column . getStorage + + group_builder.specify "NoNulls_10percent" <| + LongNullHandling.runNoNulls (get_storage "a") (get_storage "b") + group_builder.specify "BoxingNulls_10percent" <| + LongNullHandling.runBoxingNulls (get_storage "a") (get_storage "b") + group_builder.specify "ReportingNulls_10percent" <| + LongNullHandling.runReportingNulls (get_storage "a") (get_storage "b") + + group_builder.specify "NoNulls_none" <| + LongNullHandling.runNoNulls (get_storage "a") (get_storage "c") + group_builder.specify "BoxingNulls_none" <| + LongNullHandling.runBoxingNulls (get_storage "a") (get_storage "c") + group_builder.specify "ReportingNulls_none" <| + LongNullHandling.runReportingNulls (get_storage "a") (get_storage "c") + +main = collect_benches . run_main diff --git a/test/Table_Tests/src/Common_Table_Operations/Conversion_Spec.enso b/test/Table_Tests/src/Common_Table_Operations/Conversion_Spec.enso index 018033c319a9..7f024f35897c 100644 --- a/test/Table_Tests/src/Common_Table_Operations/Conversion_Spec.enso +++ b/test/Table_Tests/src/Common_Table_Operations/Conversion_Spec.enso @@ -67,6 +67,13 @@ spec setup = c.value_type.is_text . should_be_true c.to_vector . should_equal ["{{{MY Type [x=42] }}}", "{{{MY Type [x=X] }}}"] + # TODO what to test here? + Test.specify "should allow to cast an integer column to a decimal type" <| + t = table_builder [["X", [1, 2, 3]]] + c = t.at "X" . cast Value_Type.Decimal + c.value_type.is_decimal . should_be_true + c.to_vector . should_equal [1, 2, 3] + if setup.test_selection.fixed_length_text_columns then Test.specify "should allow to cast a text column to fixed-length" <| t = table_builder [["X", ["a", "DEF", "a slightly longer text"]]] @@ -447,9 +454,6 @@ spec setup = t = table_builder [["X", [1, 2, 3]]] # currently unsupported - r1 = t.cast "X" Value_Type.Decimal - r1.should_fail_with Illegal_Argument - r2 = t.cast "X" Value_Type.Binary r2.should_fail_with Illegal_Argument diff --git a/test/Table_Tests/src/Database/Postgres_Spec.enso b/test/Table_Tests/src/Database/Postgres_Spec.enso index 4d120619db27..68ee72d90a00 100644 --- a/test/Table_Tests/src/Database/Postgres_Spec.enso +++ b/test/Table_Tests/src/Database/Postgres_Spec.enso @@ -6,7 +6,7 @@ import Standard.Base.Runtime.Ref.Ref import Standard.Table.Data.Type.Value_Type.Bits from Standard.Table import Table, Value_Type from Standard.Table.Data.Aggregate_Column.Aggregate_Column import all hiding First, Last -from Standard.Table.Errors import Invalid_Column_Names, Duplicate_Output_Column_Names +from Standard.Table.Errors import Invalid_Column_Names, Inexact_Type_Coercion, Duplicate_Output_Column_Names import Standard.Database.Data.Column.Column import Standard.Database.Data.SQL_Type.SQL_Type @@ -279,6 +279,66 @@ postgres_specific_spec connection db_name setup = Test.with_clue "d.value_type="+d.value_type.to_display_text+": " <| d.value_type.variable_length.should_be_true + Test.specify "should be able to round-trip a BigInteger column" <| + x = 2^70 + m1 = Table.new [["X", [10, x]]] + m1.at "X" . value_type . should_be_a (Value_Type.Decimal ...) + + t1 = m1.select_into_database_table connection (Name_Generator.random_name "BigInteger") primary_key=[] temporary=True + t1.at "X" . value_type . should_be_a (Value_Type.Decimal ...) + t1.at "X" . value_type . scale . should_equal 0 + # If we want to enforce the scale, Postgres requires us to enforce a precision too, so we use the biggest one we can: + t1.at "X" . value_type . precision . should_equal 1000 + w1 = Problems.expect_only_warning Inexact_Type_Coercion t1 + w1.requested_type . should_equal (Value_Type.Decimal precision=Nothing scale=0) + w1.actual_type . should_equal (Value_Type.Decimal precision=1000 scale=0) + + v1x = t1.at "X" . to_vector + v1x.should_equal [10, x] + v1x.each e-> Test.with_clue "("+e.to_text+"): " <| e.should_be_a Integer + + t2 = t1.set "[X] + 10" "Y" + t2.at "X" . value_type . should_be_a (Value_Type.Decimal ...) + t2.at "Y" . value_type . should_be_a (Value_Type.Decimal ...) + # Unfortunately, performing operations on a Decimal column in postgres can lose information about it being an integer column. + t2.at "Y" . value_type . scale . should_equal Nothing + t2.at "X" . to_vector . should_equal [10, x] + t2.at "Y" . to_vector . should_equal [20, x+10] + + m2 = t2.remove_warnings.read + m2.at "X" . value_type . should_be_a (Value_Type.Decimal ...) + # As noted above - once operations are performed, the scale=0 may be lost and the column will be approximated as a float. + m2.at "Y" . value_type . should_equal Value_Type.Float + m2.at "X" . to_vector . should_equal [10, x] + m2.at "Y" . to_vector . should_equal [20, x+10] + w2 = Problems.expect_only_warning Inexact_Type_Coercion m2 + w2.requested_type . should_equal (Value_Type.Decimal precision=Nothing scale=Nothing) + w2.actual_type . should_equal Value_Type.Float + + # This has more than 1000 digits. + super_large = 11^2000 + m3 = Table.new [["X", [super_large]]] + m3.at "X" . value_type . should_be_a (Value_Type.Decimal ...) + t3 = m3.select_into_database_table connection (Name_Generator.random_name "BigInteger2") primary_key=[] temporary=True + t3 . at "X" . value_type . should_be_a (Value_Type.Decimal ...) + # If we exceed the 1000 digits precision, we cannot enforce neither scale nor precision anymore. + t3 . at "X" . value_type . precision . should_equal Nothing + t3 . at "X" . value_type . scale . should_equal Nothing + # Works but only relying on imprecise float equality: + t3 . at "X" . to_vector . should_equal [super_large] + w3 = Problems.expect_only_warning Inexact_Type_Coercion t3 + w3.requested_type . should_equal (Value_Type.Decimal precision=Nothing scale=0) + w3.actual_type . should_equal (Value_Type.Decimal precision=Nothing scale=Nothing) + + m4 = t3.remove_warnings.read + # Because we no longer have a set scale, we cannot get a BigInteger column back - we'd need BigDecimal, but that is not fully supported yet in Enso - so we get the closest approximation - the imprecise Float. + m4 . at "X" . value_type . should_equal Value_Type.Float + m4 . at "X" . to_vector . should_equal [super_large] + w4 = Problems.expect_only_warning Inexact_Type_Coercion m4 + w4.requested_type . should_equal (Value_Type.Decimal precision=Nothing scale=Nothing) + w4.actual_type . should_equal Value_Type.Float + + Test.group "[PostgreSQL] math functions" <| Test.specify "round, trunc, ceil, floor" <| col = table_builder [["x", [0.1, 0.9, 3.1, 3.9, -0.1, -0.9, -3.1, -3.9]]] . at "x" diff --git a/test/Table_Tests/src/In_Memory/Column_Spec.enso b/test/Table_Tests/src/In_Memory/Column_Spec.enso index dc38def69f74..607cdabdf413 100644 --- a/test/Table_Tests/src/In_Memory/Column_Spec.enso +++ b/test/Table_Tests/src/In_Memory/Column_Spec.enso @@ -91,8 +91,7 @@ spec = c1 = Column.from_vector "" [1, 2, 3] c1.should_fail_with Invalid_Column_Names - c2 = Column.from_vector Nothing [1, 2, 3] - c2.should_fail_with Invalid_Column_Names + Test.expect_panic_with (Column.from_vector Nothing [1, 2, 3]) Type_Error c3 = Column.from_vector '\0' [1, 2, 3] c3.should_fail_with Invalid_Column_Names diff --git a/test/Table_Tests/src/In_Memory/Integer_Overflow_Spec.enso b/test/Table_Tests/src/In_Memory/Integer_Overflow_Spec.enso index 461ed1228bab..66bc81de4802 100644 --- a/test/Table_Tests/src/In_Memory/Integer_Overflow_Spec.enso +++ b/test/Table_Tests/src/In_Memory/Integer_Overflow_Spec.enso @@ -2,7 +2,7 @@ from Standard.Base import all import Standard.Table.Data.Type.Value_Type.Bits from Standard.Table import all -from Standard.Table.Errors import Arithmetic_Overflow, Conversion_Failure +from Standard.Table.Errors import Arithmetic_Overflow, Conversion_Failure, Invalid_Value_Type, No_Common_Type, Loss_Of_Integer_Precision from Standard.Test import Test, Test_Suite, Problems import Standard.Test.Extensions @@ -14,6 +14,8 @@ polyglot java import java.lang.Short as Java_Short polyglot java import java.lang.Integer as Java_Integer polyglot java import java.lang.Long as Java_Long +polyglot java import org.enso.table_test_helpers.PolyglotHelpers + main = Test_Suite.run_main spec spec = @@ -138,6 +140,28 @@ spec = test_no_overflow (Value_Type.Integer Bits.Bits_16) Java_Short.MAX_VALUE Java_Short.MIN_VALUE test_no_overflow (Value_Type.Integer Bits.Bits_32) Java_Integer.MAX_VALUE Java_Integer.MIN_VALUE + Test.specify "if we cast to Decimal first, then the operations will not overflow" <| + t0 = Table.new [["X", [0, 1, Java_Long.MAX_VALUE, 0]], ["U", [1, 1, 1, 1]]] + t1 = t0.cast "X" (Value_Type.Decimal scale=0) + x = t1.at "X" + u = t1.at "U" + + c1 = x + 1 + Problems.assume_no_problems c1 + c1.to_vector . should_equal [1, 2, Java_Long.MAX_VALUE+1, 1] + + c2 = x + u + Problems.assume_no_problems c2 + c2.to_vector . should_equal [1, 2, Java_Long.MAX_VALUE+1, 1] + + c3 = u - x - x + Problems.assume_no_problems c3 + c3.to_vector . should_equal [1, -1, 1 - 2*Java_Long.MAX_VALUE, 1] + + c4 = x * x + Problems.assume_no_problems c4 + c4.to_vector . should_equal [0, 1, Java_Long.MAX_VALUE*Java_Long.MAX_VALUE, 0] + Test.specify "mixed operations" <| t = Table.new [["X", [Java_Short.MAX_VALUE]], ["Y", [1]]] x = t.at "X" . cast (Value_Type.Integer Bits.Bits_16) @@ -170,3 +194,259 @@ spec = Problems.assume_no_problems c5 (x%2).value_type . should_equal (Value_Type.Integer Bits.Bits_64) + + Test.group "[In-Memory] Handling of Big Integer values" <| + Test.specify "will create a BigInteger column if some values do not fit in long" <| + c0 = Column.from_vector "X" [Java_Long.MAX_VALUE, 0, 1] + Problems.assume_no_problems c0 + c0.value_type . should_equal (Value_Type.Integer Bits.Bits_64) + + c1 = Column.from_vector "X" [Java_Long.MAX_VALUE, 2^70, 100] + Problems.assume_no_problems c1 + c1.value_type . should_be_a (Value_Type.Decimal ...) + c1.to_vector . should_equal [Java_Long.MAX_VALUE, 2^70, 100] + c1.to_vector.each e-> e.should_be_a Integer + c1.to_vector.map .to_text . should_equal [Java_Long.MAX_VALUE.to_text, (2^70).to_text, "100"] + + c2 = c1.cast (Value_Type.Integer Bits.Bits_64) + c2.to_vector . should_equal [Java_Long.MAX_VALUE, Nothing, 100] + Problems.expect_only_warning Conversion_Failure c2 + + t0 = Table.new [["X", [Java_Long.MAX_VALUE, 0, 1]]] + Problems.assume_no_problems t0 + t0.at "X" . value_type . should_equal (Value_Type.Integer Bits.Bits_64) + + t1 = Table.new [["X", [Java_Long.MAX_VALUE, 2^70, 100]]] + Problems.assume_no_problems t1 + t1.at "X" . value_type . should_be_a (Value_Type.Decimal ...) + + Test.specify "should fail if a big integer is provided for an Integer 64-bit column" <| + c1 = Column.from_vector "X" [Java_Long.MAX_VALUE, 2^70, 100] value_type=Value_Type.Integer + c1.should_fail_with Invalid_Value_Type + c1.catch.to_display_text . should_contain "Decimal" + + Test.specify "allows to construct a column from big integers coming from Java" <| + big_integer_but_small = PolyglotHelpers.createSmallBigIntegerComingFromJava + t1 = Table.new [["X", [big_integer_but_small]]] + t1.at "X" . value_type . should_equal (Value_Type.Integer Bits.Bits_64) + t1.at "X" . to_vector . should_equal [big_integer_but_small] + + big_big_integer = PolyglotHelpers.createBigBigIntegerComingFromJava + t2 = Table.new [["X", [big_big_integer]]] + t2.at "X" . value_type . should_be_a (Value_Type.Decimal ...) + v2 = t2.at "X" . to_vector + v2.should_equal [big_big_integer] + v2.at 0 . should_be_a Integer + v2.at 0 . to_text . should_equal big_big_integer.to_text + + Test.specify "will create a Mixed column if other types are present" <| + c1 = Column.from_vector "X" [Java_Long.MAX_VALUE, 2^70, "abc"] + Problems.assume_no_problems c1 + c1.value_type . should_equal Value_Type.Mixed + + Test.specify "should allow to create a Float column from a big integer, but warn about Loss_Of_Integer_Precision if relevant" <| + # 2^70 is not exactly representable as a Float. + (2^70 + 0.0).truncate . should_not_equal (2^70) + + c1 = Column.from_vector "X" [1, 2^70, 1.5] + Problems.expect_only_warning Loss_Of_Integer_Precision c1 + c1.value_type . should_equal Value_Type.Float + + # A number around ~2^100 that _can_ be exactly represented as float. + x = (2^100 + 0.0).truncate + c2 = Column.from_vector "X" [x, 1.5] + # So there is no warning, as no real precision loss here. + Problems.assume_no_problems c2 + c2.value_type . should_equal Value_Type.Float + # This will be an inexact equality, based on float rounding. + c2.to_vector . should_equal [x, 1.5] + + c3 = Column.from_vector "X" [2^70, 1] + Problems.assume_no_problems c3 + c3.value_type . should_be_a (Value_Type.Decimal ...) + c4 = c3.remove_warnings.cast Value_Type.Float + c4.to_vector . should_equal [2^70, 1] + Problems.expect_only_warning Loss_Of_Integer_Precision c4 + + Test.specify "should use Decimal type if a mapping operation yields a numeric column with big integers" <| + c = Column.from_vector "X" [1, 2, 3] + + f1 x = if x == 2 then 2^70 else x + c1 = c.map f1 + c1.to_vector . should_equal [1, 2^70, 3] + Problems.assume_no_problems c1 + c1.value_type . should_be_a (Value_Type.Decimal ...) + + f2 x = case x of + 2 -> 2^100 + 3 -> "foobar" + _ -> x + c2 = c.map f2 + c2.to_vector . should_equal [1, 2^100, "foobar"] + # But no warning if the operation yields other types of values as well. + Problems.assume_no_problems c2 + c2.value_type . should_equal Value_Type.Mixed + + Test.specify "allows arithmetic on Decimal columns" <| + t = Table.new [["X", [10^30, 2^70, Nothing, 3]], ["Y", [10^20, 2, 3, 4]]] + x = t.at "X" + y = t.at "Y" + x.value_type . should_be_a (Value_Type.Decimal ...) + y.value_type . should_be_a (Value_Type.Decimal ...) + + r1 = x + y + r1.value_type . should_be_a (Value_Type.Decimal ...) + r1.to_vector . should_equal [10^30 + 10^20, 2^70 + 2, Nothing, 3 + 4] + + (x - y) . to_vector . should_equal [10^30 - 10^20, 2^70 - 2, Nothing, 3 - 4] + (x * y) . to_vector . should_equal [10^50, 2^71, Nothing, 3 * 4] + r2 = x % y + r2.value_type . should_be_a (Value_Type.Decimal ...) + r2.to_vector . should_equal [0, 0, Nothing, 3] + + r3 = x / y + r3.value_type . should_equal Value_Type.Float + r3.to_vector . should_equal [10^10, 2^69, Nothing, 3 / 4] + + r4 = x ^ y + r4.value_type . should_equal Value_Type.Float + r4.to_vector . should_equal [(10^30)^(10^20), (2^70)^2, Nothing, 3^4] + + (x.min y).to_vector . should_equal [10^20, 2, 3, 3] + (x.max y).to_vector . should_equal [10^30, 2^70, 3, 4] + + r5 = x.fill_nothing y + r5.value_type . should_be_a (Value_Type.Decimal ...) + r5.to_vector . should_equal [10^30, 2^70, 3, 3] + + x.is_nothing . to_vector . should_equal [False, False, True, False] + x.is_nan . to_vector . should_equal [False, False, Nothing, False] + x.is_infinite . to_vector . should_equal [False, False, Nothing, False] + x.is_in [3, 2^70] . to_vector . should_equal [False, True, False, True] + + Test.specify "allows arithmetic on Decimal columns and other numeric columns" <| + t = Table.new [["X", [10^30, 2^70, Nothing, 3]], ["Y", [1, 2, 3, 4]], ["Z", [1.5, 2.5, 3.5, 4.5]]] + x = t.at "X" + y = t.at "Y" + z = t.at "Z" + x.value_type . should_be_a (Value_Type.Decimal ...) + y.value_type . should_equal Value_Type.Integer + z.value_type . should_equal Value_Type.Float + + c1 = x + y + c1.value_type . should_be_a (Value_Type.Decimal ...) + c1.to_vector . should_equal [10^30 + 1, 2^70 + 2, Nothing, 3 + 4] + + c2 = x - z + c2.value_type . should_equal Value_Type.Float + c2.to_vector . should_equal [10^30 - 1.5, 2^70 - 2.5, Nothing, 3 - 4.5] + + (x * y) . to_vector . should_equal [10^30, 2^71, Nothing, 3 * 4] + (x % y) . to_vector . should_equal [0, 0, Nothing, 3] + (x % z) . to_vector . should_equal [1.0, 1.5, Nothing, 3.0] + (x / y) . to_vector . should_equal [10^30, 2^69, Nothing, 0.75] + (x ^ y) . to_vector . should_equal [10^30, 2^140, Nothing, 3^4] + (x == y) . to_vector . should_equal [False, False, Nothing, False] + (x < y) . to_vector . should_equal [False, False, Nothing, True] + (x >= y) . to_vector . should_equal [True, True, Nothing, False] + (x != z) . to_vector . should_equal [True, True, Nothing, True] + (x > z) . to_vector . should_equal [True, True, Nothing, False] + (x <= z) . to_vector . should_equal [False, False, Nothing, True] + (x.min y) . to_vector . should_equal [1, 2, 3, 3] + (x.max y) . to_vector . should_equal [10^30, 2^70, 3, 4] + (x.min z) . to_vector . should_equal [1.5, 2.5, 3.5, 3] + (x.max [y, z]) . to_vector . should_equal [10^30, 2^70, 3.5, 4.5] + + (y * x) . to_vector . should_equal [10^30, 2^71, Nothing, 3 * 4] + (y % x) . to_vector . should_equal [1, 2, Nothing, 1] + (z / x) . to_vector . should_equal [1.5 / 10^30, 2.5 / 2^70, Nothing, 4.5 / 3] + (z ^ x) . to_vector . should_equal [1.5^(10^30), 2.5^(2^70), Nothing, (4.5)^3] + (z == x) . to_vector . should_equal [False, False, Nothing, False] + (z < x) . to_vector . should_equal [True, True, Nothing, False] + (z >= x) . to_vector . should_equal [False, False, Nothing, True] + (y != x) . to_vector . should_equal [True, True, Nothing, True] + (y > x) . to_vector . should_equal [False, False, Nothing, True] + (y <= x) . to_vector . should_equal [True, True, Nothing, False] + (y.min x) . to_vector . should_equal [1, 2, 3, 3] + (y.max x) . to_vector . should_equal [10^30, 2^70, 3, 4] + (z.min x) . to_vector . should_equal [1.5, 2.5, 3.5, 3] + (z.max x) . to_vector . should_equal [10^30, 2^70, 3.5, 4.5] + + x.fill_nothing y . to_vector . should_equal [10^30, 2^70, 3, 3] + x.fill_nothing z . to_vector . should_equal [10^30, 2^70, 3.5, 3] + y.fill_nothing x . to_vector . should_equal [1, 2, 3, 4] + y2 = (y < 2).iif Nothing y + y2.to_vector . should_equal [Nothing, 2, 3, 4] + y2.fill_nothing x . to_vector . should_equal [10^30, 2, 3, 4] + ((z < 2).iif Nothing z).fill_nothing x . to_vector . should_equal [10^30, 2.5, 3.5, 4.5] + + r3 = x.fill_nothing 1.5 + r3.value_type . should_equal Value_Type.Float + # 2^70 is not exactly representable as a Float. + (2^70 + 0.0).truncate . should_not_equal (2^70) + Problems.expect_only_warning Loss_Of_Integer_Precision r3 + r3.to_vector . should_equal [10^30, 2^70, 1.5, 3] + + r4 = x.fill_nothing 23 + r4.value_type . should_be_a (Value_Type.Decimal ...) + r4.to_vector . should_equal [10^30, 2^70, 23, 3] + + Test.specify "returns a Decimal column if the scalar argument is a big integer" <| + c = Column.from_vector "X" [1, 2, Nothing, 3] + c.value_type.should_equal Value_Type.Integer + x = 2^70 + + r1 = c + x + r1.value_type . should_be_a (Value_Type.Decimal ...) + r1.to_vector . should_equal [1 + x, 2 + x, Nothing, 3 + x] + + (c - x) . to_vector . should_equal [1 - x, 2 - x, Nothing, 3 - x] + (c * x) . to_vector . should_equal [1 * x, 2 * x, Nothing, 3 * x] + r2 = c % x + r2.value_type . should_be_a (Value_Type.Decimal ...) + r2.to_vector . should_equal [1, 2, Nothing, 3] + + r3 = (c == x) + r3.value_type . should_equal Value_Type.Boolean + r3.to_vector . should_equal [False, False, Nothing, False] + + (c < x) . to_vector . should_equal [True, True, Nothing, True] + (c >= x) . to_vector . should_equal [False, False, Nothing, False] + + r4 = c / x + r4.value_type . should_equal Value_Type.Float + r4.to_vector . should_equal [1 / x, 2 / x, Nothing, 3 / x] + + r5 = c ^ x + r5.value_type . should_equal Value_Type.Float + r5.to_vector . should_equal [1 ^ x, 2 ^ x, Nothing, 3 ^ x] + + r6 = c.min x + r6.value_type . should_be_a (Value_Type.Decimal ...) + r6.to_vector . should_equal [1, 2, x, 3] + c.max x . to_vector . should_equal [x, x, x, x] + + r7 = c.fill_nothing x + r7.value_type . should_be_a (Value_Type.Decimal ...) + r7.to_vector . should_equal [1, 2, x, 3] + + Test.specify "should work fine with typing edge cases" <| + c1 = Column.from_vector "X" [2^70, 100, Nothing, 200] + c1.value_type . should_be_a (Value_Type.Decimal ...) + + r2 = c1.fill_nothing "NA" + r2.should_fail_with No_Common_Type + + c2 = c1.cast Value_Type.Mixed . fill_nothing "NA" + c2.value_type . should_equal Value_Type.Mixed + c2.to_vector . should_equal [2^70, 100, "NA", 200] + + c3 = (c2 == "NA").iif 0 c2 + c3.to_vector . should_equal [2^70, 100, 0, 200] + # Still a mixed value type due to combining of input types: Integer+Mixed->Mixed + c3.value_type . should_equal Value_Type.Mixed + + # But the precise type should be inferred: + c3.inferred_precise_value_type . should_be_a (Value_Type.Decimal ...) + # And so arithmetic should work: + (c3 + 100) . to_vector . should_equal [2^70 + 100, 200, 100, 300] diff --git a/test/Tests/polyglot-sources/enso-test-java-helpers/src/main/java/org/enso/table_test_helpers/PolyglotHelpers.java b/test/Tests/polyglot-sources/enso-test-java-helpers/src/main/java/org/enso/table_test_helpers/PolyglotHelpers.java new file mode 100644 index 000000000000..b89fde98b158 --- /dev/null +++ b/test/Tests/polyglot-sources/enso-test-java-helpers/src/main/java/org/enso/table_test_helpers/PolyglotHelpers.java @@ -0,0 +1,15 @@ +package org.enso.table_test_helpers; + +import java.math.BigInteger; + +public class PolyglotHelpers { + /* Creates a big integer that is larger than long can fit. */ + public static BigInteger createBigBigIntegerComingFromJava() { + return BigInteger.TWO.pow(70).subtract(BigInteger.ONE); + } + + /* Creates a big integer that could fit in a long. */ + public static BigInteger createSmallBigIntegerComingFromJava() { + return BigInteger.TEN; + } +}