Как написать хорошую функцию round_double в c ++? - PullRequest
1 голос
/ 14 апреля 2011

Я пытаюсь написать хорошую функцию round_double, которая округляет double с заданной точностью:

1.

double round_double(double num, int prec)
{
    for (int i = 0; i < abs(prec); ++i)
        if(prec > 0)
            num *= 10.0;
        else
            num /= 10.0;
    double result = (long long)floor(num + 0.5);
    for (int i = 0; i < abs(prec); ++i)
        if(prec > 0)
            result /= 10.0;
        else
            result *= 10.0;
    return result;
}

2.

double round_double(double num, int prec)
{
    double tmp = pow(10.0, prec);
    double result = (long long)floor(num * tmp + 0.5);
    result /= tmp;
    return result;
}

Эта функцияделай то, что я не хочу, но они, на мой взгляд, недостаточно хороши.Поскольку, начиная с точности = 13 - 14, они возвращают плохие результаты.

Причина, по которой я уверен, что можно написать хороший double_round, заключается в том, что просто печатает число через cout с заданной точностью (скажем, 18) выводит лучший результат, чем результат моей функции.

Например, эта часть кода:

int prec = 18;
double num = 10.123456789987654321;
cout << setiosflags(ios::showpoint | ios::fixed)
<< setprecision(prec) << "round_double(" << num << ", " 
<< prec << ") = " << round_double(num, prec) << endl;

будет печатать round_double(10.123456789987655000, 18) = -9.223372036854776500 для первого round_double и round_double(10.123456789987655000, 18) = -9.223372036854776500 для второго.

Как написать хорошую функцию round_double в c ++?Или уже существует?

Ответы [ 3 ]

2 голосов
/ 14 апреля 2011

Не приводите к long long, что приводит к принудительному преобразованию в целое число с ограниченным диапазоном, превышающим то, что требуется для 10 ^ 13 (хорошо 19 для 64-разрядных без целой числовой части). Достаточно просто позвонить floor.

double round_double(double num, int prec)
{
    double tmp = pow(10.0, prec);
    double result = floor(num * tmp + 0.5);
    result /= tmp;
    return result;
}

Обратите внимание, что Майк тоже прав, у вас есть ограниченный диапазон, который вы можете представить просто в двойном размере. Это не так здорово, если вам нужны чистые десятичные ответы. Но long long является причиной ваших совершенно дурацких чисел.

1 голос
/ 14 апреля 2011

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

double обычно означает 64-битное двоичное представление, как указано в IEEE754, с 52-битной дробной частью.Это дает точность приблизительно 16 десятичных цифр.

Если вам нужна более высокая точность, чем это, то, вероятно, лучше всего использовать арифметическую библиотеку произвольной точности, такую ​​как GMP .Ваш компилятор может предлагать или не предлагать тип long double с более высокой точностью, чем double.

EDIT : извините, я не заметил, что вы получаете совершенно неверные результаты,Как говорится в другом ответе, это связано с переполнением long long.

0 голосов
/ 14 апреля 2011

Другой подход заключается в округлении на основе двоичных цифр точности.Пример реализации ниже - не уверен, полезен ли он для вас, но, поскольку вы заставили меня играть, я подумал, что выложу его там.ч заголовок часто встречается на системах Linux: он может быть легко перенесен на Windows, но это, несомненно, немного повозка, запряженная волами и будет ли это уместно в любой производственный код вызова от случая к случаю 1007 * можно аппроксимировать некоторой десятичной.почти эквивалентны, например, умножьте требуемую десятичную точность на 10 и разделите на 3 (на основе 2 ^ 10 ~ = 10 ^ 3).

Входное число (10.1234 ...) с 1 битомточности 8;при 2 это 10 и т. д.

Отдельно, десятичное округление IMHO лучше всего делать во время вывода или при использовании представления с десятичной дробью (например, для хранения мантиссы int и степени степени 10).

#include <ieee754.h>
#include <iostream>
#include <iomanip>

double round_double(double d, int precision)
{
    ieee754_double* p = reinterpret_cast<ieee754_double*>(&d);
    std::cout << "mantissa  0:" << std::hex << p->ieee.mantissa0
        << ", 1:" << p->ieee.mantissa1 << '\n';
    unsigned mask0 = precision < 20 ? 0x000FFFFF << (20 - precision) :
                                      0x000FFFFF;
    unsigned mask1 = precision <  20 ? 0 :
                     precision == 53 ? 0xFFFFFFFF :
                                       0xFFFFFFFE << (32 + 20 - precision);
    std::cout << "masks     0:" << mask0 << ", 1: " << mask1 << '\n';
    p->ieee.mantissa0 &= mask0;
    p->ieee.mantissa1 &= mask1;
    std::cout << "mantissa' 0:" << p->ieee.mantissa0
        << ", 1:" << p->ieee.mantissa1 << '\n';
    return d;
}

int main()
{
    double num = 10.123456789987654321;

    for (int prec = 1; prec <= 53; ++prec)
        std::cout << std::setiosflags(std::ios::showpoint | std::ios::fixed)
            << std::setprecision(60)
            << "round_double(" << num << ", "  << prec << ") = "
            << round_double(num, prec) << std::endl;
}

Вывод ...

mantissa  0:43f35, 1:ba76eea7
masks     0:fff80000, 1: 0
mantissa' 0:0, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 1) = 8.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:fffc0000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 2) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:fffe0000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 3) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:ffff0000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 4) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:ffff8000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 5) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:ffffc000, 1: 0
mantissa' 0:40000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 6) = 10.000000000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:ffffe000, 1: 0
mantissa' 0:42000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 7) = 10.062500000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:fffff000, 1: 0
mantissa' 0:43000, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 8) = 10.093750000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:7ffff800, 1: 0
mantissa' 0:43800, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, 9) = 10.109375000000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:3ffffc00, 1: 0
mantissa' 0:43c00, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, a) = 10.117187500000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:1ffffe00, 1: 0
mantissa' 0:43e00, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, b) = 10.121093750000000000000000000000000000000000000000000000000000
mantissa  0:43f35, 1:ba76eea7
masks     0:fffff00, 1: 0
mantissa' 0:43f00, 1:0
round_double(10.123456789987654858009591407608240842819213867187500000000000, c) = 10.123046875000000000000000000000000000000000000000000000000000

etc....
...