Re: [Boost-users] mpl rational_c and fixed_c
Matt Calabrese writes:
A couple of days ago I started work on a library where I need a way to represent rational numbers and fixed point precision numbers at compile time, so I started creating a compile-time rational number type following the style of integral_c in boost::mpl. After completing most operations, I notices that in the mpl/math directory there were already the beginnings of rational_c and fixed_c types, however, they are far from complete (in rational_c there are just two static constants for numerator and denominator and a self typedef, with no specialized operations). Are these types still in development or are they abandoned?
They are waiting for somebody to pick them up. To some extent, that
already happened with rational numbers. There are a couple of more or
less complete implementations laying around:
* http://tinyurl.com/2qrpq, 'rational_c' implementation by Hugo Duncan;
* http://tinyurl.com/39mjl, "static_rational.hpp" by Matthias Schabel
(it's a part of YANL, his dimensional analysis library).
The main reason one of them is not in the library yet is that their
notation for arithmetic operations leaves something to be desired --
you have to write something like
rational_plus
I'd like to continue my project using mpl::rational_c and mpl::fixed_c as I need to make them work with the mpl integral constants anyways.
So is development on these templates halted, meaning I should just go ahead and finish my types, or are the types coming soon enough so I just should just hold off and wait for the cavalry to arrive?
No, please go ahead! Cavalry have enough on their plates already (among other things, a new version of the library is upcoming, with plenty of new exciting and useful stuff like associative sequences), so progress on these two is totally in your hands. -- Aleksey Gurtovoy MetaCommunications Engineering
Besides this issue, AFAIK these are already usable. Of course, if you would like to improve them/contribute more optimal/clean/better in any other regard implementation, please do.
No, please go ahead! Cavalry have enough on their plates already (among other things, a new version of the library is upcoming, with plenty of new exciting and useful stuff like associative sequences), so progress on these two is totally in your hands.
-- Aleksey Gurtovoy MetaCommunications Engineering
Okay, I decided before I continue with my other library, I'll fully implement rational_c properly (and possibly fixed_c), however, doing so without making the code horribly complex would require changes to the current definitions of pretty much all of the operations (plus, multiplies, etc. as well as the conditional operators). The problem is that those current functions take 5 parameters, making specialization nearly impossible for the rational and fixed templates as you'd have to account for when the 3rd, 4th, 5th, etc parameters are not passed (and so the default integral 0 is used). That can't be easily done (at least I don't see how) without having to specialize each condition separately (one for 3 rational types, one for 4 rational types, etc ). Moreso, this applies to the conditional operators as well, there is no easy way to account for using different types in multiplies IE multiplying a rational and integral together, or a fixed and rational, etc. Again, to account for these possibilities you'd have to make an incredibly large amount of specializations. Of course, you could always just limit them to only work when all the parameters are the same kind of compile-time value template, but that would be somewhat limitting,especially with other solutions available. What I propose is a way around this situation, but still have the metafunctions declared the same way, therefore not breaking old code and not limiting functionality. To do so, the following adjustments would have to be made (and I have already begun to make them for my own use): 1) Additional versions of all of the arithmetic operators which take 2 parameters, each need to be defined and work like binary operators. They should be specialized for when both the left and right operands are of the same type. How to account for when they are different types will be covered in the future points. 2) A value_cast metafunction which can cast between all different compile-time constant types. The way it is implemented is by requiring all types to have specialized metafunctions for converting to and from the fixed point type. When value_cast is used, it, by default, converts the right hand operand to fixed_c, then converts that fixed_c to the target type. Since a fixed_c can hold pretty much any real value to a certain precision, you can be certain, to an extent, that little, if any precision will be lost through conversion. So, with that proposed addition, you'd be able to convert between all different types and only require people developing new types to have to specialize conversion to and from fixed_c. The only problem is that in order for the value_cast to exist, it obviously needs to have a parameter which specifies a type to convert to. The only way I can currently see that would allow that would be to have the value_cast take a type parameter which would be a template instantiation with a dummy value, which would, in a way, be very similar to rebinding allocators. 3) The conditional operations and 2 operand arithmetic operations should be redefined in the base template definition to convert both types to fixed_c and then make a comaprison, therefore allowing programmers to use the operations on two different types of compile-time constants without having to manually convert them to similar types before-hand (as I alluded in first point). The conditional operations can then be specialized for when the types are the same on each side to avoid the cost of a cast on each of the operands to fixed_c. 4) The current "varying length" operations which cause problems (multiplies, plus, minus, etc) should be redefined to just call the 2 operand version of the corresponding operation on each of the parameters passed to the operation. This way, programmers can still pass multiple parameters to these functions, with each parameter being a different type, without the developer having to make an extremely large amount of specializations for each type (including those which have yet to be developed). I have already begun implementing all of the points I have suggested, though with my rational_c as a the basis for conversion rather than fixed_c (as I haven't yet developed fixed_c). Also, converting to and from rational_c is considerably simpler than converting to and from fixed_c. For this reason, I may end up leaving the basis for conversion as rational_c instead of fixed_c. If anyone sees any other, more elegant solutions for the problems I mentioned, please post them and I may change my approach. Additional suggestions are also greatly appreciated. Thanks. -Matt Calabrese
Matt Calabrese wrote:
Okay, I decided before I continue with my other library, I'll fully implement rational_c properly (and possibly fixed_c), however, doing so without making the code horribly complex would require changes to the current definitions of pretty much all of the operations (plus, multiplies, etc. as well as the conditional operators). ^^^^^^^^^^^ I suppose you meant "comparison", here and below.
The problem is that those current functions take 5 parameters, making specialization nearly impossible for the rational and fixed templates as you'd have to account for when the 3rd, 4th, 5th, etc parameters are not passed (and so the default integral 0 is used). That can't be easily done (at least I don't see how) without having to specialize each condition separately (one for 3 rational types, one for 4 rational types, etc ). Moreso, this applies to the conditional operators as well, there is no easy way to account for using different types in multiplies IE multiplying a rational and integral together, or a fixed and rational, etc. Again, to account for these possibilities you'd have to make an incredibly large amount of specializations. Of course, you could always just limit them to only work when all the parameters are the same kind of compile-time value template, but that would be somewhat limitting,especially with other solutions available.
I pretty much agree with the analysis.
What I propose is a way around this situation, but still have the metafunctions declared the same way, therefore not breaking old code and not limiting functionality. To do so, the following adjustments would have to be made (and I have already begun to make them for my own use):
1) Additional versions of all of the arithmetic operators which take 2 parameters, each need to be defined and work like binary operators. They should be specialized for when both the left and right operands are of the same type. How to account for when they are different types will be covered in the future points.
Sounds good so far.
2) A value_cast metafunction which can cast between all different compile-time constant types. The way it is implemented is by requiring all types to have specialized metafunctions for converting to and from the fixed point type. When value_cast is used, it, by default, converts the right hand operand to fixed_c, then converts that fixed_c to the target type. Since a fixed_c can hold pretty much any real value to a certain precision, you can be certain, to an extent, that little, if any precision will be lost through conversion. So, with that proposed addition, you'd be able to convert between all different types and only require people developing new types to have to specialize conversion to and from fixed_c. The only problem is that in order for the value_cast to exist, it obviously needs to have a parameter which specifies a type to convert to. The only way I can currently see that would allow that would be to have the value_cast take a type parameter which would be a template instantiation with a dummy value, which would, in a way, be very similar to rebinding allocators.
The idea of avoiding a combinatorial explosion through a single user-defined conversion metafunction which will be used to even the argument types is definitely solid; however, I am afraid we cannot escape with something as simplistic as conversion to a default predefined "contains-it-all" numeric type -- simply because there is no such type (for instance, 'fixed_c' is not capable of holding compile-time complex numbers, and so on). Moreover, specializing of arithmetic metafunctions is not necessary a prerogative of numeric types -- consider, for example, random-access iterators. To summarize, IMO the idea is excellent, but it needs to be carried a little bit further -- please see below.
3) The conditional operations and 2 operand arithmetic operations should be redefined in the base template definition to convert both types to fixed_c and then make a comaprison, therefore allowing programmers to use the operations on two different types of compile-time constants without having to manually convert them to similar types before-hand (as I alluded in first point). The conditional operations can then be specialized for when the types are the same on each side to avoid the cost of a cast on each of the operands to fixed_c.
Yep to everything except the 'fixed_c' part.
4) The current "varying length" operations which cause problems (multiplies, plus, minus, etc) should be redefined to just call the 2 operand version of the corresponding operation on each of the parameters passed to the operation. This way, programmers can still pass multiple parameters to these functions, with each parameter being a different type, without the developer having to make an extremely large amount of specializations for each type (including those which have yet to be developed).
Yep! Something like
template<
typename N1
, typename N2
, typename N3 = int_<0>
, typename N4 = int_<0>
, typename N5 = int_<0>
>
struct plus
: plus2< plus2< plus2< plus2
I have already begun implementing all of the points I have suggested, though with my rational_c as a the basis for conversion rather than fixed_c (as I haven't yet developed fixed_c). Also, converting to and from rational_c is considerably simpler than converting to and from fixed_c. For this reason, I may end up leaving the basis for conversion as rational_c instead of fixed_c.
If anyone sees any other, more elegant solutions for the problems I mentioned, please post them and I may change my approach. Additional suggestions are also greatly appreciated.
You are going in the right direction. Here are some suggestions of how
to generalize the scheme to get what I would consider an ideal solution:
1) Make specializable binary templates for each arithmetic/comparison
metafunctions to be tag-dispatched metafunction classes, e.g.
template<
typename N1
, typename N2
>
struct plus2
: plus_impl< // 'plus_impl' is a point of customization
typename plus_tag<N1>::type
, typename plus_tag<N2>::type
>::template apply
"Aleksey Gurtovoy"
Matt Calabrese wrote:
Okay, I decided before I continue with my other library, I'll fully implement rational_c properly (and possibly fixed_c), however, doing so without making the code horribly complex would require changes to the current definitions of pretty much all of the operations (plus, multiplies, etc. as well as the conditional operators). ^^^^^^^^^^^ I suppose you meant "comparison", here and below.
The problem is that those current functions take 5 parameters, making specialization nearly impossible for the rational and fixed templates as you'd have to account for when the 3rd, 4th, 5th, etc parameters are not passed (and so the default integral 0 is used). That can't be easily done (at least I don't see how) without having to specialize each condition separately (one for 3 rational types, one for 4 rational types, etc ). Moreso, this applies to the conditional operators as well, there is no easy way to account for using different types in multiplies IE multiplying a rational and integral together, or a fixed and rational, etc. Again, to account for these possibilities you'd have to make an incredibly large amount of specializations.
I have come up against the above problems. My solution was to create a metafunction like so: template< typename A, typename Op, // some type representing the op. //I actually use template<typename> class Op // with std::plus etc as the token typename B
struct binary_operation{ typedef ... result_type; // works with result_of }; notably compatible with mpl::plus (say); (This is actually my standalone impl of mpl ops. I dont use the extra params. OTOH My impl is easy to replace with official version namespace boost{namespace mpl{ template< typename A, typename B
struct plus // unused params default aways { typedef typename binary_operation< A, std::plus,B
::result_type type; }; }}
Actually definition for my rational_c op integral_c and vice versa is then:
(Assuming binary_operation
Sorry if this is a double post, new to this.
Besides this issue, AFAIK these are already usable. Of course, if you would like to improve them/contribute more optimal/clean/better in any other regard implementation, please do.
No, please go ahead! Cavalry have enough on their plates already (among other things, a new version of the library is upcoming, with plenty of new exciting and useful stuff like associative sequences), so progress on these two is totally in your hands.
-- Aleksey Gurtovoy MetaCommunications Engineering
Okay, I decided before I continue with my other library, I'll fully implement rational_c properly (and possibly fixed_c), however, doing so without making the code horribly complex would require changes to the current definitions of pretty much all of the operations (plus, multiplies, etc. as well as the conditional operators). The problem is that those current functions take 5 parameters, making specialization nearly impossible for the rational and fixed templates as you'd have to account for when the 3rd, 4th, 5th, etc parameters are not passed (and so the default integral 0 is used). That can't be easily done (at least I don't see how) without having to specialize each condition separately (one for 3 rational types, one for 4 rational types, etc ). Moreso, this applies to the conditional operators as well, there is no easy way to account for using different types in multiplies IE multiplying a rational and integral together, or a fixed and rational, etc. Again, to account for these possibilities you'd have to make an incredibly large amount of specializations. Of course, you could always just limit them to only work when all the parameters are the same kind of compile-time value template, but that would be somewhat limitting,especially with other solutions available. What I propose is a way around this situation, but still have the metafunctions declared the same way, therefore not breaking old code and not limiting functionality. To do so, the following adjustments would have to be made (and I have already begun to make them for my own use): 1) Additional versions of all of the arithmetic operators which take 2 parameters, each need to be defined and work like binary operators. They should be specialized for when both the left and right operands are of the same type. How to account for when they are different types will be covered in the future points. 2) A value_cast metafunction which can cast between all different compile-time constant types. The way it is implemented is by requiring all types to have specialized metafunctions for converting to and from the fixed point type. When value_cast is used, it, by default, converts the right hand operand to fixed_c, then converts that fixed_c to the target type. Since a fixed_c can hold pretty much any real value to a certain precision, you can be certain, to an extent, that little, if any precision will be lost through conversion. So, with that proposed addition, you'd be able to convert between all different types and only require people developing new types to have to specialize conversion to and from fixed_c. The only problem is that in order for the value_cast to exist, it obviously needs to have a parameter which specifies a type to convert to. The only way I can currently see that would allow that would be to have the value_cast take a type parameter which would be a template instantiation with a dummy value, which would, in a way, be very similar to rebinding allocators. 3) The conditional operations and 2 operand arithmetic operations should be redefined in the base template definition to convert both types to fixed_c and then make a comaprison, therefore allowing programmers to use the operations on two different types of compile-time constants without having to manually convert them to similar types before-hand (as I alluded in first point). The conditional operations can then be specialized for when the types are the same on each side to avoid the cost of a cast on each of the operands to fixed_c. 4) The current "varying length" operations which cause problems (multiplies, plus, minus, etc) should be redefined to just call the 2 operand version of the corresponding operation on each of the parameters passed to the operation. This way, programmers can still pass multiple parameters to these functions, with each parameter being a different type, without the developer having to make an extremely large amount of specializations for each type (including those which have yet to be developed). I have already begun implementing all of the points I have suggested, though with my rational_c as a the basis for conversion rather than fixed_c (as I haven't yet developed fixed_c). Also, converting to and from rational_c is considerably simpler than converting to and from fixed_c. For this reason, I may end up leaving the basis for conversion as rational_c instead of fixed_c. If anyone sees any other, more elegant solutions for the problems I mentioned, please post them and I may change my approach. Additional suggestions are also greatly appreciated. Thanks. -Matt Calabrese
The problem is that those current functions take 5 parameters, making specialization nearly impossible for the rational and fixed templates as you'd have to account for when the 3rd, 4th, 5th, etc parameters are not passed (and so the default integral 0 is used). That can't be easily done (at least I don't see how) without having to specialize each condition separately (one for 3 rational types, one for 4 rational types, etc ).
Scratch that, it can just call a specialized version of a 2 parameter operation like the one I suggested later in the post, but still, all of the other problems remain (when types are different).
participants (3)
-
Aleksey Gurtovoy
-
Andy Little
-
Matt Calabrese