微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

std::setprecision 设置有效数字的数量如何使用 iomanip 设置精度?

如何解决std::setprecision 设置有效数字的数量如何使用 iomanip 设置精度?

我一直发现 iomanip 令人困惑且违反直觉。我需要帮助。

快速互联网搜索发现 (https://www.vedantu.com/maths/precision)“因此,我们将精度视为十进制数中小数点后的最大有效数字位数”(重点是我的)。这也符合我的理解。但是我写了一个测试程序并且:

stm << std::setprecision(3) << 5.12345678;
std::cout << "5.12345678: " << stm.str() << std::endl;
stm.str("");

stm << std::setprecision(3) << 25.12345678;
std::cout << "25.12345678: " << stm.str() << std::endl;
stm.str("");

stm << std::setprecision(3) << 5.1;
std::cout << "5.1: " << stm.str() << std::endl;
stm.str("");

输出

5.12345678: 5.12
25.12345678: 25.1
5.1: 5.1

如果精度为 3,则输出应为:

5.12345678: 5.123
25.12345678: 25.123
5.1: 5.1

显然,C++ 标准对与浮点数相关的“精度”的含义有不同的解释。

如果我这样做:

stm.setf(std::ios::fixed,std::ios::floatfield);

然后前两个值的格式正确,但最后一个显示5.100

如何在没有填充的情况下设置精度?

解决方法

我不认为你可以做得很好。有两种候选格式:defaultfloatfixed。对于前者,“精度”是最大位数,其中小数点分隔符的两边都计数。对于后者,“精度”是小数点分隔符之后的确切位数。

所以我认为您的解决方案是使用 fixed 格式,然后手动清除尾随零:

#include <iostream>
#include <iomanip>
#include <sstream>

void print(const double number)
{
    std::ostringstream stream;
    stream << std::fixed << std::setprecision(3) << number;
    auto string=stream.str();
    while(string.back()=='0')
        string.pop_back();
    if(string.back()=='.') // in case number is integral; beware of localization issues
        string.pop_back();
    std::cout << string << "\n";
}

int main()
{
    print(5.12345678);
    print(25.12345678);
    print(5.1);
}
,

您可以尝试使用此解决方法:

decltype(std::setprecision(1)) setp(double number,int p) {
    int e = static_cast<int>(std::abs(number));
    e = e != 0? static_cast<int>(std::log10(e)) + 1 + p : p;
    while(number != 0.0 && static_cast<int>(number*=10) == 0 && e > 1) 
        e--; // for numbers like 0.001: those zeros are not treated as digits by setprecision.
    return std::setprecision(e);
}

然后:

auto v = 5.12345678;
stm << setp(v,3) << v;

另一个更冗长和优雅的解决方案是创建一个像这样的结构:

struct __setp {
    double number;
    bool fixed = false;
    int prec;
};

std::ostream& operator<<(std::ostream& os,const __setp& obj)
{
    if(obj.fixed)
        os << std::fixed;
    else os << std::defaultfloat;
    os.precision(obj.prec);
    os << obj.number; // comment this if you do not want to print immediately the number
    return os;
}

__setp setp(double number,int p) {
     __setp setter;
     setter.number = number;
     
    int e = static_cast<int>(std::abs(number));
    e = e != 0? static_cast<int>(std::log10(e)) + 1 + p : p;
    while(number != 0.0 && static_cast<int>(number*=10) == 0)
        e--; // for numbers like 0.001: those zeros are not treated as digits by setprecision.

    if(e <= 0) {
        setter.fixed = true;
        setter.prec = 1;
    } else
        setter.prec = e;
    return setter;
}

像这样使用它:

auto v = 5.12345678;
stm << setp(v,3);
,

固定格式几乎可以满足您的需求,只是它保留了尾随零。没有内置的方法可以避免这种情况,但您可以轻松地手动删除这些零。例如,在 C++20 中,您可以使用 std::format 执行以下操作:

std::string format_fixed(double d) {
  auto s = fmt::format("{:.3f}",d);
  auto end = s.find_last_not_of('0');
  return end != std::string::npos ? std::string(s.c_str(),end + 1) : s;
}

std::cout << "5.12345678: " << format_fixed(5.12345678) << "\n";
std::cout << "25.12345678: " << format_fixed(25.12345678) << "\n";
std::cout << "5.1: " << format_fixed(5.1) << "\n";

输出:

5.12345678: 5.123
25.12345678: 25.123
5.1: 5.1

the {fmt} library 相同的示例,std::format 基于:godbolt

免责声明:我是 {fmt} 和 C++20 std::format 的作者。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。