Округление двойных значений в C ++, как это делает MS Excel - PullRequest
3 голосов
/ 06 января 2012

Я искал по всей сети, но не смог найти решение своей проблемы.Я просто хочу функцию, которая округляет двойные значения, как MS Excel.Вот мой код:

#include <iostream>
#include "math.h"

using namespace std;

double Round(double value, int precision) {
    return floor(((value * pow(10.0, precision)) + 0.5)) / pow(10.0, precision);
}

int main(int argc, char *argv[]) {
    /* The way MS Excel does it:
        1.27815 1.27840 ->  1.27828
        1.27813 1.27840 ->  1.27827
        1.27819 1.27843 ->  1.27831
        1.27999 1.28024 ->  1.28012
        1.27839 1.27866 ->  1.27853
    */
    cout << Round((1.27815 + 1.27840)/2, 5) << "\n"; // *
    cout << Round((1.27813 + 1.27840)/2, 5) << "\n";
    cout << Round((1.27819 + 1.27843)/2, 5) << "\n";
    cout << Round((1.27999 + 1.28024)/2, 5) << "\n"; // *
    cout << Round((1.27839 + 1.27866)/2, 5) << "\n"; // *

    if(Round((1.27815 + 1.27840)/2, 5) == 1.27828) {
                      cout << "Hurray...\n";
    }
    system("PAUSE");
    return EXIT_SUCCESS;
}

Я нашел здесь функцию в stackoverflow, в ответе говорится, что она работает как встроенная процедура округления в Excel, но это не так.Не могли бы вы сказать мне, что мне не хватает?

Ответы [ 5 ]

2 голосов
/ 06 января 2012

В некотором смысле то, что вы запрашиваете, невозможно:

Значения с плавающей запятой на большинстве распространенных платформ не имеют понятия «количество десятичных разрядов».Числа вроде 2.3 или 8.71 просто не могут быть представлены точно.Следовательно, нет смысла запрашивать какую-либо функцию, которая будет возвращать значение с плавающей запятой с заданным числом ненулевых десятичных разрядов - такие числа просто не существуют .

.единственное, что вы можете сделать с типами с плавающей запятой, это вычислить ближайшее представимое приближение, а затем распечатать результат с требуемой точностью, что даст вам текстовую форму числа, которое вы хотите.Чтобы вычислить представление, вы можете сделать это:

double round(double x, int n)
{
    int e;
    double d;

    std::frexp(x, &e);

    if (e >= 0) return x; // number is an integer, nothing to do

    double const f = std::pow(10.0, n);
    std::modf(x * f, &d);                 // d == integral part of 10^n * x

    return d / f;
}

(Вы также можете использовать modf вместо frexp, чтобы определить, является ли x целым числом. Вы также должны проверить, что n является неотрицательным, или иначе определяет семантику для отрицательной «точности».)


В качестве альтернативы использованию плавающих типов точек, вы можете выполнить фиксированная точечная арифметика.То есть вы храните все как целых чисел , но вы рассматриваете их как единицы, скажем, 1/1000.Затем вы могли бы напечатать такое число следующим образом:

 std::cout << n / 1000 << "." << n % 1000;

Сложение работает, как и ожидалось, хотя вы должны написать свою собственную функцию умножения.

2 голосов
/ 06 января 2012

Для сравнения двойных значений необходимо указать диапазон сравнения, в котором результат можно считать «безопасным».Вы можете использовать макрос для этого.

Вот один пример того, что вы можете использовать:

#define COMPARE( A, B, PRECISION ) ( ( A >= B - PRECISION ) && ( A <= B + PRECISION ) ) 

int main()
{
  double a = 12.34567;
  bool equal = COMPARE( a, 12.34567F, 0.0002 );

  equal = COMPARE( a, 15.34567F, 0.0002 );
  return 0;
}
1 голос
/ 07 января 2012

Спасибо всем за ответы!После рассмотрения возможных решений я изменил исходную функцию Round () в своем коде, добавив к значению 0,6 вместо 0,5.

Значение «127827,5» (я понимаю, что это не точное представление!)становится «127828.1» и, наконец, через floor () и деление становится «1.27828» (или что-то более похожее на 1.2782800..001).Используя COMPARE, предложенный Ренаном Грайнертом, с правильно выбранной точностью, я могу смело сравнивать значения сейчас.

Вот окончательный вариант:

#include <iostream>
#include "math.h"
#define COMPARE(A, B, PRECISION) ((A >= B-PRECISION) && (A <= B+PRECISION))

using namespace std;

double Round(double value, int precision) {
    return floor(value * pow(10.0, precision) + 0.6) / pow(10.0, precision);
}
int main(int argc, char *argv[]) {
    /* The way MS Excel does it:
        1.27815 1.27840 // 1.27828
        1.27813 1.27840 ->  1.27827
        1.27819 1.27843 ->  1.27831
        1.27999 1.28024 ->  1.28012
        1.27839 1.27866 ->  1.27853
    */
    cout << Round((1.27815 + 1.27840)/2, 5) << "\n"; 
    cout << Round((1.27813 + 1.27840)/2, 5) << "\n";
    cout << Round((1.27819 + 1.27843)/2, 5) << "\n";
    cout << Round((1.27999 + 1.28024)/2, 5) << "\n";
    cout << Round((1.27839 + 1.27866)/2, 5) << "\n";

    //Comparing the rounded value against a fixed one
    if(COMPARE(Round((1.27815 + 1.27840)/2, 5), 1.27828, 0.000001)) {
       cout << "Hurray!\n";
    }
    //Comparing two rounded values
    if(COMPARE(Round((1.27815 + 1.27840)/2, 5), Round((1.27814 + 1.27841)/2, 5), 0.000001)) {
       cout << "Hurray!\n";
    }    

    system("PAUSE");
    return EXIT_SUCCESS;
}

Я проверил его, округлив сто двойныхзначения и чем сравнивать результаты с тем, что дает Excel.Они были все одинаковы.

0 голосов
/ 06 января 2012

Вы должны сделать математику без десятичной части, чтобы получить эти числа ... так что-то вроде этого.

double dPow = pow(10.0, 5.0);

    double a = 1.27815;
    double b = 1.27840;


    double a2 = 1.27815 * dPow;
    double b2 = 1.27840 * dPow;

    double c = (a2 + b2) / 2 + 0.5;

Использование вашей функции ...

double c = (Round(a) + Round(b)) / 2 + 0.5;
0 голосов
/ 06 января 2012

Боюсь, ответ заключается в том, что Раунд не может творить магию.
Поскольку 1.27828 не может быть точно представлен как двойной, вы не можете сравнить некоторый двойной с 1.27828 и надеетесь, что он совпадет.

...