【问题标题】:How to set format of std::scientific如何设置 std::scientific 的格式
【发布时间】:2021-06-24 06:17:47
【问题描述】:

我想将 12345.0 打印为 0.1235e+05。但是下面的代码给出了 1.23450e+04。有没有办法控制 std::scientific 的格式?

#include <iostream>
#include <string>
#include <sstream>


int main()
{
    double a = 12345.0;

    std::stringstream s;

    s.precision(3);

    s << std::scientific;

    s << a;

    std::cout << s.str() << std::endl;

    return 0;
}

输出是

1.235e+04

我也试过这里的代码Print exponential notation with one leading zero with C++,但是四舍五入有问题。如果我将精度设置为 4,它不会四舍五入并给出 0.1234E5 但它应该是 0.1235E5。

有什么解决办法吗?

【问题讨论】:

  • 我认为如果你想要准确性,你需要编写自己的转换器,例如见stackoverflow.com/questions/7153979/…
  • @Alan Birtles 我不想要准确度,我想要从 0.12345 到 0.1235 而不是 0.1234 的正确舍入。
  • 我确信您会得到看起来正确但在某些情况下会失败(有时非常糟糕)的答案,因为它们会引入舍入错误。我建议打印到具有所需精度的字符串,然后以文本形式将“1.235e+04”后处理为“0.1235e+05”。
  • @chux - 恢复 Monica 它是两次乘法和一次舍入的数字。没有数以百万计的重复计算来累积舍入误差。如果代码只为两次乘法提供舍入误差累积,那么没有人可以做任何事情。
  • "如果我将精度设置为 4,它不会四舍五入并给出 0.1234E5 但它应该是 0.1235E5。"是一个错误的结论。值为 12345.0;输出为“0.1234E5”。该输出肯定是四舍五入的。它只是没有朝着预期的方向转动。对于“四舍五入,与偶数相等”,“0.1234E5”是更好的答案。你期待什么舍入模式?

标签: c++ c++11 format std precision


【解决方案1】:

那么这不是科学形式。科学形式必须产生 1-10 之间的数字并乘以 10
来源:http://chemed.chem.purdue.edu/genchem/topicreview/bp/ch1/scinot.html

【讨论】:

  • 我在整本工程书籍和计算器中都看到了 0.1235E5 格式。如果它不是科学形式,那它是什么?
  • @KC_ 科学形式有多种定义。也许称之为指数形式。 IAC,“科学形式必须产生一个介于 1-10 之间的数字并乘以 10 的幂”不允许打印零。
  • @chux - 恢复莫妮卡指数形式是一个数学表达式和软件/计算器方面的函数而不是打印格式。
【解决方案2】:

double 重新发明为字符串转换的一个问题是许多极端情况。

考虑在 99995.0 到 4 位附近打印值

void foo(double value, int precision) {
  if (value == 0.) {
    printf("%g\n", 0.0);
  }

  int exponent = (int) floor(log10(fabs(value)));
  double base = value / pow(10, exponent);

  base /= 10.0;
  int p = precision;
  base = round(base * pow(10, p)) / pow(10, p);
  exponent++;

  printf("%.*fE%d\n", p, base, exponent);
  bar(value, precision);
}

foo(99994.99, 4);
foo(99995.01, 4);

打印

0.9999E5
1.0000E5

而不是希望

0.9999E5
0.1000E6

与其尝试将库中的浮点数转换为字符串,不如简单地将字符串后处理为所需的格式。

下面是 C,而 OP 正在寻找 C++,所以以此为指导。

void bar(double value, int precision) {
  precision--;
  char buf[400];
  snprintf(buf, sizeof buf, "%.*e", precision, value);

  if (isfinite(value)) {
    char *e = strchr(buf, 'e');
    char *first_digit = e - precision - 2;
    int expo = atoi(e + 1) + 1;
    printf("%.*s0.%c%.*sE%d\n", !isdigit((unsigned char )buf[0]), buf,
        *first_digit, precision, first_digit + 2, expo);
  } else {
    printf("%s\n", buf);
  }
}

bar(99994.99, 4);
bar(99995.01, 4);

打印

0.9999E5
0.1000E6

“如果我将精度设置为 4,它不会四舍五入并给出 0.1234E5 但它应该是 0.1235E5。”

这是因为默认的舍入模式是“四舍五入到最接近,平到偶数”,而 OP 希望“四舍五入到最近,平到远离”。

base = round(base * pow(10, p)) / pow(10, p); 之类的代码可能会在特定情况下实现 OP 的目标,因为此处的乘法和除法可能会由于不精确而导致舍入,有时会在所需的方向上进行。这在所有double 中并不可靠,无法实现“四舍五入”。

【讨论】:

  • 我并不想通过设置精度和修复来自己进行舍入。我让函数 round() 完成它的工作。如果你认为 base = round(base * pow(10, p)) / pow(10, p);将失败您假设函数 round() 在某些情况下会失败。我不这么认为。
  • 关于1.0000E5,没有你之前提到的舍入误差。结果是正确的。但是,我同意正确格式存在问题。解决方案很简单:应用该过程两次。我复制并粘贴了相关行并更新了解决方案。现在它给出 0.1000E6。
【解决方案3】:

我通过添加两行代码更正了链接Print exponential notation with one leading zero with C++ 中给出的解决方案中的错误。我还重复了正确格式的过程:

int p = stream.precision();
base = round(base * pow(10, p)) / pow(10, p);

它现在进行正确的舍入并给出 0.1235E5。这是整个代码:

#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <cmath>

class Double {
public:
    Double(double x) : value(x) {}
    const double value;
};

std::ostream& operator<< (std::ostream& stream, const Double& x) {
    // So that the log does not scream
    if (x.value == 0.) {
        stream << 0.0;
        return stream;
    }


    int exponent = floor(log10(std::abs(x.value)));
    double base = x.value / pow(10, exponent);
    // Transform here
    base /= 10.0;
    int p = stream.precision();  // This line is added to correct the code
    base = round(base * pow(10, p)) / pow(10, p); // This line is added to correct the code
    exponent += 1;

    double x2 = base * pow(10, exponent);

    exponent = floor(log10(std::abs(x2)));
    base = x2 / pow(10, exponent);
    // Transform here
    base /= 10.0;
    base = round(base * pow(10, p)) / pow(10, p); // This line is added to correct the code
    exponent += 1;

    stream << base << "+e" << exponent;


    return stream;
}


int main()
{
    //double a = 12345.0;
    double a = 99995.01;

    std::stringstream s;

    s.precision(4);

    s << Double(a);

    std::cout << s.str() << std::endl;

    return 0;
}

【讨论】:

  • base * pow(10, p) 和其他人引入了舍入错误。在大多数情况下,该错误可能很小,但对于接近 10 次方的值来说是灾难性的。
  • @chux - 恢复莫妮卡,我们正在谈论两个乘法。我用 pow(10,10) 乘除 0.12345 万次,得到相同的结果。你可以自己试试。
  • log10(x) 倾向于返回一个四舍五入 整数答案的x 值,只是小于 10.0 的幂。然后调用floor() 会放大问题。
  • @chux - 恢复莫妮卡 我们不是在谈论数百万重复行或 10E15 之类的东西。这是简单的计算器表达式。 10美元的计算器就能搞定。
猜你喜欢
  • 2020-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-04
  • 1970-01-01
  • 2016-03-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多