Polynomial fixes: for mixed-type arithmetic etc.#41
Polynomial fixes: for mixed-type arithmetic etc.#41kundor wants to merge 16 commits intoboostorg:developfrom
Conversation
|
Thanks for picking up these defects. I remember now when I was implementing |
|
It would be great if you could include in this PR the unit tests that would otherwise fail without these changes , including explicit tests of the new |
|
Hmmm, I guess this begs the question as to whether non-PoD numbers (such as polynomial) should promote according to the same rules as PoDs. What do the multiprecision classes do, John? How do they handle arithmetic with a mixture of types? |
|
Speaking of which, is there really a need to template the shift operators on the right-hand parameter? Is there a case for calling it with anything but an |
|
On 15/05/2016 16:46, Nick Matteo wrote:
I wouldn't think so no. Jeremy? |
It shouldn't make any difference for template functions, but it's silly to only have it sometimes
|
On 15/05/2016 12:53, Jeremy W. Murphy wrote:
Well first off they don't use Boost.Operators which is rather ill suited a op b If there is an implicit (ie non-lossy) conversion from b to a, then b is Of course internally, the value may be used directly where it is more In this situation, for: polynomial op U we would want to enforce that there is an implicit, non-lossy conversion The logic used is somewhat multiprecision-specific, and in HTH, John. |
|
I rebased the branch, and added the tests Jeremy asked for. Is it necessary for tests to be pre-C++11? It's kind of painful. |
|
With respect to the shift operators, no, there is no particular need for them to be templated, and off the cuff it only makes sense to call them with an integral type. How to handle negative values raises a curious thought, though: should |
|
Hmmm, so for |
|
Re: polynomial<int> p{3,2};
p *= 1.5;to give us 3x + 4, analogous to what Personally, I would rather have it act like built-in Making it an error would require a templated version of the function just to fail, using something like the On the other hand, removing the templating from poly-poly operations ( |
|
Commit 910a62a removes arithmetic between polynomials with different base types, so you'll have to explicitly cast one operand or the other (e.g. if All the tests in test_polynomial.cpp still pass. |
We no longer need polynomial<U> arguments to prefer a different overload, since mixed-type polynomial operations were nixed.
| for (iter it = m_data.begin(); it != m_data.end(); ++it) | ||
| *it -= T(value * T(*it / value)); | ||
| return *this; | ||
| } |
There was a problem hiding this comment.
Should modulus still have a U template?
There was a problem hiding this comment.
Yes, operator %=(U), and the other self-modifying operators for scalar arguments, are all still templated. We can change that if you want to be more strict.
I was fixing some minor problems in the polynomial documentation, when I noticed that
operator / (polynomial<T>, U)andoperator % (polynomial<T>, U)were not actually templated on the type of the scalarU, as reported in the docs, but were provided by Boost.Operators and only accepted scalars of typeT.This isn't very noticeable, since either way, the code compiles only if
Uis implicitly convertible toT. However, it does cause different behavior. For instance,polynomial<int>{2,4,6} / 1.5is an identity operation (the1.5is truncated to1), whilepolynomial<int>{2,4,6} * 0.6667yields {1,2,4}.More seriously, if
ais apolynomial<int>,a = a / 1.5anda /= 1.5would give different results.To fix this, I added templated implementations of these operators just like all the other polynomial-scalar operators.
For consistency, I used Boost.Operators to provide all the polynomial-polynomial operations (it was being used just for division, modulus, and operator !=), which are conveniently packed together in the class
ordered_euclidean_ring_operators< polynomial<T> >. (This entailed also adding anoperator <, which also enables polynomials to be put instd::setetc. It compares by degree first, so that it's compatible with the Euclidean ordering, then lexicographically starting with the highest-degree coefficient.)The self-assign operators are all templated on
polynomial<U>. However,operator /=and%=call out toquotient_remainder, which only takes two matchingpolynomial<T>arguments. So it's ambiguous what to convert when callingquotient_remainder(polynomial<T>, polynomial<U>), causing an error.To fix this,
polynomial<T>::operator /=(const polynomial<U>& q)can callquotient_remainder(*this, polynomial<T>(q)). But then you might as well not template the operator on type U, and just make itpolynomial::operator /=(const polynomial& q); you can still use it with anypolynomial<U>because there is an implicit conversion topolynomial<T>.Unfortunately, just removing the templating causes
polynomial<U>arguments to choose the scalar versionoperator /=(const U& value)instead. So I had to guard that function withenable_if< boost::is_convertible<U,T> >.This situation does result in inconsistent behavior. For instance, multiplying a
polynomial<int>by apolynomial<double>will do each multiplication withdoubles before truncating back toint, whereas dividing by apolynomial<double>will first truncate all the values toint.Similarly, with
polynomial<int> a{3},a /= 1.5results in 2, buta /= polynomial<double>{1.5}results in 3.Changing this, so that it promotes internal computations and then truncates, would mean changing the implementation of quotient_remainder.
Finally, when
Tis integral, there can be a remainder after dividing by a scalar, so I added an implementation ofoperator %=for this (instead of just setting to zero as before.) It usesenable_if_c<std::numeric_limits<T>::is_integer>—in theory, the algorithm used should work for other types, but with floating point types it would probably end up with annoying near-zero coefficients.I would be happy to fix up this PR if you want to merge #39 first (it will conflict a little.)