C++浮点数转字符串

huangapple go评论75阅读模式
英文:

c++ float to string

问题

C++是否有任何机制(函数或其他方式)将浮点数(或双精度浮点数)转换为既保持数字精度又保持数字长度最小的表示形式?我的意思是类似JavaScript的方式。

看起来C++似乎不愿意这样做,例如:

  • std::to_string(1.23456789e10); // 返回"12345678900.000000"(多余的零)
  • (std::ostringstream() << 1.23456789e10).str() // 返回"1.234567e10"(丢失了两位数字)
  • snprintf(buffer, sizeof(buffer), "%g", 1.23456789e10); // 与上述相同
英文:

Does C++ have ANY mechanism (function or whatever) to convert a float (or double) to the representation that maintains both precision of a number and also a minimal length of the number? I mean something like JavaScript does.

It looks like C++ is not willing to do so, for example:

  • std::to_string(1.23456789e10); // returns "12345678900.000000" (unnecessary zeros)
  • (std::ostringstream() << 1.23456789e10).str() // returns "1.234567e10" (two digits lost)
  • snprintf(buffer, sizeof(buffer), "%g", 1.23456789e10); // as above

答案1

得分: -1

The answer is not that obvious but actually Yes. Here I share the code that converts fundamental types to std::string maintaining precision and relatively short representation of float and double types.

template <typename Type>
constexpr static std::string toString(Type value) noexcept
{
    std::ostringstream oss;

    if constexpr (std::is_floating_point<typename std::remove_cv<Type>::type>::value)
        oss << std::defaultfloat << std::setprecision(std::numeric_limits<Type>::digits10 + 1);

    oss << value;
    return oss.str();
}

Now some background. The code above is based on std::ostringstream. You may think why the above implementation is better than std::to_string for example? Moreover, why not other methods as C++ provides actually a lot of different methods offering similar stuff. Ok then, lets walk through each method:

  • std::string: First of all it may produce invalid outputs. Especially for small values of float and double source. Moreover it appends lots of unnecessary zeros for example for 1.23456789e10 input the function produces "12345678900.000000".
  • snprintf with "%g" specifier. It produces either "%f" or "%e" whatever is shorter. Unfortunately reduces precision while it could be preserved. For example, the 1.23456789e10 produces "1.234567e10". Also if you'd manually specify the precision, it will either add unnecessary zeros or remove less significant digits. We would like to have something that automatically maintains the precision and short length of the string
  • std::format: Omg, I was waiting years for this function. Unfortunately, as of today only MSVC and clang implements the header and it is still missing in g++.
  • std::to_chars: Feature of C++23, not existing yet. Looks like another remake of std::to_string.
  • Any use of third party libraries - came on! For just a such purpose it would be an overkill.

std::ostringstream by itself does not keep the accuracy of a number. For example the 1.23456789e10 produces "1.234567e10". It requires to specify std::setprecision along with the std::defaultfloat to maintain the precision and relatively short (user friendly) representation. For more information please refer to the cppreference.com. And it is not that obvious in general - for more information please refer to the source.

Important note: The code is not exactly maintaining the precision as stated above. This was done on purpose. If you look carefully the std::numeric_limits<Type>::digits10 is "number of decimal digits that can be represented without change" while std::numeric_limits<Type>::max_digits10 is "number of decimal digits necessary to differentiate all values of this type". Therefore, you need by your own decide what exactly you need. In case you are presenting a number to the user the digits10 is the most "user friendly" representation (0.3f is "0.3" for float), however only max_digits10 will give you the exact representation (0.3f is "0.300000012" for float). Thank you Eric for indicating this!

英文:

The answer is not that obvious but actually Yes. Here I share the code that converts fundamental types to std::string maintaining precision and relatively short representation of float and double types.

    template &lt;typename Type&gt;
    constexpr static std::string toString(Type value) noexcept
    {
        std::ostringstream oss;

        if constexpr (std::is_floating_point&lt;typename std::remove_cv&lt;Type&gt;::type&gt;::value)
            oss &lt;&lt; std::defaultfloat &lt;&lt; std::setprecision(std::numeric_limits&lt;Type&gt;::digits10 + 1);

        oss &lt;&lt; value;
        return oss.str();
    }

Now some background. The code above is based on std::ostringstream. You may think why the above implementation is better than std::to_string for example? Moreover, why not other methods as C++ provides actually a lot of different methods offering similar stuff. Ok then, lets walk through each method:

  • std::string: First of all it may produce invalid outputs. Especially for small values of float and double source. Moreover it appends lots of unnecessary zeros for example for 1.23456789e10 input the function produces "12345678900.000000".
  • snprintf with &quot;%g&quot; specifier. It produces either &quot;%f&quot; or &quot;%e&quot; whatever is shorter. Unfortunately reduces precision while it could be preserved. For example, the 1.23456789e10 produces "1.234567e10". Also if you'd manually specify the precision, it will either add unnecessary zeros or remove less significant digits. We would like to have something that automatically maintains the precision and short length of the string
  • std::format: Omg, I was waiting years for this function. Unfortunately, as of today only MSVC and clang implements the <format> header and it is still missing in g++.
  • std::to_chars: Feature of C++23, not existing yet. Looks like another remake of std::to_string.
  • Any use of third party libraries - came on! For just a such purpose it would be an overkill.

std::ostringstream by itself does not keep the accuracy of a number. For example the 1.23456789e10 produces "1.234567e10". It requires to specify std::setprecision along with the std::defaultfloat to maintain the precision and relatively short (user friendly) representation. For more information please refer to the cppreference.com. And it is not that obvious in general - for more information please refer to the source.

Important note: The code is not exactly maintaining the precision as stated above. This was done on purpose. If you look carefully the std::numeric_limits&lt;Type&gt;::digits10 is "number of decimal digits that can be represented without change" while std::numeric_limits&lt;Type&gt;::max_digits10 is "number of decimal digits necessary to differentiate all values of this type". Therefore, you need by your own decide what exactly you need. In case you are presenting a number to the user the digits10 is the most "user friendly" representation (0.3f is "0.3" for float), however only max_digits10 will give you the exact representation (0.3f is "0.300000012" for float). Thank you Eric for indicating this!

huangapple
  • 本文由 发表于 2023年4月13日 23:41:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76007359.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定