diff --git a/Modules/Core/Common/include/itkNumberToString.h b/Modules/Core/Common/include/itkNumberToString.h index 82cfb5800a1..bf7c1cd685a 100644 --- a/Modules/Core/Common/include/itkNumberToString.h +++ b/Modules/Core/Common/include/itkNumberToString.h @@ -43,16 +43,16 @@ class ITK_TEMPLATE_EXPORT NumberToString { public: std::string - operator()(TValue val); + operator()(TValue val) const; }; // declaration of specialization template <> ITKCommon_EXPORT std::string -NumberToString::operator()(double val); +NumberToString::operator()(double val) const; template <> ITKCommon_EXPORT std::string -NumberToString::operator()(float val); +NumberToString::operator()(float val) const; } // namespace itk diff --git a/Modules/Core/Common/include/itkNumberToString.hxx b/Modules/Core/Common/include/itkNumberToString.hxx index f935126257b..b0a59472cbe 100644 --- a/Modules/Core/Common/include/itkNumberToString.hxx +++ b/Modules/Core/Common/include/itkNumberToString.hxx @@ -28,7 +28,7 @@ namespace itk template std::string -NumberToString::operator()(TValue val) +NumberToString::operator()(TValue val) const { std::ostringstream output; output << static_cast::PrintType>(val); diff --git a/Modules/Core/Common/src/itkNumberToString.cxx b/Modules/Core/Common/src/itkNumberToString.cxx index 36a15fdb5e2..a8a03bd6144 100644 --- a/Modules/Core/Common/src/itkNumberToString.cxx +++ b/Modules/Core/Common/src/itkNumberToString.cxx @@ -26,7 +26,7 @@ namespace itk template <> std::string -NumberToString::operator()(double val) +NumberToString::operator()(double val) const { char buf[256]; const double_conversion::DoubleToStringConverter & converter = @@ -42,7 +42,7 @@ NumberToString::operator()(double val) template <> std::string -NumberToString::operator()(float val) +NumberToString::operator()(float val) const { char buf[256]; const double_conversion::DoubleToStringConverter & converter = diff --git a/Modules/Core/Common/test/CMakeLists.txt b/Modules/Core/Common/test/CMakeLists.txt index 799b939f807..48cb56b61e5 100644 --- a/Modules/Core/Common/test/CMakeLists.txt +++ b/Modules/Core/Common/test/CMakeLists.txt @@ -617,6 +617,7 @@ set(ITKCommonGTests itkIndexRangeGTest.cxx itkMersenneTwisterRandomVariateGeneratorGTest.cxx itkNeighborhoodAllocatorGTest.cxx + itkNumberToStringGTest.cxx itkOptimizerParametersGTest.cxx itkPointGTest.cxx itkShapedImageNeighborhoodRangeGTest.cxx diff --git a/Modules/Core/Common/test/itkNumberToStringGTest.cxx b/Modules/Core/Common/test/itkNumberToStringGTest.cxx new file mode 100644 index 00000000000..1a59800a311 --- /dev/null +++ b/Modules/Core/Common/test/itkNumberToStringGTest.cxx @@ -0,0 +1,144 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * 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.txt + * + * 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. + * + *=========================================================================*/ + +// First include the header file to be tested: +#include "itkNumberToString.h" +#include + +namespace +{ + +template +void +Test_all_digits() +{ + const itk::NumberToString numberToString{}; + + for (auto i = 9; i >= 0; --i) + { + EXPECT_EQ(numberToString(i), std::to_string(i)); + } +} + + +template +void +Test_non_finite_special_floating_point_values() +{ + using NumericLimitsType = std::numeric_limits; + const itk::NumberToString numberToString{}; + + EXPECT_EQ(numberToString(NumericLimitsType::quiet_NaN()), "NaN"); + EXPECT_EQ(numberToString(NumericLimitsType::infinity()), "Infinity"); + EXPECT_EQ(numberToString(-NumericLimitsType::infinity()), "-Infinity"); +} + + +template +void +Test_round_trip_of_finite_numeric_limits() +{ + using NumericLimitsType = std::numeric_limits; + const itk::NumberToString numberToString{}; + + for (const TValue expectedValue : { NumericLimitsType::lowest(), + NumericLimitsType::epsilon(), + NumericLimitsType::round_error(), + NumericLimitsType::denorm_min(), + NumericLimitsType::min(), + NumericLimitsType::max() }) + { + std::istringstream inputStream{ numberToString(expectedValue) }; + EXPECT_FALSE(inputStream.eof()); + TValue actualValue; + inputStream >> actualValue; + EXPECT_TRUE(inputStream.eof()); + EXPECT_EQ(actualValue, expectedValue); + } +} + + +template +void +Test_decimal_notation_supports_up_to_twentyone_digits() +{ + const itk::NumberToString numberToString{}; + + for (std::int8_t exponent{ 20 }; exponent > 0; --exponent) + { + const auto power_of_ten = std::pow(TValue{ 10 }, static_cast(exponent)); + + // Test +/- 10 ^ exponent + EXPECT_EQ(numberToString(power_of_ten), '1' + std::string(exponent, '0')); + EXPECT_EQ(numberToString(-power_of_ten), "-1" + std::string(exponent, '0')); + } + + for (std::int8_t exponent{ -6 }; exponent < 0; ++exponent) + { + const auto power_of_ten = std::pow(TValue{ 10 }, static_cast(exponent)); + + // Test +/- 10 ^ exponent + EXPECT_EQ(numberToString(power_of_ten), "0." + std::string(-1 - exponent, '0') + '1'); + EXPECT_EQ(numberToString(-power_of_ten), "-0." + std::string(-1 - exponent, '0') + '1'); + } +} + +} // namespace + + +// Tests NumberToString for 0 to 9. +TEST(NumberToString, SupportsAllDigits) +{ + Test_all_digits(); + Test_all_digits(); + Test_all_digits(); +} + + +// Tests that the function object returns a unique string for both positive and negative zero. +TEST(NumberToString, HasUniqueZeroString) +{ + const std::string expectedZeroString = "0"; + + EXPECT_EQ(itk::NumberToString{}(0), expectedZeroString); + EXPECT_EQ(itk::NumberToString{}(+0.0f), expectedZeroString); + EXPECT_EQ(itk::NumberToString{}(-0.0f), expectedZeroString); + EXPECT_EQ(itk::NumberToString{}(+0.0), expectedZeroString); + EXPECT_EQ(itk::NumberToString{}(-0.0), expectedZeroString); +} + + +TEST(NumberToString, NonFiniteSpecialFloatingPointValues) +{ + Test_non_finite_special_floating_point_values(); + Test_non_finite_special_floating_point_values(); +} + + +TEST(NumberToString, RoundTripOfFiniteFloatingPointNumericLimits) +{ + Test_round_trip_of_finite_numeric_limits(); + Test_round_trip_of_finite_numeric_limits(); +} + + +TEST(NumberToString, DecimalNotationUpTo21Digits) +{ + Test_decimal_notation_supports_up_to_twentyone_digits(); + Test_decimal_notation_supports_up_to_twentyone_digits(); +}