From 580a32692a6e75d65646f544dcee8d3f29caf835 Mon Sep 17 00:00:00 2001 From: Ryoji Kurosawa Date: Tue, 17 Oct 2023 21:49:10 +0900 Subject: [PATCH] test cast between float and int --- .../expression/details/cast_evaluation.cpp | 37 +++- .../impl/expression/details/constants.h | 135 +++++++++++++++ .../process/cast_between_numerics_test.cpp | 158 ++++++++++++++---- 3 files changed, 298 insertions(+), 32 deletions(-) create mode 100644 src/jogasaki/executor/process/impl/expression/details/constants.h diff --git a/src/jogasaki/executor/process/impl/expression/details/cast_evaluation.cpp b/src/jogasaki/executor/process/impl/expression/details/cast_evaluation.cpp index b6d41165a..d71fddd90 100644 --- a/src/jogasaki/executor/process/impl/expression/details/cast_evaluation.cpp +++ b/src/jogasaki/executor/process/impl/expression/details/cast_evaluation.cpp @@ -36,6 +36,7 @@ #include #include "common.h" +#include "constants.h" #include "jogasaki/executor/process/impl/expression/evaluator_context.h" namespace jogasaki::executor::process::impl::expression::details { @@ -167,6 +168,7 @@ any to_int1(takatori::decimal::triple src, evaluator_context& ctx) { decimal::context = decimal::IEEEContext(128); decimal::Decimal value{src}; decimal::context.clear_status(); + decimal::context.round(MPD_ROUND_DOWN); auto rounded = value.to_integral(); if(! validate_decimal_in_int64(rounded)) { return any{std::in_place_type, error(error_kind::overflow)}; @@ -178,6 +180,7 @@ any to_int2(takatori::decimal::triple src, evaluator_context& ctx) { decimal::context = decimal::IEEEContext(128); decimal::Decimal value{src}; decimal::context.clear_status(); + decimal::context.round(MPD_ROUND_DOWN); auto rounded = value.to_integral(); if(! validate_decimal_in_int64(rounded)) { return any{std::in_place_type, error(error_kind::overflow)}; @@ -189,6 +192,7 @@ any to_int4(takatori::decimal::triple src, evaluator_context& ctx) { decimal::context = decimal::IEEEContext(128); decimal::Decimal value{src}; decimal::context.clear_status(); + decimal::context.round(MPD_ROUND_DOWN); auto rounded = value.to_integral(); if(! validate_decimal_in_int64(rounded)) { return any{std::in_place_type, error(error_kind::overflow)}; @@ -200,6 +204,7 @@ any to_int8(takatori::decimal::triple src, evaluator_context& ctx) { decimal::context = decimal::IEEEContext(128); decimal::Decimal value{src}; decimal::context.clear_status(); + decimal::context.round(MPD_ROUND_DOWN); auto rounded = value.to_integral(); if(! validate_decimal_in_int64(rounded)) { return any{std::in_place_type, error(error_kind::overflow)}; @@ -722,19 +727,43 @@ any to_character(float src, evaluator_context& ctx, std::optional l } any to_int1(float src, evaluator_context& ctx) { - return validate_integer_range(std::floor(src), ctx); + if(std::isinf(src) || std::isnan(src)) { + return any{std::in_place_type, error(error_kind::overflow)}; + } + return validate_integer_range(std::trunc(src), ctx); } any to_int2(float src, evaluator_context& ctx) { - return validate_integer_range(std::floor(src), ctx); + if(std::isinf(src) || std::isnan(src)) { + return any{std::in_place_type, error(error_kind::overflow)}; + } + return validate_integer_range(std::trunc(src), ctx); } + any to_int4(float src, evaluator_context& ctx) { - return validate_integer_range(std::floor(src), ctx); + if(std::isinf(src) || std::isnan(src)) { + return any{std::in_place_type, error(error_kind::overflow)}; + } + auto truncated = std::trunc(src); + // float values close to int4 max/min go over them when converted to int4 + if(truncated > max_integral_float_convertible_to_int || + truncated < min_integral_float_convertible_to_int) { + return any{std::in_place_type, error(error_kind::overflow)}; + } + return validate_integer_range(truncated, ctx); } any to_int8(float src, evaluator_context& ctx) { - return validate_integer_range(std::floor(src), ctx); + if(std::isinf(src) || std::isnan(src)) { + return any{std::in_place_type, error(error_kind::overflow)}; + } + auto truncated = std::trunc(src); + if(truncated > max_integral_float_convertible_to_int || + truncated < min_integral_float_convertible_to_int) { + return any{std::in_place_type, error(error_kind::overflow)}; + } + return validate_integer_range(truncated, ctx); } any to_float8(float src, evaluator_context& ctx) { diff --git a/src/jogasaki/executor/process/impl/expression/details/constants.h b/src/jogasaki/executor/process/impl/expression/details/constants.h new file mode 100644 index 000000000..0eb2f2834 --- /dev/null +++ b/src/jogasaki/executor/process/impl/expression/details/constants.h @@ -0,0 +1,135 @@ +/* + * Copyright 2018-2023 Project Tsurugi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace jogasaki::executor::process::impl::expression::details { + +using kind = meta::field_type_kind; + +template +using runtime_type = typename meta::field_type_traits::runtime_type; + +template +constexpr runtime_type max_integral_float_convertible_to_int_source; + +template +constexpr runtime_type max_integral_float_convertible_to_int = + static_cast>(max_integral_float_convertible_to_int_source); + +// from float4 +template <> +constexpr runtime_type max_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::max()); + +template <> +constexpr runtime_type max_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::max()); + +template <> +constexpr runtime_type max_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::max() - 64); + +template <> +constexpr runtime_type max_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::max() - 256L*1024L*1024L*1024L); + + +// from float8 +template <> +constexpr runtime_type max_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::max()); + +template <> +constexpr runtime_type max_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::max()); + +template <> +constexpr runtime_type max_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::max() - 2024); + +template <> +constexpr runtime_type max_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::max() - 0); //TODO + + +template +constexpr runtime_type min_integral_float_convertible_to_int_source; + +template +constexpr runtime_type min_integral_float_convertible_to_int = + static_cast>(min_integral_float_convertible_to_int_source); + +// from float4 +template <> +constexpr runtime_type min_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::min()); + +template <> +constexpr runtime_type min_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::min()); + +template <> +constexpr runtime_type min_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::min()); + +template <> +constexpr runtime_type min_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::min() - 0); //TODO + + +// from float8 +template <> +constexpr runtime_type min_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::min()); + +template <> +constexpr runtime_type min_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::min()); + +template <> +constexpr runtime_type min_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::min()); + +template <> +constexpr runtime_type min_integral_float_convertible_to_int_source = + static_cast>(std::numeric_limits::min() - 0); //TODO + +} // namespace diff --git a/test/jogasaki/executor/process/cast_between_numerics_test.cpp b/test/jogasaki/executor/process/cast_between_numerics_test.cpp index d0b2d4bd3..6d8305962 100644 --- a/test/jogasaki/executor/process/cast_between_numerics_test.cpp +++ b/test/jogasaki/executor/process/cast_between_numerics_test.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -68,6 +69,7 @@ #include #include #include +#include #include @@ -287,15 +289,11 @@ void cast_between_numerics_test::test_decimal_to_int(std::function, 10}), fn(make_triple("10"), ctx)); EXPECT_EQ((any{std::in_place_type, 123}), fn(make_triple("123"), ctx)); - // round up/down boundaries on each .500, inclusive/exclusive changes on even and odd alternately - EXPECT_EQ((any{std::in_place_type, 1}), fn(make_triple("1.499"), ctx)); - EXPECT_EQ((any{std::in_place_type, 2}), fn(make_triple("1.500"), ctx)); - EXPECT_EQ((any{std::in_place_type, 2}), fn(make_triple("2.500"), ctx)); - EXPECT_EQ((any{std::in_place_type, 3}), fn(make_triple("2.501"), ctx)); - EXPECT_EQ((any{std::in_place_type, 3}), fn(make_triple("3.499"), ctx)); - EXPECT_EQ((any{std::in_place_type, 4}), fn(make_triple("3.500"), ctx)); - EXPECT_EQ((any{std::in_place_type, 4}), fn(make_triple("4.500"), ctx)); - EXPECT_EQ((any{std::in_place_type, 5}), fn(make_triple("4.501"), ctx)); + // the numbers under decimal point will be truncated + EXPECT_EQ((any{std::in_place_type, 1}), fn(make_triple("1.5"), ctx)); + EXPECT_EQ((any{std::in_place_type, 2}), fn(make_triple("2.5"), ctx)); + EXPECT_EQ((any{std::in_place_type, -1}), fn(make_triple("-1.5"), ctx)); + EXPECT_EQ((any{std::in_place_type, -2}), fn(make_triple("-2.5"), ctx)); EXPECT_EQ((any{std::in_place_type, int_max()}), fn(make_triple(std::to_string(int_max())), ctx)); EXPECT_EQ(any_error(error_kind::overflow), fn(make_triple(int_max_plus_one_str()), ctx)); @@ -472,31 +470,135 @@ TEST_F(cast_between_numerics_test, float8_to_decimal) { EXPECT_EQ((any_error(error_kind::overflow)), from_float8::to_decimal(std::numeric_limits::quiet_NaN(), ctx, {}, {})); } -TEST_F(cast_between_numerics_test, float4_to_int4) { - evaluator_context ctx{&resource_}; - EXPECT_EQ((any_triple(1, 0, 1000, -3)), from_float4::to_int4(1.0f, ctx, 5, 3)); - EXPECT_EQ((any_triple(0, 0, 0, -3)), from_float4::to_decimal(0.0f, ctx, 5, 3)); - EXPECT_EQ((any_triple(0, 0, 0, -3)), from_float4::to_decimal(-0.0f, ctx, 5, 3)); - EXPECT_EQ((any_triple(-1, 0, 1000, -3)), from_float4::to_decimal(-1.0f, ctx, 5, 3)); - EXPECT_EQ((any_triple(1, 0, 10000, -3)), from_float4::to_decimal(10.0f, ctx, 5, 3)); - EXPECT_EQ((any_triple(1, 0, 1230, -3)), from_float4::to_decimal(1.23f, ctx, 5, 3)); - EXPECT_EQ((any_triple(1, 0, 123, -2)), from_float4::to_decimal(1.23f, ctx, 5, 2)); +template +void test_verify_constants() { + // be careful in testing on constexpr template constants - updating constants file and recompile sometime fails to + // reflect the update. I had to recompile the testcase file to ensure updates reflected. + { + auto x = static_cast(max_integral_float_convertible_to_int_source); + EXPECT_LT(0, x); + std::cerr << "x=" << x << std::endl; + std::feclearexcept(FE_ALL_EXCEPT); + auto y = static_cast>(x); + EXPECT_FALSE(std::fetestexcept(FE_INVALID)); + EXPECT_LT(0, y); + std::cerr << "y=" << y << std::endl; + } - // verify on float min/max - EXPECT_EQ((any_error(error_kind::overflow)), from_float4::to_decimal(3.40282e+38f, ctx, {}, {})); - EXPECT_EQ((any_triple(0, 0, 0, -6)), from_float4::to_decimal(1.17549e-38f, ctx, {}, {})); - EXPECT_EQ((any_triple(0, 0, 0, 0)), from_float4::to_decimal(1.17549e-38f, ctx, {}, 0)); + { + auto x = static_cast(max_integral_float_convertible_to_int_source + 1); + EXPECT_LT(0, x); + std::cerr << "x=" << x << std::endl; + std::feclearexcept(FE_ALL_EXCEPT); + auto y = static_cast>(x); + EXPECT_TRUE(std::fetestexcept(FE_INVALID)); + EXPECT_GT(0, y); + std::cerr << "y=" << y << std::endl; + } +} - // verify on decimal min/max - EXPECT_FALSE(from_float4::to_decimal(1.0e+38f-1, ctx, {}, 0).error()); // 9999...(38 digits) - alpha - EXPECT_EQ((any_error(error_kind::overflow)), from_float4::to_decimal(1.1e+38f, ctx, 38, 0)); // 9999...(38 digits) + alpha + +TEST_F(cast_between_numerics_test, verify_float4_in4_max_constants) { + // verify int4 max - 64 is the maximum that is safe to convert between int4/float4 + test_verify_constants(); +} + + +TEST_F(cast_between_numerics_test, verify_float4_in8_max_constants) { + // verify int8 max - 64 is the maximum that is safe to convert between int4/float4 + test_verify_constants(); +} + +TEST_F(cast_between_numerics_test, verify_float4_int4_min_constants) { + { + auto x = static_cast(min_integral_float_convertible_to_int_source); + EXPECT_GT(0, x); + std::cerr << "x=" << x << std::endl; + std::feclearexcept(FE_ALL_EXCEPT); + auto y = static_cast(x); + EXPECT_FALSE(std::fetestexcept(FE_INVALID)); + EXPECT_GT(0, y); + std::cerr << "y=" << y << std::endl; + } + // as int4 min is used for the constant, we cannot test constant - 1. +} + + +template +void test_float_to_int_common(evaluator_context& ctx, std::function fn) { + EXPECT_EQ((any{std::in_place_type, 0}), fn(0.0f, ctx)); + EXPECT_EQ((any{std::in_place_type, 0}), fn(-0.0f, ctx)); + EXPECT_EQ((any{std::in_place_type, 1}), fn(1.0f, ctx)); + EXPECT_EQ((any{std::in_place_type, -1}), fn(-1.0f, ctx)); + EXPECT_EQ((any{std::in_place_type, 10}), fn(10.0f, ctx)); + + // right to decimal point are truncated (round down) + EXPECT_EQ((any{std::in_place_type, 1}), fn(1.5f, ctx)); + EXPECT_EQ((any{std::in_place_type, 2}), fn(2.5f, ctx)); + EXPECT_EQ((any{std::in_place_type, -1}), fn(-1.5f, ctx)); + EXPECT_EQ((any{std::in_place_type, -2}), fn(-2.5f, ctx)); + + // verify on floats min/max + EXPECT_EQ((any_error(error_kind::overflow)), fn(std::numeric_limits::max(), ctx)); + EXPECT_EQ((any{std::in_place_type, 0}), fn(std::numeric_limits::min(), ctx)); // special values - EXPECT_EQ((any_error(error_kind::overflow)), from_float4::to_decimal(std::numeric_limits::infinity(), ctx, {}, {})); - EXPECT_EQ((any_error(error_kind::overflow)), from_float4::to_decimal(-std::numeric_limits::infinity(), ctx, {}, {})); - EXPECT_EQ((any_error(error_kind::overflow)), from_float4::to_decimal(std::numeric_limits::quiet_NaN(), ctx, {}, {})); + EXPECT_EQ((any_error(error_kind::overflow)), fn(std::numeric_limits::infinity(), ctx)); + EXPECT_EQ((any_error(error_kind::overflow)), fn(-std::numeric_limits::infinity(), ctx)); + EXPECT_EQ((any_error(error_kind::overflow)), fn(std::numeric_limits::quiet_NaN(), ctx)); + EXPECT_EQ((any_error(error_kind::overflow)), fn(-std::numeric_limits::quiet_NaN(), ctx)); + +} + +template +void test_float_to_int_minmax(evaluator_context& ctx, std::function, evaluator_context&)> fn) { + // verify on int min/max + using Int = runtime_type; + using Float = runtime_type; + EXPECT_EQ((any{std::in_place_type, static_cast(max_integral_float_convertible_to_int)}), fn(max_integral_float_convertible_to_int, ctx)); + EXPECT_EQ((any_error(error_kind::overflow)), fn(static_cast(max_integral_float_convertible_to_int_source+1), ctx)); + EXPECT_EQ((any{std::in_place_type, static_cast(min_integral_float_convertible_to_int)}), fn(min_integral_float_convertible_to_int, ctx)); + EXPECT_EQ((any_error(error_kind::overflow)), fn(min_integral_float_convertible_to_int-256, ctx)); // simply -1 doesn't change the float //FIXME } +TEST_F(cast_between_numerics_test, float4_to_int1) { + evaluator_context ctx{&resource_}; + test_float_to_int_common(ctx, [](float src, evaluator_context& ctx){ + return from_float4::to_int1(src, ctx); + }); + test_float_to_int_minmax(ctx, [](float src, evaluator_context& ctx){ + return from_float4::to_int1(src, ctx); + }); +} + +TEST_F(cast_between_numerics_test, float4_to_int2) { + evaluator_context ctx{&resource_}; + test_float_to_int_common(ctx, [](float src, evaluator_context& ctx){ + return from_float4::to_int2(src, ctx); + }); + test_float_to_int_minmax(ctx, [](float src, evaluator_context& ctx){ + return from_float4::to_int2(src, ctx); + }); +} +TEST_F(cast_between_numerics_test, float4_to_int4) { + evaluator_context ctx{&resource_}; + test_float_to_int_common(ctx, [](float src, evaluator_context& ctx){ + return from_float4::to_int4(src, ctx); + }); + test_float_to_int_minmax(ctx, [](float src, evaluator_context& ctx){ + return from_float4::to_int4(src, ctx); + }); +} + +TEST_F(cast_between_numerics_test, float4_to_int8) { + evaluator_context ctx{&resource_}; + test_float_to_int_common(ctx, [](float src, evaluator_context& ctx){ + return from_float4::to_int8(src, ctx); + }); + test_float_to_int_minmax(ctx, [](float src, evaluator_context& ctx){ + return from_float4::to_int8(src, ctx); + }); +} }