Возвращение двойного с точностью - PullRequest
1 голос
/ 10 сентября 2011

Скажем, у меня есть метод, возвращающий double, но я хочу определить точность после точки возвращаемого значения.Я не знаю значения переменной double.

Пример:

double i = 3.365737;
return i;

Я хочу, чтобы возвращаемое значение было с точностью до 3 числа после точки

Значение: возвращаемое значение 3.365.

Другой пример:

double i = 4644.322345;
return i;

Я хочу, чтобы возвращаемое значение было: 4644.322

Ответы [ 7 ]

6 голосов
/ 10 сентября 2011

То, что вы хотите, это усечение десятичных цифр после определенной цифры.Вы можете легко сделать это с помощью функции floor из <math.h> (или std::floor из <cmath>, если вы используете C ++):

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

Тем не менее, я думаю, что в некоторых случаях выможет привести к некоторым странным результатам (последняя цифра будет равна одному или другому) из-за того, как внутренне работает плавающая точка.

С другой стороны, большую часть времени вы просто пропускаете double как есть и усекаете его только при выводе его в поток, что автоматически выполняется с помощью флагов правого потока.

2 голосов
/ 10 сентября 2011

Вам нужно будет позаботиться о пограничных случаях. Любая реализация, основанная исключительно на pow и приведении или fmod, иногда будет давать неправильные результаты, особенно в реализации, основанной на pow(- PRECISION).

Самый безопасный вариант - реализовать то, что ни C, ни C ++ не предоставляют: возможность арифметики с фиксированной запятой. В отсутствие этого вам нужно будет найти соответствующие пограничные случаи. Этот вопрос похож на вопрос о том, как Excel выполняет округление. Адаптируя мой ответ, Как Excel успешно округляет плавающие числа, даже если они неточные? , к этой проблеме,

// 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;
}
1 голос
/ 10 сентября 2011

Вы не можете «убрать» точность из двойника. Вы можете иметь: 4644,322000. Это другое число, но точность одинакова.

Как сказал @David Heffernan, сделайте это, когда преобразуете его в строку для отображения.

0 голосов
/ 10 сентября 2011

Точно так же, как вы форматируете дату перед ее отображением, вы должны сделать то же самое с двойной.

Однако вот два подхода, которые я использовал для округления.

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

0 голосов
/ 10 сентября 2011

Нет хорошего способа сделать это с обычными double с, но вы можете написать 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 перед точкой + 3 после. Это использует список инициализации 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<< с помощью одной из функций усечения, заданных другими ответами.

0 голосов
/ 10 сентября 2011

Вместо умножения и деления на степени 10, как в других ответах, вы можете использовать функцию fmod, чтобы найти цифры после требуемой точности, а затем вычесть их, чтобы убрать.

#include <math.h>
#define PRECISION 0.001
double truncate(double x) {
    x -= fmod(x,PRECISION);
    return x;
}
0 голосов
/ 10 сентября 2011

Вы хотите обрезать свой двойник до n десятичных знаков, тогда вы можете использовать эту функцию:

#import <cmath>

double truncate_to_places(double d, int n) {
    return d - fmod(d, pow(10.0, -n));
}
...