diff --git a/Documentation/release/dev/vtkNumberToString-deprecation.md b/Documentation/release/dev/vtkNumberToString-deprecation.md new file mode 100644 index 0000000000000000000000000000000000000000..c09f081a16572eb7b7906235c0822078211e13fa --- /dev/null +++ b/Documentation/release/dev/vtkNumberToString-deprecation.md @@ -0,0 +1,47 @@ +## vtkNumberToString operator() deprecation + +In order to introduce more flexibility in vtkNumberToString, +the vtkNumberToString () operator has been deprecated and +the usage as change. + +Previous usage: + +``` +vtkNumberToString convert; +std::cout<<convert(val)<<std::endl; +``` + +New usage: + +``` +vtkNumberToString converter; +std::cout<<converter.Convert(val)<<std::endl; +``` + +## vtkNumberToString lowExponent and highExponent parameters + +vtkNumberToString now lets users specify a lowExponent and highExponent in +order to specify when scientific or fixed notation should be used. + +eg: + +``` +vtkNumberToString converter; +converter.SetHighExponent(6); +std::cout<<converter.Convert(1e7)<<std::endl; +converter.SetHighExponent(7); +std::cout<<converter.Convert(1e7)<<std::endl; +converter.SetLowExponent(-6); +std::cout<<converter.Convert(1e-7)<<std::endl; +converter.SetLowExponent(-7); +std::cout<<converter.Convert(1e-7)<<std::endl; +``` + +Will output: + +``` +1e7 +10000000 +1e-7 +0.0000001 +``` diff --git a/IO/Core/Testing/Cxx/TestNumberToString.cxx b/IO/Core/Testing/Cxx/TestNumberToString.cxx index ef82469d08f5065c17740fe4d9f934894e0654d2..91cfcd1751a25c06357771a65f2d1bc98efbb52e 100644 --- a/IO/Core/Testing/Cxx/TestNumberToString.cxx +++ b/IO/Core/Testing/Cxx/TestNumberToString.cxx @@ -23,7 +23,9 @@ namespace { template <typename T> -int TestConvert(unsigned int samples); +int TestConvertPrecision(unsigned int samples); +template <typename T> +int TestConvertLowHigh(unsigned int samples); template <typename T> int ConvertNumericLimitsValue(const char* t, T); } @@ -71,31 +73,33 @@ int TestNumberToString(int, char*[]) } unsigned int samples = 10000; - if (TestConvert<float>(samples) || TestConvert<double>(samples)) + if (TestConvertPrecision<float>(samples) || TestConvertPrecision<double>(samples)) { return EXIT_FAILURE; } - else + + if (TestConvertLowHigh<float>(samples) || TestConvertLowHigh<double>(samples)) { - return EXIT_SUCCESS; + return EXIT_FAILURE; } + + return EXIT_SUCCESS; } namespace { template <typename T> -int TestConvert(unsigned int samples) +int TestConvertPrecision(unsigned int samples) { std::cout << "Testing type: " << typeid(T).name() << std::endl; + vtkNumberToString converter; for (int p = 5; p < 20; ++p) { - unsigned int matches = 0; unsigned int mismatches = 0; // Now convert numbers to strings. Read the strings as floats and doubles // and compare the results with the original values. - vtkNumberToString convert; { vtkSmartPointer<vtkMinimalStandardRandomSequence> randomSequence = vtkSmartPointer<vtkMinimalStandardRandomSequence>::New(); @@ -104,7 +108,7 @@ int TestConvert(unsigned int samples) randomSequence->Next(); T value = randomSequence->GetRangeValue(-1.0, 1.0); std::stringstream convertedStr; - convertedStr << convert(value); + convertedStr << converter.Convert(value); std::stringstream rawStr; rawStr << std::setprecision(p) << value; @@ -141,15 +145,50 @@ int TestConvert(unsigned int samples) return EXIT_SUCCESS; } +template <typename T> +int TestConvertLowHigh(unsigned int samples) +{ + // Now convert numbers to strings. Read the strings as floats and doubles + // and compare the results with the original values. + for (int iLow = -20; iLow <= 0; iLow++) + { + for (int iHigh = 0; iHigh <= 20; iHigh++) + { + std::cout << "Testing low exponent: " << iLow << ", high exponent: " << iHigh << "." + << std::endl; + vtkNumberToString converter; + converter.SetLowExponent(iLow); + converter.SetHighExponent(iHigh); + vtkSmartPointer<vtkMinimalStandardRandomSequence> randomSequence = + vtkSmartPointer<vtkMinimalStandardRandomSequence>::New(); + for (unsigned int i = 0; i < samples; ++i) + { + randomSequence->Next(); + T value = randomSequence->GetRangeValue( + std::numeric_limits<T>::min(), std::numeric_limits<T>::max()); + std::string str = converter.Convert(value); + T convertedValue = std::stod(str); + if (convertedValue != value) + { + std::cout << "ERROR: " << value << " != " << convertedValue << std::endl; + return EXIT_FAILURE; + } + } + } + } + + return EXIT_SUCCESS; +} + template <typename T> int ConvertNumericLimitsValue(const char* t, T) { - vtkNumberToString convert; + vtkNumberToString converter; int status = EXIT_SUCCESS; { T value = std::numeric_limits<T>::max(); std::stringstream convertedStr; - convertedStr << convert(value); + convertedStr << converter.Convert(value); std::istringstream convertedStream(convertedStr.str()); std::cout << t << "(max) " << "raw: " << value << " converted: " << convertedStream.str() << std::endl; @@ -165,7 +204,7 @@ int ConvertNumericLimitsValue(const char* t, T) { T value = std::numeric_limits<T>::min(); std::stringstream convertedStr; - convertedStr << convert(value); + convertedStr << converter.Convert(value); std::istringstream convertedStream(convertedStr.str()); std::cout << t << "(min) " << "raw: " << value << " converted: " << convertedStream.str() << std::endl; @@ -181,7 +220,7 @@ int ConvertNumericLimitsValue(const char* t, T) { T value = std::numeric_limits<T>::lowest(); std::stringstream convertedStr; - convertedStr << convert(value); + convertedStr << converter.Convert(value); std::istringstream convertedStream(convertedStr.str()); std::cout << t << "(lowest) " << "raw: " << value << " converted: " << convertedStream.str() << std::endl; @@ -197,7 +236,7 @@ int ConvertNumericLimitsValue(const char* t, T) { T value = std::numeric_limits<T>::epsilon(); std::stringstream convertedStr; - convertedStr << convert(value); + convertedStr << converter.Convert(value); std::istringstream convertedStream(convertedStr.str()); std::cout << t << "(epsilon) " << "raw: " << value << " converted: " << convertedStream.str() << std::endl; diff --git a/IO/Core/vtkNumberToString.cxx b/IO/Core/vtkNumberToString.cxx index 20dad560d9c9f366269036d48bb52844d4bc8d01..7b164e86d58a422a880fdfa0466bfa0b78a13ca6 100644 --- a/IO/Core/vtkNumberToString.cxx +++ b/IO/Core/vtkNumberToString.cxx @@ -19,21 +19,18 @@ #include VTK_DOUBLECONVERSION_HEADER(double-conversion.h) // clang-format on +#include <array> #include <sstream> VTK_ABI_NAMESPACE_BEGIN + namespace { template <typename TagT> inline ostream& ToString(ostream& stream, const TagT& tag) { - char buf[256]; - const double_conversion::DoubleToStringConverter& converter = - double_conversion::DoubleToStringConverter::EcmaScriptConverter(); - double_conversion::StringBuilder builder(buf, sizeof(buf)); - builder.Reset(); - converter.ToShortest(tag.Value, &builder); - stream << builder.Finalize(); + vtkNumberToString converter; + stream << converter.Convert(tag.Value); return stream; } } @@ -49,4 +46,62 @@ ostream& operator<<(ostream& stream, const vtkNumberToString::TagFloat& tag) { return ToString(stream, tag); } + +//------------------------------------------------------------------------------ +void vtkNumberToString::SetLowExponent(int lowExponent) +{ + this->LowExponent = lowExponent; +} + +//------------------------------------------------------------------------------ +int vtkNumberToString::GetLowExponent() +{ + return this->LowExponent; +} + +//------------------------------------------------------------------------------ +void vtkNumberToString::SetHighExponent(int highExponent) +{ + this->HighExponent = highExponent; +} + +//------------------------------------------------------------------------------ +int vtkNumberToString::GetHighExponent() +{ + return this->HighExponent; +} + +//------------------------------------------------------------------------------ +std::string vtkNumberToString::Convert(double val) +{ + // Copied from double-conversion::EcmaScriptConverter + // the last two arguments of the constructor have no effect with ToShortest + constexpr int flags = double_conversion::DoubleToStringConverter::UNIQUE_ZERO | + double_conversion::DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN; + double_conversion::DoubleToStringConverter converter( + flags, "Infinity", "NaN", 'e', this->LowExponent, this->HighExponent + 1, 6, 0); + + std::array<char, 256> buf; + double_conversion::StringBuilder builder(buf.data(), static_cast<int>(buf.size())); + builder.Reset(); + converter.ToShortest(val, &builder); + return builder.Finalize(); +} + +std::string vtkNumberToString::Convert(float val) +{ + // Copied from double-conversion::EcmaScriptConverter + // the last two arguments of the constructor have no effect with ToShortest + constexpr int flags = double_conversion::DoubleToStringConverter::UNIQUE_ZERO | + double_conversion::DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN; + double_conversion::DoubleToStringConverter converter( + flags, "Infinity", "NaN", 'e', this->LowExponent, this->HighExponent + 1, 6, 0); + + std::array<char, 256> buf; + double_conversion::StringBuilder builder(buf.data(), static_cast<int>(buf.size())); + builder.Reset(); + converter.ToShortestSingle(val, &builder); + return builder.Finalize(); +} + VTK_ABI_NAMESPACE_END diff --git a/IO/Core/vtkNumberToString.h b/IO/Core/vtkNumberToString.h index 4aff3556c4cdbe9f25d4ba2437ccea123cf85704..504385fd6bbff74b197bf7c0397a9e382fc3c4cb 100644 --- a/IO/Core/vtkNumberToString.h +++ b/IO/Core/vtkNumberToString.h @@ -16,22 +16,36 @@ * @class vtkNumberToString * @brief Convert floating and fixed point numbers to strings * - * This class uses the double-conversion library to convert floating point and - * fixed point numbers to ASCII versions that are represented without - * numerical precision errors. + * This class uses the double-conversion library to convert float and double + * numbers to std::string without numerical precision errors. + * It is possible to specify the low and high exponent where the string representation + * will switch to scientific notation instead of fixed point notation. + * + * For other types, this class rely on std::to_string. * * Typical use: * * @code{cpp} * #include "vtkNumberToString.h" - * vtkNumberToString convert; * float a = 1.0f/3.0f; - * std::cout << convert(a) << std::endl; + * vtkNumberToString converter; + * std::cout << converter.Convert(a) << std::endl; * @endcode + * + * @code{cpp} + * #include "vtkNumberToString.h" + * double a = 1e7*vtkMath::PI(); + * vtkNumberToString converter; + * converter.SetLowExponent(-6); + * converter.SetHighExponent(6); + * std::cout << converter.Convert(a) << std::endl; + * @endcode + */ #ifndef vtkNumberToString_h #define vtkNumberToString_h +#include "vtkDeprecation.h" // For VTK_DEPRECATED_IN_9_3_0 #include "vtkIOCoreModule.h" // For export macro #include "vtkTypeTraits.h" @@ -42,6 +56,45 @@ VTK_ABI_NAMESPACE_BEGIN class VTKIOCORE_EXPORT vtkNumberToString { public: + ///@{ + /** + * Set/Get the LowExponent for string conversion. + * It correspond to the closest to zero exponent value that + * will use fixed point notation in the returned string instead of a scientific notation. + * eg: + * LowExponent = 6, 1e-6 -> "0.000001" + * LowExponent = 5, 1e-6 -> "1e-6" + */ + void SetLowExponent(int lowExponent); + int GetLowExponent(); + ///@} + + ///@{ + /** + * Set/Get the HighExponent for string conversion. + * HighExponent correspond to the highest exponent value that + * will use fixed point notation in the returned string instead of a scientific notation. + * HighExponent = 6, 1e6 -> "1000000" + * HighExponent = 5, 1e6 -> "1e6" + */ + void SetHighExponent(int highExponent); + int GetHighExponent(); + + ///@{ + /** + * Convert a number to an accurate string representation of that number. + * A templated generic implementation is provided, which rely on std::to_string for types + * other than double or float. + */ + std::string Convert(double val); + std::string Convert(float val); + template <typename T> + std::string Convert(const T& val) + { + return std::to_string(val); + } + ///@} + struct TagDouble { double Value; @@ -65,8 +118,14 @@ public: { return val; } + VTK_DEPRECATED_IN_9_3_0("Use vtkNumberToString::Convert instead.") TagDouble operator()(const double& val) const { return TagDouble(val); } + VTK_DEPRECATED_IN_9_3_0("Use vtkNumberToString::Convert instead.") TagFloat operator()(const float& val) const { return TagFloat(val); } + +private: + int LowExponent = -6; + int HighExponent = 20; }; VTKIOCORE_EXPORT ostream& operator<<(ostream& stream, const vtkNumberToString::TagDouble& tag); diff --git a/IO/Export/vtkOBJExporter.cxx b/IO/Export/vtkOBJExporter.cxx index 618c83b6f5f5eeacc4d86b9b8a63dc2a526a68cc..bc94c5f24e28de58997859adbf02a9ed0d48841a 100644 --- a/IO/Export/vtkOBJExporter.cxx +++ b/IO/Export/vtkOBJExporter.cxx @@ -194,23 +194,23 @@ void vtkOBJExporter::WriteAnActor( { return; } - vtkNumberToString convert; double temp; + vtkNumberToString converter; fpMtl << "newmtl mtl" << idStart << "\n"; tempd = prop->GetAmbientColor(); temp = prop->GetAmbient(); - fpMtl << "Ka " << convert(temp * tempd[0]) << " " << convert(temp * tempd[1]) << " " - << convert(temp * tempd[2]) << "\n"; + fpMtl << "Ka " << converter.Convert(temp * tempd[0]) << " " << converter.Convert(temp * tempd[1]) + << " " << converter.Convert(temp * tempd[2]) << "\n"; tempd = prop->GetDiffuseColor(); temp = prop->GetDiffuse(); - fpMtl << "Kd " << convert(temp * tempd[0]) << " " << convert(temp * tempd[1]) << " " - << convert(temp * tempd[2]) << "\n"; + fpMtl << "Kd " << converter.Convert(temp * tempd[0]) << " " << converter.Convert(temp * tempd[1]) + << " " << converter.Convert(temp * tempd[2]) << "\n"; tempd = prop->GetSpecularColor(); temp = prop->GetSpecular(); - fpMtl << "Ks " << convert(temp * tempd[0]) << " " << convert(temp * tempd[1]) << " " - << convert(temp * tempd[2]) << "\n"; - fpMtl << "Ns " << convert(prop->GetSpecularPower()) << "\n"; - fpMtl << "Tr " << convert(prop->GetOpacity()) << "\n"; + fpMtl << "Ks " << converter.Convert(temp * tempd[0]) << " " << converter.Convert(temp * tempd[1]) + << " " << converter.Convert(temp * tempd[2]) << "\n"; + fpMtl << "Ns " << converter.Convert(prop->GetSpecularPower()) << "\n"; + fpMtl << "Tr " << converter.Convert(prop->GetOpacity()) << "\n"; fpMtl << "illum 3\n"; // Actor has the texture @@ -267,7 +267,8 @@ void vtkOBJExporter::WriteAnActor( for (i = 0; i < points->GetNumberOfPoints(); i++) { p = points->GetPoint(i); - fpObj << "v " << convert(p[0]) << " " << convert(p[1]) << " " << convert(p[2]) << "\n"; + fpObj << "v " << converter.Convert(p[0]) << " " << converter.Convert(p[1]) << " " + << converter.Convert(p[2]) << "\n"; } idNext = idStart + static_cast<int>(points->GetNumberOfPoints()); points->Delete(); @@ -282,7 +283,8 @@ void vtkOBJExporter::WriteAnActor( for (i = 0; i < normals->GetNumberOfTuples(); i++) { p = normals->GetTuple(i); - fpObj << "vn " << convert(p[0]) << " " << convert(p[1]) << " " << convert(p[2]) << "\n"; + fpObj << "vn " << converter.Convert(p[0]) << " " << converter.Convert(p[1]) << " " + << converter.Convert(p[2]) << "\n"; } } @@ -292,7 +294,8 @@ void vtkOBJExporter::WriteAnActor( for (i = 0; i < tcoords->GetNumberOfTuples(); i++) { p = tcoords->GetTuple(i); - fpObj << "vt " << convert(p[0]) << " " << convert(p[1]) << " " << 0.0 << "\n"; + fpObj << "vt " << converter.Convert(p[0]) << " " << converter.Convert(p[1]) << " " << 0.0 + << "\n"; } } diff --git a/IO/Geometry/Testing/Cxx/TestOBJPolyDataWriter.cxx b/IO/Geometry/Testing/Cxx/TestOBJPolyDataWriter.cxx index 146afdf199de6dad14c3b1d00f07ae02b89eea86..a13cdf815b4c99ae35df747de9de0b06ca83fdde 100644 --- a/IO/Geometry/Testing/Cxx/TestOBJPolyDataWriter.cxx +++ b/IO/Geometry/Testing/Cxx/TestOBJPolyDataWriter.cxx @@ -87,10 +87,10 @@ int TestOBJPolyDataWriter(int argc, char* argv[]) } // check values - vtkNumberToString convert; int numberOfDifferentPoints = 0; int numberOfDifferentNormals = 0; int numberOfDifferentTCoords = 0; + vtkNumberToString converter; for (vtkIdType i = 0; i < polyInput->GetNumberOfPoints(); i++) { double pi[3], po[3]; @@ -101,10 +101,10 @@ int TestOBJPolyDataWriter(int argc, char* argv[]) if (vtkMath::Distance2BetweenPoints(pi, po) > 0.0) { cerr << "Point is different.\n"; - cerr << " Input: " << convert(pi[0]) << " " << convert(pi[1]) << " " << convert(pi[2]) - << "\n"; - cerr << " Output: " << convert(po[0]) << " " << convert(po[1]) << " " << convert(po[2]) - << "\n"; + cerr << " Input: " << converter.Convert(pi[0]) << " " << converter.Convert(pi[1]) << " " + << converter.Convert(pi[2]) << "\n"; + cerr << " Output: " << converter.Convert(po[0]) << " " << converter.Convert(po[1]) << " " + << converter.Convert(po[2]) << "\n"; numberOfDifferentPoints++; } @@ -114,10 +114,10 @@ int TestOBJPolyDataWriter(int argc, char* argv[]) if (vtkMath::AngleBetweenVectors(pi, po) > 0) { cerr << "Normal is different:\n"; - cerr << " Input: " << convert(pi[0]) << " " << convert(pi[1]) << " " << convert(pi[2]) - << "\n"; - cerr << " Output: " << convert(po[0]) << " " << convert(po[1]) << " " << convert(po[2]) - << "\n"; + cerr << " Input: " << converter.Convert(pi[0]) << " " << converter.Convert(pi[1]) << " " + << converter.Convert(pi[2]) << "\n"; + cerr << " Output: " << converter.Convert(po[0]) << " " << converter.Convert(po[1]) << " " + << converter.Convert(po[2]) << "\n"; numberOfDifferentNormals++; } @@ -128,8 +128,8 @@ int TestOBJPolyDataWriter(int argc, char* argv[]) if (vtkMath::Distance2BetweenPoints(pi, po) > 0.0) { cerr << "Texture coord is different:\n"; - cerr << "Input: " << convert(pi[0]) << " " << convert(pi[1]) << "\n"; - cerr << "Output: " << convert(po[0]) << " " << convert(po[1]) << "\n"; + cerr << "Input: " << converter.Convert(pi[0]) << " " << converter.Convert(pi[1]) << "\n"; + cerr << "Output: " << converter.Convert(po[0]) << " " << converter.Convert(po[1]) << "\n"; numberOfDifferentTCoords++; } } diff --git a/IO/Geometry/vtkOBJWriter.cxx b/IO/Geometry/vtkOBJWriter.cxx index 1d2fb3bddda22e5a6a099f2b83b106549136f5ee..68d8e5ec777393bc51faa0c5f00e94ac7b83d947 100644 --- a/IO/Geometry/vtkOBJWriter.cxx +++ b/IO/Geometry/vtkOBJWriter.cxx @@ -96,15 +96,16 @@ struct EndIndex void WritePoints(std::ostream& f, vtkPoints* pts, vtkDataArray* normals, const std::vector<vtkDataArray*>& tcoordsArray, std::vector<EndIndex>* endIndexes) { - vtkNumberToString convert; vtkIdType nbPts = pts->GetNumberOfPoints(); // Positions + vtkNumberToString converter; for (vtkIdType i = 0; i < nbPts; i++) { double p[3]; pts->GetPoint(i, p); - f << "v " << convert(p[0]) << " " << convert(p[1]) << " " << convert(p[2]) << "\n"; + f << "v " << converter.Convert(p[0]) << " " << converter.Convert(p[1]) << " " + << converter.Convert(p[2]) << "\n"; } // Normals @@ -114,7 +115,8 @@ void WritePoints(std::ostream& f, vtkPoints* pts, vtkDataArray* normals, { double p[3]; normals->GetTuple(i, p); - f << "vn " << convert(p[0]) << " " << convert(p[1]) << " " << convert(p[2]) << "\n"; + f << "vn " << converter.Convert(p[0]) << " " << converter.Convert(p[1]) << " " + << converter.Convert(p[2]) << "\n"; } } @@ -135,7 +137,7 @@ void WritePoints(std::ostream& f, vtkPoints* pts, vtkDataArray* normals, tcoords->GetTuple(i, p); if (p[0] != -1.0) { - f << "vt " << convert(p[0]) << " " << convert(p[1]) << "\n"; + f << "vt " << converter.Convert(p[0]) << " " << converter.Convert(p[1]) << "\n"; ++vtEndIndex; pointEndIndex = i + 1; } diff --git a/IO/XML/vtkXMLWriter.cxx b/IO/XML/vtkXMLWriter.cxx index 0edcc6064a7a286b57e493fa5a15ff968c6c5fa2..8feda99876f0d81eb4897174df657c034f0f0c02 100644 --- a/IO/XML/vtkXMLWriter.cxx +++ b/IO/XML/vtkXMLWriter.cxx @@ -1593,14 +1593,14 @@ const char* vtkXMLWriter::GetWordTypeName(int dataType) template <class T> int vtkXMLWriterWriteVectorAttribute(ostream& os, const char* name, int length, T* data) { - vtkNumberToString convert; + vtkNumberToString converter; os << " " << name << "=\""; if (length) { - os << convert(data[0]); + os << converter.Convert(data[0]); for (int i = 1; i < length; ++i) { - os << " " << convert(data[i]); + os << " " << converter.Convert(data[i]); } } os << "\""; @@ -1896,8 +1896,8 @@ bool vtkXMLWriter::WriteInformation(vtkInformation* info, vtkIndent indent) template <class T> inline ostream& vtkXMLWriteAsciiValue(ostream& os, const T& value) { - vtkNumberToString convert; - os << convert(value); + vtkNumberToString converter; + os << converter.Convert(value); return os; }