Как установить режим округления в Boost Multiprecision при использовании MPFR - PullRequest
2 голосов
/ 16 июня 2020

Я пытаюсь понять, как отформатировать числа mpfr_float, используя режим округления в Boost Multiprecision. В приведенном ниже примере я ожидаю, что 1,55 округлится до 1,5 или 1,6 в зависимости от того, какой режим округления используется, но вместо этого для всех случаев он выводит 1,5. Как мне достичь этой простой функциональности в Boost с MPFR?

#include <iostream>
#include <boost/multiprecision/mpfr.hpp>

void setRoundingMode(boost::multiprecision::mpfr_float m, mpfr_rnd_t r)
{
    mpfr_t tmp;
    mpfr_init(tmp);
    mpfr_set(tmp, m.backend().data(), r);
    mpfr_clear(tmp);
}

int main()
{
    using namespace boost::multiprecision;
    using std::cout;
    using std::endl;
    using std::setprecision;

    mpfr_float::default_precision(50);
    mpfr_float a("1.55");

    setRoundingMode(a, MPFR_RNDN); /* round to nearest, with ties to even */
    cout << setprecision(2) << a << endl;

    setRoundingMode(a, MPFR_RNDZ); /* round toward zero */
    cout << setprecision(2) << a << endl;

    setRoundingMode(a, MPFR_RNDU); /* round toward +Inf */
    cout << setprecision(2) << a << endl;

    setRoundingMode(a, MPFR_RNDD); /* round toward -Inf */
    cout << setprecision(2) << a << endl;

    setRoundingMode(a, MPFR_RNDA); /* round away from zero */
    cout << setprecision(2) << a << endl;

    setRoundingMode(a, MPFR_RNDF); /* faithful rounding */
    cout << setprecision(2) << a << endl;

    setRoundingMode(a, MPFR_RNDNA); /* round to nearest, with ties away from zero (mpfr_round) */
    cout << setprecision(2) << a << endl;

    return 0;
}

1 Ответ

3 голосов
/ 16 июня 2020

документы говорят:

Все преобразования выполняются базовой библиотекой MPFR

Однако do c для бэкэнда mpfr float заявляет:

Что следует знать при использовании этого типа:

  • Созданный по умолчанию mpfr_float_backend равен нулю (обратите внимание, что это не поведение MPFR по умолчанию).
  • Все операции используют округление до ближайшего .

(выделено мной)

Я обнаружил, что MPFR не имеет глобального переопределения округления по умолчанию, поэтому он указывает только c операции (присваивание и изменение точности), которые принимают mpfr_rnd_t.

Вы это поняли, следовательно, ваш:

void setRoundingMode(boost::multiprecision::mpfr_float m, mpfr_rnd_t r)
{
    mpfr_t tmp;
    mpfr_init(tmp);
    mpfr_set(tmp, m.backend().data(), r);
    mpfr_clear(tmp);
}

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

Это ... ничего не делает эффективно.

Итак ...

Как работает IO?

operator<< вызывает:

template <class Backend, expression_template_option ExpressionTemplates>
inline std::ostream& operator<<(std::ostream& os, const number<Backend, ExpressionTemplates>& r)
{
   std::streamsize d  = os.precision();
   std::string     s  = r.str(d, os.flags());
   std::streamsize ss = os.width();
   if (ss > static_cast<std::streamsize>(s.size()))
   {
      char fill = os.fill();
      if ((os.flags() & std::ios_base::left) == std::ios_base::left)
         s.append(static_cast<std::string::size_type>(ss - s.size()), fill);
      else
         s.insert(static_cast<std::string::size_type>(0), static_cast<std::string::size_type>(ss - s.size()), fill);
   }
   return os << s;
}

Пока все хорошо. Мясо находится в

std :: string s = r.str (d, os.flags ());

Реализация str(...) ретранслирует от внешнего интерфейса (number<>) к бэкэнд, и в итоге делает (среди множества разных вещей):

 char* ps = mpfr_get_str(0, &e, 10, static_cast<std::size_t>(digits), m_data, GMP_RNDN);

Итак, вот оно. Это просто жестко запрограммировано.

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

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

Что такое округление, правда?

Округление в MPFR - это не то, что вы думаете: это не округлить до десятичных позиций. Он округляется до двоичных цифр в представлении.

Давайте продемонстрируем это, реализовав округление MPFR:

Live On Coliru

#include <iostream>
#include <boost/multiprecision/mpfr.hpp>

namespace bmp = boost::multiprecision;

namespace detail {
    template <
        unsigned srcDigits10, bmp::mpfr_allocation_type srcAlloc,
        unsigned dstDigits10, bmp::mpfr_allocation_type dstAlloc
    >
    void round(
            bmp::mpfr_float_backend<srcDigits10, srcAlloc> const& src, 
            mpfr_rnd_t r,
            bmp::mpfr_float_backend<dstDigits10, dstAlloc>& dst)
    {
        mpfr_set(dst.data(), src.data(), r);
    }

    template <unsigned dstDigits10, unsigned srcDigits10, bmp::mpfr_allocation_type alloc>
    auto round(bmp::mpfr_float_backend<srcDigits10, alloc> const& src, mpfr_rnd_t r) {
        bmp::mpfr_float_backend<dstDigits10, alloc> dst;
        round(src, r, dst);
        return dst;
    }
}

template <unsigned dstDigits10, typename Number>
auto round(Number const& src, mpfr_rnd_t r) {
    auto dst = detail::round<dstDigits10>(src.backend(), r);
    return bmp::number<decltype(dst)>(dst);
}

int main() {
    using bmp::mpfr_float;
    mpfr_float::default_precision(50);
    mpfr_float const a("1.55");

    std::cout << std::setprecision(20) << std::fixed;

    for (mpfr_rnd_t r : {
             MPFR_RNDN, /* round to nearest, with ties to even */
             MPFR_RNDZ, /* round toward zero */
             MPFR_RNDU, /* round toward +Inf */
             MPFR_RNDD, /* round toward -Inf */
             MPFR_RNDA, /* round away from zero */
             MPFR_RNDF, /* faithful rounding */
             MPFR_RNDNA, /* round to nearest, with ties away from zero (mpfr_round) */
         })
    {
        std::cout << round<2>(a, r) << std::endl;
    }
}

Печать

1.54687500000000000000
1.54687500000000000000
1.55468750000000000000
1.54687500000000000000
1.55468750000000000000
1.55468750000000000000
1.55468750000000000000

Итак, чего мы ждем?

Давайте повторно реализуем строковую форму:

Live On Coliru

#include <iostream>
#include <boost/multiprecision/mpfr.hpp>

namespace bmp = boost::multiprecision;

template <unsigned srcDigits10, bmp::mpfr_allocation_type alloc>
auto to_string(bmp::mpfr_float_backend<srcDigits10, alloc> const& src,
               unsigned digits, mpfr_rnd_t r, std::ios::fmtflags fmtflags) {
    std::streamsize org_digits(digits);
    std::string result;

    mpfr_exp_t e = 0;
    char* ps = mpfr_get_str(0, &e, 10, static_cast<std::size_t>(digits),
                            src.data(), r);
    --e; // To match with what our formatter expects.
    if (e != -1) {
        // Oops we actually need a different number of digits to what we asked
        // for:
        mpfr_free_str(ps);
        digits += e + 1;
        if (digits == 0) {
            // We need to get *all* the digits and then possibly round up,
            // we end up with either "0" or "1" as the result.
            ps = mpfr_get_str(0, &e, 10, 0, src.data(), r);
            --e;
            unsigned offset = *ps == '-' ? 1 : 0;
            if (ps[offset] > '5') {
                ++e;
                ps[offset] = '1';
                ps[offset + 1] = 0;
            } else if (ps[offset] == '5') {
                unsigned i = offset + 1;
                bool round_up = false;
                while (ps[i] != 0) {
                    if (ps[i] != '0') {
                        round_up = true;
                        break;
                    }
                    ++i;
                }
                if (round_up) {
                    ++e;
                    ps[offset] = '1';
                    ps[offset + 1] = 0;
                } else {
                    ps[offset] = '0';
                    ps[offset + 1] = 0;
                }
            } else {
                ps[offset] = '0';
                ps[offset + 1] = 0;
            }
        } else if (digits > 0) {
            mp_exp_t old_e = e;
            ps = mpfr_get_str(0, &e, 10, static_cast<std::size_t>(digits),
                              src.data(), r);
            --e; // To match with what our formatter expects.
            if (old_e > e) {
                // in some cases, when we ask for more digits of precision, it
                // will change the number of digits to the left of the decimal,
                // if that happens, account for it here. example: cout << fixed
                // << setprecision(3) << mpf_float_50("99.9809")
                digits -= old_e - e;
                ps = mpfr_get_str(0, &e, 10, static_cast<std::size_t>(digits),
                                  src.data(), r);
                --e; // To match with what our formatter expects.
            }
        } else {
            ps = mpfr_get_str(0, &e, 10, 1, src.data(), r);
            --e;
            unsigned offset = *ps == '-' ? 1 : 0;
            ps[offset] = '0';
            ps[offset + 1] = 0;
        }
    }
    result = ps ? ps : "0";
    if (ps)
        mpfr_free_str(ps);
    bmp::detail::format_float_string(result, e, org_digits, fmtflags,
                                     0 != mpfr_zero_p(src.data()));
    return result;
}

template <unsigned srcDigits10, bmp::mpfr_allocation_type alloc>
auto to_string(
    bmp::number<bmp::mpfr_float_backend<srcDigits10, alloc>> const& src,
    unsigned digits, mpfr_rnd_t r,
    std::ios::fmtflags fmtflags = std::ios::fixed) {
    return to_string(src.backend(), digits, r, fmtflags);
}

int main() {
    using bmp::mpfr_float;
    mpfr_float::default_precision(50);
    mpfr_float const a("1.55");

    std::cout << std::setprecision(20) << std::fixed;

    for (mpfr_rnd_t r : {
             MPFR_RNDN, /* round to nearest, with ties to even */
             MPFR_RNDZ, /* round toward zero */
             MPFR_RNDU, /* round toward +Inf */
             MPFR_RNDD, /* round toward -Inf */
             MPFR_RNDA, /* round away from zero */
             MPFR_RNDF, /* faithful rounding */
             MPFR_RNDNA, /* round to nearest, with ties away from zero (mpfr_round) */
         })
    {
        std::cout
            << " -- " << to_string(a, 2, r)
            << ", " << to_string(a, 1, r)
            << " -- "  <<  to_string(a, 2, r, std::ios::scientific)
            << ", "  <<  to_string(a, 1, r, std::ios::scientific) << std::endl;
    }
}

Печатает

 -- 1.55, 1.5 -- 1.55e+00, 1.5e+00
 -- 1.54, 1.5 -- 1.54e+00, 1.5e+00
 -- 1.55, 1.6 -- 1.55e+00, 1.6e+00
 -- 1.54, 1.5 -- 1.54e+00, 1.5e+00
 -- 1.55, 1.6 -- 1.55e+00, 1.6e+00
 -- 1.55, 1.5 -- 1.55e+00, 1.5e+00
 -- 1.55, 1.5 -- 1.55e+00, 1.5e+00

Отказ от ответственности: я изначально отказался от кода, чтобы отказаться от поддержки нотации scientifi c, поэтому может быть не 100 % до номинала. Также не тестировалось с субнормальным, бесконечным, нан. YMMV

Заключительные мысли

Если действительно речь идет не о презентации, а о округлении чисел в памяти, вы можете построить новый mpfr_float из строкового представления.

My ожиданием в этом случае было бы действительно, что вам в первую очередь нужно десятичное число с плавающей запятой (cpp_dec_float например).

...