【问题标题】:Returning double with precision精确返回双精度
【发布时间】:2015-05-06 18:12:03
【问题描述】:

假设我有一个返回double 的方法,但我想确定要返回的值的点之后的精度。我不知道double 变量的值。

例子:

double i = 3.365737;
return i;

我希望返回值在点后的精度为 3 个数字

含义:返回值为3.365

另一个例子:

double i = 4644.322345;
return i;

我希望返回值为:4644.322

【问题讨论】:

  • 通常您只需将输出格式化为小数点后 3 位。这样就容易多了。
  • 3.365 没有 double。只有3.3650000000000002,所以充其量就是你会得到的。听取建议仅在打印时限制小数位数的答案。
  • @ltzik984:负数的期望行为是什么?
  • @Matteo Italia 与正数相同...
  • @Pascal:3.365 实际上是 3.3650000000000002131628207280300557613372802734375 :)

标签: c++ c double


【解决方案1】:

您想要的是在某个数字之后截断十进制数字。您可以使用来自<math.h>floor 函数轻松做到这一点(如果您使用的是C++,则可以使用<cmath> 中的std::floor):

double TruncateNumber(double In, unsigned int Digits)
{
    double f=pow(10, Digits);
    return ((int)(In*f))/f;
}

不过,我认为在某些情况下,由于浮点内部的工作方式,您可能会得到一些奇怪的结果(最后一位数字是一个过/过)。


另一方面,大多数情况下,您只需按原样传递 double 并仅在将其输出到流上时截断它,这是使用正确的流标志自动完成的。

【讨论】:

  • Floor 会对负数做错事。如果 OP 真的想截断那些额外的数字,你需要向零舍入:换句话说,强制转换为 int。
  • @David:如果他想截断,那就对了,问题是他只举了两个例子,没有一个是处理负数的。
【解决方案2】:

您将需要注意边缘情况。任何仅基于pow 和强制转换或fmod 的实现有时会给出错误的结果,尤其是基于pow(- PRECISION) 的实现。

最安全的选择是实现 C 和 C++ 都没有提供的东西:定点算术能力。缺少这一点,您将需要找到相关边界案例的表示。这个问题类似于 Excel 如何进行舍入的问题。在那里调整我的答案 How does Excel successfully Rounds Floating numbers even though they are imprecise? 来解决这个问题,

// Compute 10 to some positive integral power.
// Dealing with overflow (exponent > 308) is an exercise left to the reader.
double pow10 (unsigned int exponent) {
  double result = 1.0;
  double base = 10.0;
  while (exponent > 0) {
    if ((exponent & 1) != 0) result *= base;
    exponent >>= 1;
    base *= base;
  }
  return result;
}

// Truncate number to some precision.
// Dealing with nonsense such as nplaces=400 is an exercise left to the reader.
double truncate (double x, int nplaces) {
  bool is_neg = false;

  // Things will be easier if we only have to deal with positive numbers.
  if (x < 0.0) {
     is_neg = true;
     x = -x;
  }

  // Construct the supposedly truncated value (round down) and the nearest
  // truncated value above it.
  double round_down, round_up;
  if (nplaces < 0) {
    double scale = pow10 (-nplaces);
    round_down   = std::floor (x / scale);
    round_up     = (round_down + 1.0) * scale;
    round_down  *= scale;
  }
  else {
    double scale = pow10 (nplaces);
    round_down   = std::floor (x * scale);
    round_up     = (round_down + 1.0) / scale;
    round_down  /= scale;
  }

  // Usually the round_down value is the desired value.
  // On rare occasions it is the rounded-up value that is.
  // This is one of those cases where you do want to compare doubles by ==.
  if (x != round_up) x = round_down;

  // Correct the sign if needed.
  if (is_neg) x = -x;

  return x;
}

【讨论】:

    【解决方案3】:

    您不能从双精度中“删除”精度。你可以有:4644.322000。这是一个不同的数字,但精度是相同的。

    正如@David Heffernan 所说,当您将其转换为字符串以进行显示时执行此操作。

    【讨论】:

      【解决方案4】:

      你想将你的双精度截断到n小数位,那么你可以使用这个函数:

      #import <cmath>
      
      double truncate_to_places(double d, int n) {
          return d - fmod(d, pow(10.0, -n));
      }
      

      【讨论】:

      • 从他的第一个例子看来,他想要截断,而不是四舍五入。
      【解决方案5】:

      您可以使用fmod 函数查找所需精度之后的数字,然后减去以删除它们,而不是像其他答案那样乘以和除以 10。

      #include <math.h>
      #define PRECISION 0.001
      double truncate(double x) {
          x -= fmod(x,PRECISION);
          return x;
      }
      

      【讨论】:

      • pow() 可能是一个非常昂贵的函数。您可能希望确保编译器对其进行评估。 ;)
      • @Peter 好电话。我以为它会,但我刚刚测试过,它没有。我将更新我的帖子以仅使用0.001
      • -1。例如,这给出了 x=1.0 的错误答案。它在边缘案件上做得非常糟糕。输入为 x.xxx 的数字有超过 50-50 的机会被向下截断 1/1000。
      【解决方案6】:

      使用普通的doubles 没有好方法,但你可以写一个class 或简单的struct 之类的

      struct lim_prec_float {
        float value;
        int precision;
      };
      

      然后有你的功能

      lim_prec_float testfn() {
        double i = 3.365737;
        return lim_prec_float{i, 4};
      }
      

      (4 = 1 before point + 3 after。这使用 C++11 初始化列表,如果 lim_prec_float 是带有适当构造函数的 class 会更好。)

      当您现在想要输出变量时,请使用自定义执行此操作

      std::ostream &operator<<(std::ostream &tgt, const lim_prec_float &v) {
        std::stringstream s;
        s << std::setprecision(v.precision) << v.value;
        return (tgt << s.str());
      }
      

      现在你可以,例如,

      int main() {
        std::cout << testfn() << std::endl
                  << lim_prec_float{4644.322345, 7} << std::endl;
        return 0;
      }
      

      哪个会输出

      3.366
      4644.322
      

      这是因为std::setprecision 表示四舍五入 到所需的位数,这可能是您真正想要的。如果您实际上是指 截断,则可以使用其他答案给出的截断函数之一来修改 operator&lt;&lt;

      【讨论】:

      • 建议的答案不正确。您的函数将值四舍五入(不截断它们)。正如您在描述中看到的,3.365737 的期望结果是 3.365(不是 3.366)。
      【解决方案7】:

      与在显示日期之前格式化日期一样,你应该对双精度执行相同的操作。

      但是,这里有两种我用于舍入的方法。

      double roundTo3Places(double d) {
          return round(d * 1000) / 1000.0;
      }
      
      double roundTo3Places(double d) {
          return (long long) (d * 1000 + (d > 0 ? 0.5 : -0.5)) / 1000.0;
      }
      

      后者更快,但数字不能大于 9e15

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多