C ++ Boost Multiprecision与Python的mpmath之间的互操作性

时间:2019-03-13 01:02:01

标签: python c++ boost mpmath boost-multiprecision

我有分别使用Boost Multiprecision和Python的mpmath的经验。

当要使两者进行通信时(例如,在C ++中创建Python扩展),我的尝试总是涉及某种浪费的浮点到字符串转换和字符串到浮点转换。

我的问题是:是否可以使两者以更高效(更优雅)的方式进行交流?而且,我的意思是,是否有一种方法可以直接从Python mpmath.mpf对象以相同的方式as C's mpp does via pybind11加载C ++ Boost Multiprecision并将其导出到该对象?

我一直在寻找这个。 only other similar question I found只是使用pybind11从Boost Multiprecision导出到Python(通常),而不是直接导出到mpmath对象。在这个问题中,OP最终使用了我尝试避免的相同方法(也就是说,在与C ++和Python通信时,从字符串转换为字符串)。

1 个答案:

答案 0 :(得分:0)

这仅部分回答您的问题。因为直接的答案是:不,没有浪费的字符串转换就不可能以一种干净的方式进行,因为mpmath是一个纯Python库,没有用C或{{1 }},因此,即使您尝试通过使用某种二进制兼容性来跳过“浪费的转换”,您的代码也会非常脆弱:每次使用某些C++python内部构件时,它都会中断的变化是如此之小。

但是我需要完全相同的东西。因此,我选择了通过mpmath注册的自动转换,该转换使用字符串进行检查和转换。实际上,在python内部,您还从字符串创建了boost::python对象,因此它几乎是相同的,除了在下面的代码中,它更快,因为它是在mpmath.mpf内部编写的。

所以这对我有用:

C++

现在,您可以使用以下命令编译此代码:

#include <boost/python.hpp>
#include <iostream>
#include <limits>
#include <sstream>
#include <boost/math/constants/constants.hpp>
#include <boost/multiprecision/cpp_bin_float.hpp>
namespace py = ::boost::python;
using Prec80 = boost::multiprecision::number<boost::multiprecision::cpp_bin_float<80>>;

template<typename ArbitraryReal>
struct ArbitraryReal_to_python {
    static PyObject* convert(const ArbitraryReal& val){
        std::stringstream ss{};
        ss << std::setprecision(std::numeric_limits<ArbitraryReal>::digits10+1) << val;
        py::object mpmath = py::import("mpmath");
        mpmath.attr("mp").attr("dps")=int(std::numeric_limits<ArbitraryReal>::digits10+1);
        py::object result = mpmath.attr("mpf")(ss.str());
        return boost::python::incref( result.ptr() );
    }
};

template<typename ArbitraryReal>
struct ArbitraryReal_from_python {
    ArbitraryReal_from_python(){
         boost::python::converter::registry::push_back(&convertible,&construct,boost::python::type_id<ArbitraryReal>());
    }
    static void* convertible(PyObject* obj_ptr){
        // Accept whatever python is able to convert into float
        // This works with mpmath numbers. However if you want to accept strings as numbers this checking code can be a little longer to verify if string is a valid number.
        double check = PyFloat_AsDouble(obj_ptr);
        return (PyErr_Occurred()==nullptr) ? obj_ptr : nullptr;
    }
    static void construct(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data){
        std::istringstream ss{ py::call_method<std::string>(obj_ptr, "__str__") };
        void* storage=((boost::python::converter::rvalue_from_python_storage<ArbitraryReal>*)(data))->storage.bytes;
        new (storage) ArbitraryReal;
        ArbitraryReal* val=(ArbitraryReal*)storage;
        ss >> *val;
        data->convertible=storage;
    }
};


struct Var
{
    Prec80 value{"-71.23"};
    Prec80 get() const   { return value; };
    void set(Prec80 val) { value = val;  };
};

BOOST_PYTHON_MODULE(pysmall)
{
    ArbitraryReal_from_python<Prec80>();
    py::to_python_converter<Prec80,ArbitraryReal_to_python<Prec80>>();

    py::class_<Var>("Var" )
        .add_property("val", &Var::get, &Var::set);
}

这是一个示例g++ -O1 -g pysmall.cpp -o pysmall.so -std=gnu++17 -fPIC -shared -I/usr/include/python3.7m/ -lboost_python37 -lpython3.7m -Wl,-soname,"pysmall.so" 会话:

python

In [1]: import pysmall In [2]: a=pysmall.Var() In [3]: a.val Out[3]: mpf('-71.2299999999999999999999999999999999999999999999999999999999999999999999999999997072') In [4]: a.val=123.12 In [5]: a.val Out[5]: mpf('123.120000000000000000000000000000000000000000000000000000000000000000000000000000003') 代码并不关心mpmath是否已经在python中导入。如果是,它将获取现有的库句柄,如果不是,则将其导入。 如果您在此代码段中有任何改进的地方,请告诉我!

在我写这篇文章时,有一些有用的参考文献:

  1. https://misspent.wordpress.com/2009/09/27/how-to-write-boost-python-converters/
  2. https://github.com/bluescarni/mppp/blob/master/include/mp%2B%2B/extra/pybind11.hpp(但我不想使用pybind11,仅使用C++