/*! * Copyright (c) 2021 Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See LICENSE file in the project root for license information. */ #include #include #include "../include/LightGBM/utils/common.h" // This is a basic test for floating number parsing. // Most of the test cases come from: // https://github.com/dmlc/xgboost/blob/master/tests/cpp/common/test_charconv.cc // https://github.com/Alexhuszagh/rust-lexical/blob/master/data/test-parse-unittests/strtod_tests.toml class AtofPreciseTest : public testing::Test { public: struct AtofTestCase { const char* data; double expected; }; static double TestAtofPrecise( const char* data, double expected, bool test_eq = true) { double got = 0; const char* end = LightGBM::Common::AtofPrecise(data, &got); EXPECT_TRUE(end != data) << "fail to parse: " << data; EXPECT_EQ(*end, '\0') << "not parsing to end: " << data; if (test_eq) { EXPECT_EQ(expected, got) << "parse string: " << data; } return got; } static double Int64Bits2Double(uint64_t v) { union { uint64_t i; double d; } conv; conv.i = v; return conv.d; } }; TEST_F(AtofPreciseTest, Basic) { AtofTestCase test_cases[] = { { "0", 0.0 }, { "0E0", 0.0 }, { "-0E0", 0.0 }, { "-0", -0.0 }, { "1", 1.0 }, { "1E0", 1.0 }, { "-1", -1.0 }, { "-1E0", -1.0 }, { "123456.0", 123456.0 }, { "432E1", 432E1 }, { "1.2345678", 1.2345678 }, { "2.4414062E-4", 2.4414062E-4 }, { "3.0540412E5", 3.0540412E5 }, { "3.355445E7", 3.355445E7 }, { "1.1754944E-38", 1.1754944E-38 }, }; for (auto const& test : test_cases) { TestAtofPrecise(test.data, test.expected); } } TEST_F(AtofPreciseTest, CornerCases) { AtofTestCase test_cases[] = { { "1e-400", 0.0 }, { "2.4703282292062326e-324", 0.0 }, { "4.9406564584124654e-324", Int64Bits2Double(0x0000000000000001LU) }, { "8.44291197326099e-309", Int64Bits2Double(0x0006123400000001LU) }, // FLT_MAX { "3.40282346638528859811704183484516925440e38", static_cast(std::numeric_limits::max()) }, // FLT_MIN { "1.1754943508222875079687365372222456778186655567720875215087517062784172594547271728515625e-38", static_cast(std::numeric_limits::min()) }, // DBL_MAX (1 + (1 - 2^-52)) * 2^1023 = (2^53 - 1) * 2^971 { "17976931348623157081452742373170435679807056752584499659891747680315" "72607800285387605895586327668781715404589535143824642343213268894641" "82768467546703537516986049910576551282076245490090389328944075868508" "45513394230458323690322294816580855933212334827479782620414472316873" "8177180919299881250404026184124858368", std::numeric_limits::max() }, { "1.7976931348623158e+308", std::numeric_limits::max() }, // 2^971 * (2^53 - 1 + 1/2) : the smallest number resolving to inf {"179769313486231580793728971405303415079934132710037826936173778980444" "968292764750946649017977587207096330286416692887910946555547851940402" "630657488671505820681908902000708383676273854845817711531764475730270" "069855571366959622842914819860834936475292719074168444365510704342711" "559699508093042880177904174497792", std::numeric_limits::infinity() }, // Near DBL_MIN { "2.2250738585072009e-308", Int64Bits2Double(0x000fffffffffffffLU) }, // DBL_MIN 2^-1022 { "2.2250738585072012e-308", std::numeric_limits::min() }, { "2.2250738585072014e-308", std::numeric_limits::min() }, }; for (auto const& test : test_cases) { TestAtofPrecise(test.data, test.expected); } } TEST_F(AtofPreciseTest, ErrorInput) { double got = 0; ASSERT_THROW(LightGBM::Common::AtofPrecise("x1", &got), std::runtime_error); } TEST_F(AtofPreciseTest, NaN) { AtofTestCase test_cases[] = { { "nan", std::numeric_limits::quiet_NaN() }, { "NaN", std::numeric_limits::quiet_NaN() }, { "NAN", std::numeric_limits::quiet_NaN() }, // The behavior for parsing -nan depends on implementation. // Thus we skip binary check for negative nan. { "-nan", -std::numeric_limits::quiet_NaN() }, { "-NaN", -std::numeric_limits::quiet_NaN() }, { "-NAN", -std::numeric_limits::quiet_NaN() }, }; for (auto const& test : test_cases) { double got = TestAtofPrecise(test.data, test.expected, false); EXPECT_TRUE(std::isnan(got)) << "not parsed as NaN: " << test.data; if (got > 0) { // See comment in test_cases. EXPECT_EQ(memcmp(&got, &test.expected, sizeof(test.expected)), 0) << "parsed NaN is not the same for every bit: " << test.data; } } } TEST_F(AtofPreciseTest, Inf) { AtofTestCase test_cases[] = { { "inf", std::numeric_limits::infinity() }, { "Inf", std::numeric_limits::infinity() }, { "INF", std::numeric_limits::infinity() }, { "-inf", -std::numeric_limits::infinity() }, { "-Inf", -std::numeric_limits::infinity() }, { "-INF", -std::numeric_limits::infinity() }, }; for (auto const& test : test_cases) { double got = TestAtofPrecise(test.data, test.expected, false); EXPECT_EQ(LightGBM::Common::Sign(test.expected), LightGBM::Common::Sign(got)) << "sign differs parsing: " << test.data; EXPECT_TRUE(std::isinf(got)) << "not parsed as infinite: " << test.data; EXPECT_EQ(memcmp(&got, &test.expected, sizeof(test.expected)), 0) << "parsed infinite is not the same for every bit: " << test.data; } }