Monday, September 16, 2013

Odds and ends: basic types

Hello again.

Today's post is about the basic types we're using in the library. Most of its content was prompted by comments made on a previous post; so thank you, Matt.

In last post, I mentioned that I'm looking into publishing Implementing QuantLib as an ebook, but I'm not sure if there's any interest; please go read the post for details, if you haven't already, and leave your feedback.

Follow me on Twitter if you want to be notified of new posts, or add me to your circles, or subscribe via RSS: the widgets for that are in the sidebar, at the top right of the page. Also, make sure to check my Training page.


Odds and ends: basic types

The library interfaces don't use built-in types; instead, a number of typedefs are provided such as Time, Rate, Integer, or Size. They are all mapped to basic types (we talked about using full-featured types, possibly with range checking, but we dumped the idea). Furthermore, all floating-point types are defined as Real, which in turn is defined as double. This makes it possible to change all of them consistently by just changing Real.

In principle, this would allow one to choose the desired level of accuracy; but to this, the test-suite answers "Fiddlesticks!" since it shows a few failures when Real is defined as float or long double. The value of the typedefs is really in making the code more clear—and in allowing dimensional analysis for those who, like me, were used to it in a previous life as a physicist; for instance, expressions such as exp(r) or r+s*t can be immediately flagged as fishy if they are preceded by Rate r, Spread s, and Time t.

Of course, all those fancy types are only aliases to double and the compiler doesn't really distinguish between them. It would nice if they had stronger typing; so that, for instance, one could overload a method based on whether it is passed a price or a volatility.

One possibility would be the BOOST_STRONG_TYPEDEF macro, which is one of the bazillion utilities provided by Boost. It is used as, say,
    BOOST_STRONG_TYPEDEF(double, Time)
    BOOST_STRONG_TYPEDEF(double, Rate)
and creates a corresponding proper class with appropriate conversions to and from the underlying type. This would allow overloading methods, but has the drawbacks that not all conversions are explicit. This would break backward compatibility and make things generally awkward. For instance, a simple expression like Time t = 2.0; wouldn't compile. You'd also have to write f(Time(1.5)) instead of just f(1.5), even if f wasn't overloaded.

Also, the classes defined by the macro overload all operators: you can happily add a time to a rate, even though it doesn't make sense (yes, dimensional analysis again). It would be nice if the type system prevented this from compiling, while still allowing, for instance, to add a spread to a rate yielding another rate or to multiply a rate by a time yielding a pure number.

How to do this in a generic way, and ideally with no run-time costs, was shown first by Barton and Nackman [1]; a variation of their idea is implemented in the Boost::Units library, and a simpler one was implemented once by yours truly while still working in Physics. (I won't explain it here, but go look for it. It's almost insanely cool.) However, that might be overkill here; we don't have to deal with all possible combinations of length, mass, time and so on.

The ideal compromise for a future library might be to implement wrapper classes (à la Boost strong typedef) and to define explicitly which operators are allowed for which types. As usual, we're not the first ones to have this problem: the idea has been floating around for a while, and a proposal was put forward [2] to add to the next version of C++ a new feature, called opaque typedefs, which would make it easier to define this kind of types.

A final note: among these types, there is at least one which is not determined on its own (like Rate or Time) but depends on other types. The volatility of a price and the volatility of a rate have different dimensions, and thus should have different types. In short, Volatility should be a template type.


Bibliography

[1] J. Barton and L. R. Nackman, Dimensional Analysis, C++ Report, January 1995.
[2] W. E. Brown, Toward Opaque Typedefs for C++1Y, v2, C++ Standard Committee Paper N3741, 2013

Share this post: