使用setprecision保证默认输出格式

时间:2017-08-16 22:29:24

标签: c++ floating-point

我想要以尽可能无损的方式存储在字符串中的double,但存储格式的最大字符串长度为16,这使得这不可能(因为std::numeric_limits<double>::max_digits10表示我们需要17这个精度)。因此,考虑到字符-.的可能性,我们可能会将其限制为14或15,如下所示:

#include <sstream>
#include <limits>
#include <iomanip>

template <typename T>
std::string format(T val)
{
  int precision = std::min(std::numeric_limits<T>::max_digits10, 
    val >= 0.0 ? 15 : 14);
  std::ostringstream oss;
  oss.precision(precision);
  oss << val;
  return oss.str();
}

但是,使用默认格式,输出可能是科学记数法,结果可能仍然超出边界。例如,此输入:

7.105427357601002e-14

在产生足够短的输出之前,需要将精度降低到12:

7.1054273576e-14

那么,如果结果太长,我如何计算将输出保持在我的长度约束内的最大精度而不采用强力方法,例如以较低的精度重试?

1 个答案:

答案 0 :(得分:0)

正如@bsruth所指出的那样,默认格式相当于%g的{​​{1}},并且使用此行为指定:

  

如果非零,则P等于精度,如果未指定精度则设为6,如果精度为0,则设为1。
  然后,如果具有样式E的转换将具有指数X:

     
    

如果P> X≥-4,转换为f或F型,精度为P-1-X       否则,转换采用样式e或E和精度P - 1.

  

因此,我们用科学计数法计算base10指数,并根据需要使用它来调整精度:

fprintf

一些test cases,显示输入,精度,长度和输出:

#include <cmath>
#include <limits>
#include <string>
#include <sstream>

double frexp10_scientific(double arg, int* exp)
{
   *exp = (arg == 0) ? 0 : static_cast<int>(std::log10(std::fabs(arg)));
   double fraction = arg * pow(10 , -(*exp));
   // adjust to scientific format decomposition
   if (std::fabs(fraction) < 1.0) {
       *exp -= 1;
       fraction *= 10;
   }
   return fraction;
}

template <typename T>
int precision_needed(T val)
{
  // initial guess on precision
  int precision;
  if (val >= 0.0) {
    if (val >= 1e15 && val < 1e16)
      precision = 16; // all integer digits used
    else
      precision = 15; // '.' present
  } else {
    if (val > -1e15 && val <= -1e14)
      precision = 15; // '-' present, all integer digits used
    else
      precision = 14; // '-' and '.' present
  }

  int exp;
  frexp10_scientific(val, &exp);

  // adjust for scientific notation if needed
  if (exp < -99) {
    precision -= 5; // "e-123"
  } else if (exp < -4) {
    precision -= 4; // "e-01"
  } else if (exp >= precision) {
    if (exp > 99) {
      precision -= 5; // "e+123"
    } else {
      precision -= 4; // "e+12"
    }
  }

  return std::min(precision, std::numeric_limits<T>::max_digits10);
}

template <typename T>
std::string format(T val) {
  std::ostringstream oss;
  oss.precision(precision_needed(val));
  oss << val;
  return oss.str();
}