-
Notifications
You must be signed in to change notification settings - Fork 116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8371297134 Type promotion between integral and floating types #2173
base: master
Are you sure you want to change the base?
Changes from 5 commits
3f006eb
ad4b88b
557f796
064f14f
5280445
0c4b25b
04eeafb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -39,32 +39,56 @@ namespace arcticdb { | |||||
return false; | ||||||
} | ||||||
|
||||||
std::optional<entity::TypeDescriptor> has_valid_type_promotion( | ||||||
constexpr bool is_mixed_float_and_integer(entity::DataType left, entity::DataType right) { | ||||||
return (is_integer_type(left) && is_floating_point_type(right)) || (is_floating_point_type(left) && is_integer_type(right)); | ||||||
} | ||||||
|
||||||
std::optional<entity::TypeDescriptor> common_type_float_integer(const entity::TypeDescriptor& left, const entity::TypeDescriptor& right) { | ||||||
auto dimension = left.dimension(); | ||||||
vasil-pashov marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
auto left_type = left.data_type(); | ||||||
auto right_type = right.data_type(); | ||||||
auto left_size = slice_bit_size(left_type); | ||||||
auto right_size = slice_bit_size(right_type); | ||||||
internal::check<ErrorCode::E_ASSERTION_FAILURE>(is_mixed_float_and_integer(left_type, right_type), | ||||||
"Expected one int and one float in common_type_floats_integer"); | ||||||
|
||||||
auto target_size = entity::SizeBits::UNKNOWN_SIZE_BITS; | ||||||
auto floating_size = is_floating_point_type(left_type) ? left_size : right_size; | ||||||
auto integral_size = is_floating_point_type(left_type) ? right_size : left_size; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think using |
||||||
if (floating_size == entity::SizeBits::S64 || integral_size >= entity::SizeBits::S32) { | ||||||
// (u)int64 up to float64 will lose precision, we accept that | ||||||
target_size = entity::SizeBits::S64; | ||||||
} else { | ||||||
// (u)int(8/16) can fit in float32 since float32 has 24 precision bits | ||||||
target_size = entity::SizeBits::S32; | ||||||
} | ||||||
|
||||||
return std::make_optional<entity::TypeDescriptor>(combine_data_type(entity::ValueType::FLOAT, target_size), dimension); | ||||||
} | ||||||
|
||||||
bool is_valid_type_promotion_to_target( | ||||||
const entity::TypeDescriptor& source, | ||||||
const entity::TypeDescriptor& target | ||||||
) { | ||||||
|
||||||
if (source.dimension() != target.dimension()) { | ||||||
// Empty of dimension 0 means lack of any given type and can be promoted to anything (even if the dimensions | ||||||
// don't match), e.g. empty type can become int or array of ints. Empty type of higher dimension is used to | ||||||
// specify an empty array or an empty matrix, thus it cannot become any other type unless the dimensionality | ||||||
// matches | ||||||
if (is_empty_type(source.data_type()) && source.dimension() == entity::Dimension::Dim0) | ||||||
return target; | ||||||
return std::nullopt; | ||||||
return is_empty_type(source.data_type()) && source.dimension() == entity::Dimension::Dim0; | ||||||
} | ||||||
|
||||||
if (source == target) | ||||||
return target; | ||||||
return true; | ||||||
|
||||||
// Empty type is coercible to any type | ||||||
if (is_empty_type(source.data_type())) { | ||||||
return target; | ||||||
return true; | ||||||
} | ||||||
|
||||||
// Nothing is coercible to the empty type. | ||||||
if (is_empty_type(target.data_type())) { | ||||||
return std::nullopt; | ||||||
return false; | ||||||
} | ||||||
|
||||||
auto source_type = source.data_type(); | ||||||
|
@@ -73,101 +97,101 @@ namespace arcticdb { | |||||
auto target_size = slice_bit_size(target_type); | ||||||
|
||||||
if (is_time_type(source_type)) { | ||||||
if (!is_time_type(target_type)) | ||||||
return std::nullopt; | ||||||
return is_time_type(target_type); | ||||||
} else if (is_unsigned_type(source_type)) { | ||||||
if (is_unsigned_type(target_type)) { | ||||||
// UINT->UINT, target_size must be >= source_size | ||||||
if (source_size > target_size) | ||||||
return std::nullopt; | ||||||
// UINT->UINT | ||||||
return target_size >= source_size; | ||||||
} else if (is_signed_type(target_type)) { | ||||||
// UINT->INT, target_size must be > source_size | ||||||
if (source_size >= target_size) | ||||||
return std::nullopt; | ||||||
// UINT->INT | ||||||
return target_size > source_size; | ||||||
} else if (is_floating_point_type(target_type)) { | ||||||
// UINT->FLOAT, no restrictions on relative sizes | ||||||
// UINT->FLOAT | ||||||
return target_size == entity::SizeBits::S64 || source_size < entity::SizeBits::S32; | ||||||
} else { | ||||||
// Non-numeric target type | ||||||
return std::nullopt; | ||||||
return false; | ||||||
} | ||||||
} else if (is_signed_type(source_type)) { | ||||||
if (is_unsigned_type(target_type)) { | ||||||
// INT->UINT never promotable | ||||||
return std::nullopt; | ||||||
return false; | ||||||
} else if (is_signed_type(target_type)) { | ||||||
// INT->INT, target_size must be >= source_size | ||||||
if (source_size > target_size) | ||||||
return std::nullopt; | ||||||
// INT->INT | ||||||
return target_size >= source_size; | ||||||
} else if (is_floating_point_type(target_type)) { | ||||||
// INT->FLOAT, no restrictions on relative sizes | ||||||
// INT->FLOAT | ||||||
return target_size == entity::SizeBits::S64 || source_size < entity::SizeBits::S32; | ||||||
} else { | ||||||
// Non-numeric target type | ||||||
return std::nullopt; | ||||||
return false; | ||||||
} | ||||||
} else if (is_floating_point_type(source_type)) { | ||||||
if (is_unsigned_type(target_type) || is_signed_type(target_type)) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not part of the change but is a bit more readable
Suggested change
|
||||||
// FLOAT->U/INT never promotable | ||||||
return std::nullopt; | ||||||
return false; | ||||||
} else if (is_floating_point_type(target_type)) { | ||||||
// FLOAT->FLOAT, target_size must be >= source_size | ||||||
if (source_size > target_size) | ||||||
return std::nullopt; | ||||||
// FLOAT->FLOAT | ||||||
return target_size >= source_size; | ||||||
} else { | ||||||
// Non-numeric target type | ||||||
return std::nullopt; | ||||||
return false; | ||||||
} | ||||||
} else if (is_sequence_type(source_type) && is_sequence_type(target_type)) { | ||||||
// Only allow promotion with UTF strings, and only to dynamic (never to fixed width) | ||||||
if (!is_utf_type(source_type) || !is_utf_type(target_type) || !is_dynamic_string_type(target_type)) | ||||||
return std::nullopt; | ||||||
return is_utf_type(source_type) && is_utf_type(target_type) && is_dynamic_string_type(target_type); | ||||||
} else if (is_bool_object_type(source_type)) { | ||||||
return std::nullopt; | ||||||
return false; | ||||||
} else { | ||||||
// Non-numeric source type | ||||||
return std::nullopt; | ||||||
return false; | ||||||
} | ||||||
} | ||||||
|
||||||
std::optional<entity::TypeDescriptor> common_type_mixed_sign_ints(const entity::TypeDescriptor& left, const entity::TypeDescriptor& right) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
auto dimension = left.dimension(); | ||||||
auto left_type = left.data_type(); | ||||||
auto right_type = right.data_type(); | ||||||
auto left_size = slice_bit_size(left_type); | ||||||
auto right_size = slice_bit_size(right_type); | ||||||
// To get here we must have one signed and one unsigned type, with the width of the signed type <= the width of the unsigned type | ||||||
internal::check<ErrorCode::E_ASSERTION_FAILURE>(is_signed_type(left_type) ^ is_signed_type(right_type), | ||||||
"Expected one signed and one unsigned int in has_valid_common_type"); | ||||||
if (is_signed_type(left_type)) { | ||||||
internal::check<ErrorCode::E_ASSERTION_FAILURE>(left_size <= right_size, | ||||||
"Expected left_size <= right_size in has_valid_common_type"); | ||||||
} else { | ||||||
// is_signed_type(right_type) | ||||||
internal::check<ErrorCode::E_ASSERTION_FAILURE>(right_size <= left_size, | ||||||
"Expected right_size <= left_size in has_valid_common_type"); | ||||||
} | ||||||
|
||||||
return target; | ||||||
auto target_size = entity::SizeBits(uint8_t(std::max(left_size, right_size)) + 1); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I get why this works but it seems a bit hacky and fragile. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just the old logic moved around, so I'd rather not change it |
||||||
if (target_size < entity::SizeBits::COUNT) { | ||||||
return std::make_optional<entity::TypeDescriptor>(combine_data_type(entity::ValueType::INT, target_size), dimension); | ||||||
} else { | ||||||
return std::nullopt; | ||||||
} | ||||||
} | ||||||
|
||||||
std::optional<entity::TypeDescriptor> has_valid_common_type( | ||||||
const entity::TypeDescriptor& left, | ||||||
const entity::TypeDescriptor& right | ||||||
) { | ||||||
auto maybe_common_type = has_valid_type_promotion(left, right); | ||||||
if (!maybe_common_type) { | ||||||
maybe_common_type = has_valid_type_promotion(right, left); | ||||||
std::optional<entity::TypeDescriptor> has_valid_common_type(const entity::TypeDescriptor& left, const entity::TypeDescriptor& right) { | ||||||
if (is_valid_type_promotion_to_target(left, right)) { | ||||||
return right; | ||||||
} else if (is_valid_type_promotion_to_target(right, left)) { | ||||||
return left; | ||||||
} | ||||||
// has_valid_type_promotion checks if the second type can represent all values of the first type | ||||||
// We also want to handle cases where there is a third type that can represent both | ||||||
// In practice, this is only the case when there is one signed and one unsigned integer right now | ||||||
if (!maybe_common_type && | ||||||
left.dimension() == right.dimension() && | ||||||
is_integer_type(left.data_type()) && | ||||||
is_integer_type(right.data_type())) { | ||||||
auto dimension = left.dimension(); | ||||||
auto left_type = left.data_type(); | ||||||
auto right_type = right.data_type(); | ||||||
auto left_size = slice_bit_size(left_type); | ||||||
auto right_size = slice_bit_size(right_type); | ||||||
// To get here we must have one signed and one unsigned type, with the width of the signed type <= the width of the unsigned type | ||||||
internal::check<ErrorCode::E_ASSERTION_FAILURE>(is_signed_type(left_type) ^ is_signed_type(right_type), | ||||||
"Expected one signed and one unsigned int in has_valid_common_type"); | ||||||
if (is_signed_type(left_type)) { | ||||||
internal::check<ErrorCode::E_ASSERTION_FAILURE>(left_size <= right_size, | ||||||
"Expected left_size <= right_size in has_valid_common_type"); | ||||||
} else { | ||||||
// is_signed_type(right_type) | ||||||
internal::check<ErrorCode::E_ASSERTION_FAILURE>(right_size <= left_size, | ||||||
"Expected right_size <= left_size in has_valid_common_type"); | ||||||
} | ||||||
auto target_size = entity::SizeBits(uint8_t(std::max(left_size, right_size)) + 1); | ||||||
if (target_size < entity::SizeBits::COUNT) { | ||||||
maybe_common_type = entity::TypeDescriptor{ | ||||||
combine_data_type(entity::ValueType::INT, target_size), | ||||||
dimension}; | ||||||
} | ||||||
|
||||||
if (left.dimension() != right.dimension()) { | ||||||
return std::nullopt; | ||||||
} | ||||||
|
||||||
if (is_integer_type(left.data_type()) && is_integer_type(right.data_type())) { | ||||||
vasil-pashov marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
return common_type_mixed_sign_ints(left, right); | ||||||
} else if (is_mixed_float_and_integer(left.data_type(), right.data_type())) { | ||||||
return common_type_float_integer(left, right); | ||||||
} else { | ||||||
return std::nullopt; | ||||||
} | ||||||
return maybe_common_type; | ||||||
} | ||||||
|
||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: If used only in this file mark it as static