使用 MPFR 时如何在 Boost Multiprecision 中设置舍入模式
How to set rounding mode in Boost Multiprecision when using MPFR
我想弄清楚如何在 Boost Multiprecision 中使用舍入模式格式化 mpfr_float 数字。在下面的示例中,我希望 1.55 舍入为 1.5 或 1.6,具体取决于使用的舍入模式,但对于所有情况,它都输出 1.5。如何使用 MPFR 在 Boost 中实现这个简单的功能?
#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;
}
docs 说:
All conversions are performed by the underlying MPFR library
但是 mpfr 浮动后端的文档指出:
Things you should know when using this type:
- A default constructed mpfr_float_backend is set to zero (Note that
this is not the default MPFR behavior).
- All operations use round to nearest.
(强调我的)
我发现 MPFR 没有全局默认舍入覆盖,因此只有特定操作(赋值和精度更改)需要 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);
好了。它只是硬编码。
如果你愿意做一些假设,你可以编写自己的简化输出例程(见下文),但实际上我怀疑你是否对四舍五入如此关心,如果它只是为了显示。
假设性能不是主要关注点(因为无论如何它都是字符串 IO,它很少很快或需要如此),我将假设它对 更多 有价值能够实际获得四舍五入的数字,因此您可以使用现有的图书馆设施打印它。
什么是舍入?
MPFR 中的四舍五入不是您想象的那样:它不会四舍五入到 小数 位置。它在表示中四舍五入为二进制数字。
让我们通过实施舍入 MPFR 方式来证明这一点:
#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
那么,我们期待什么?
让我们重新实现字符串化:
#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
Disclaimer: I initially dropped some code to drop scientific notation support, so things might not be 100% up to par. Also, not tested with subnormal, infinity, nan. YMMV
结束语
如果确实不是关于表示而是舍入内存中的数字,您可以从字符串表示构造一个新的 mpfr_float。
在那种情况下,我的期望是你首先想要一个小数浮点数(cpp_dec_float
例如)。
我想弄清楚如何在 Boost Multiprecision 中使用舍入模式格式化 mpfr_float 数字。在下面的示例中,我希望 1.55 舍入为 1.5 或 1.6,具体取决于使用的舍入模式,但对于所有情况,它都输出 1.5。如何使用 MPFR 在 Boost 中实现这个简单的功能?
#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;
}
docs 说:
All conversions are performed by the underlying MPFR library
但是 mpfr 浮动后端的文档指出:
Things you should know when using this type:
- A default constructed mpfr_float_backend is set to zero (Note that this is not the default MPFR behavior).
- All operations use round to nearest.
(强调我的)
我发现 MPFR 没有全局默认舍入覆盖,因此只有特定操作(赋值和精度更改)需要 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);
好了。它只是硬编码。
如果你愿意做一些假设,你可以编写自己的简化输出例程(见下文),但实际上我怀疑你是否对四舍五入如此关心,如果它只是为了显示。
假设性能不是主要关注点(因为无论如何它都是字符串 IO,它很少很快或需要如此),我将假设它对 更多 有价值能够实际获得四舍五入的数字,因此您可以使用现有的图书馆设施打印它。
什么是舍入?
MPFR 中的四舍五入不是您想象的那样:它不会四舍五入到 小数 位置。它在表示中四舍五入为二进制数字。
让我们通过实施舍入 MPFR 方式来证明这一点:
#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
那么,我们期待什么?
让我们重新实现字符串化:
#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
Disclaimer: I initially dropped some code to drop scientific notation support, so things might not be 100% up to par. Also, not tested with subnormal, infinity, nan. YMMV
结束语
如果确实不是关于表示而是舍入内存中的数字,您可以从字符串表示构造一个新的 mpfr_float。
在那种情况下,我的期望是你首先想要一个小数浮点数(cpp_dec_float
例如)。