Type safe physics operations in C++
Asked Answered
P

10

59

Does it make sens in C++ to define physics units as separate types and define valid operations between those types?

Is there any advantage in introducing a lot of types and a lot of operator overloading instead of using just plain floating point values to represent them?

Example:

class Time{...};
class Length{...};
class Speed{...};
...
Time operator""_s(long double val){...}
Length operator""_m(long double val){...}
...
Speed operator/(const Length&, const Time&){...}

Where Time, Length and Speed can be created only as a return type from different operators?

Protective answered 11/11, 2013 at 16:12 Comment(12)
This is usually done at compile time with templates. See boost.units for example.Sample
Also refer C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond by David Abrahams and Aleksey Gurtovoy - it discusses compile-time dimensional analysis in 3.1.Hatred
Here is a nice article discussing this exact issue with code: R. Cmelik, N. Gehani, "Dimensional Analysis with C++," IEEE Software, Volume 5 Issue 3, May 1988, Page 21-27. Sorry I don't have the PDF for you.Seaworthy
@bruce3141: That sounds rather strange, a C++ article which predates even ANSI C89.Serviceable
@MSalters, I'm not a guru on C++ development history; the article emphasizes the advantages of "C++versus Ada" and acknowledges help from Bjarne Stroustrup. Its legit, I just double-checked the date; the classes and examples look cool, check it out.Seaworthy
@I searched "Dimensional Analysis with C++" and I found this: se.ethz.ch/~meyer/publications/OTHERS/scott_meyers/…Protective
@bruce3141: Figured it out thanks to Scott Meyers. 1989 was run-time checking and that is of course possible in pretty much every language; the real breakthrough was Barton&Nackman 1994.Serviceable
@MSalters, Yes I came across the Scott Meyers work a while back; your right, the article by Barton & Nackman is the one that everyone refers too. I think it was one of the first early uses of templates as a construct for something other than a general container type, at least according to Meyers, I have not yet gotten hold of the article yet. Good discussion, thanks.Seaworthy
It makes a lot of sense! Bjarne Stroustrup himself made a presentation at my university a year back talking about just this. See slide 19 and onwards.Olszewski
@kba: There are way too many unrelated pictures in the slides. Can't figure what the heck elephants are supposed to do with C++ programming. "A light-weight abstraction programming language" Yeah, right. And it only takes 3 years to write a compiler for it. Every once in a while I start thinking that guys that maintain C++ standard have a very weird vision of what their language is supposed to be.Stratopause
@Stratopause These are just the lecture slides he used, I can't remember the relevance to elephants as it was over a year ago. My point is just that he demonstrated exactly that, so I think that's a pretty good guarantee that it makes sense. Besides, Stroustrup doesn't just maintain C++, he designed and implemented it.Olszewski
@Stratopause I think the elephant contrasts with the picture on the previous slide, showing the blind men with an elephant. The "light-weight" means the abstraction is light-weight, not the language, light-weight as in C++'s abstraction facilities add little to run-time overhead.Pupillary
P
56

Does it make sens in C++ to define physics units as separate types and define valid operations between those types?

Absolutely. The standard Chrono library already does this for time points and durations.

Is there any advantage in introducing a lot of types and a lot of operator overloading instead of using just plain floating point values to represent them?

Yes: you can use the type system to catch errors like adding a mass to a distance at compile time, without adding any runtime overhead.

If you don't feel like defining the types and operators yourself, Boost has a Units library for that.

Principium answered 11/11, 2013 at 16:17 Comment(2)
also to avoid endless overloads boost::operator comes in handy by automatically deriving overloads based on CRTPSauceda
Minor correction: you should use boost::units (or similar) "if you don't feel like defining the types and operators yourself," as you stated, or if you're a programmer who doesn't suffer from "Not Invented Here" syndrome and realizes the futility in reinventing the wheel. So basically, yeah, use it.Diaphanous
H
22

I would really recommend boost::units for this. It does all the conversion compile-time and also it gives you a compile time error if you're trying using erroneous dimensions psuedo code example:

length l1, l2, l3;
area a1 = l1 * l2; // Compiles
area a2 = l1 * l2 * l3; // Compile time error, an area can't be the product of three lengths.
volume v1 = l1 * l2 * l3; // Compiles
Huggermugger answered 11/11, 2013 at 16:41 Comment(0)
S
13

I've gone down this road. The advantages are all the normal numerous and good advantages of type safety. The disadvantages I've run into:

  • You'll want to save off intermediate values in calculations... such as seconds squared. Having these values be a type is somewhat meaningless (seconds^2 obviously isn't a type like velocity is).
  • You'll want to do increasingly complex calculations which will require more and more overloads/operator defines to achieve.

At the end of the day, it's extremely clean for simple calculations and simple purposes. But when math gets complicated, it's hard to have a typed unit system play nice.

Sampling answered 11/11, 2013 at 16:24 Comment(6)
Using auto for intermediates solves that problem :)Tieck
Constants like acceleration m/s^2 is actually meters-per-second-per-second, not meters-per-second-squared. I also disagree that a constant that has a seconds-squared in it would be meaningless. If that is what the units for that constant are, then store them that way. Doing a physics problem, you should certainly write the intermediate result on your page that way, even if it doesn't make sense on its own.Pomade
The "more overloads" bit is misleading. There are only 7 fundamental SI units, and all the others are rational powers of those 7. That means there's not a single overload needed; operator* only needs to add the 7 rational powers of both arguments.Serviceable
@Serviceable I'm not sure what you mean. I wrote my own system that exactly mimicked duration. I needed a unique type for not only the 7 fundamental units but also things like speed and acceleration, etc. Each of these types also needed operator overloads. Beyond operators though, things got tricky when trying to do something cube an acceleration, or other more complex operations.Sampling
#Dave: Length would typically be a typedef for something like unit<1,0,0,0,0,0,0>, time for unit<0,1,0,0,0,0,0>, and speed is obviously then unit<1,-1,0,0,0,0,0>. Multiplying length by length gives you unit<2,0,0,0,0,0,0> simply by addition, no other overload is needed.Serviceable
I believe that a lot of concern relating to typing of intermediate values could be addressed by using C++11 "auto" capability.Biolysis
G
10

Everyone has mentioned the type-safety guarantees as a plus. Another HUGE plus is the ability to abstract the concept (length) from the units (meter).

So for example, a common issue when dealing with units is to mix SI with metric. When the concepts are abstracted as classes, this is no longer an issue:

Length width = Length::fromMeters(2.0);
Length height = Length::fromFeet(6.5);
Area area = width * height; //Area is computed correctly!
cout << "The total area is " << area.toInches() << " inches squared.";

The user of the class doesn't need to know what units the internal-representation uses... at least, as long as there are no severe rounding issues.


I really wish more trigonometry libraries did this with angles, because I always have to look up whether they're expecting degrees or radians...

Gynaecology answered 11/11, 2013 at 21:34 Comment(9)
"abstract the concept (length) from the units (meter)." You know, while it kinda sounds useful, it also sounds a lot like it violates KISS principle.Stratopause
@SigTerm: Not true at all! In fact, it's the exact opposite: users of your class no longer need to worry about implementation details such as "feet" or "inches," and can focus on the broader concepts such as "distance" or "angle." This is the classic of example of when OOP is at its most useful!Gynaecology
(opinion) I guess it is matter of taste. Most countries use metric system and I kinda wouldn't expect anything physics-related to use feet and pounds instead of meters and kilograms. Your approach makes sense for some kind of storage database of (real-life) materials, accounting(?), web shops, shipping cost calculators, and similar applications. For those apps it makes sense to use quantities that can be converted to different units. But for physics computation it doesn't sound like a very useful feature.Stratopause
@Stratopause The problem being that that is disregarding the only country that really matters! JK, although I still think that feet and inches are far superior to meters.Pomade
@AJMansfield: Well, feet vs. meters is not really the point. It's just as easy to mix up kilometers and centimeters in code, with just as disastrous results. An API like this fixes that problem before it ever starts!Gynaecology
@AJMansfield: Not sure which country you're talking about.Stratopause
@BlueRaja-DannyPflughoeft: In my opinion, if this feature is not required by the API/library/app/whatever you're developing, it should not be implemented. Some people that love OOP have this problem when they get distracted and start producing objects for everything, which wastes development time. However, if this is a required feature, this is another story. As I said, this is a matter of taste, so there's no real reason to argue about it.Stratopause
@Stratopause Also, "I kinda wouldn't expect anything physics-related to use feet and pounds instead of meters and kilograms" is actually just plain wrong. In nearly every engineering discipline (aside from space exploration) in the United States, everything is done in feet (or yards) and pounds (or tons). All of civil, naval, most automotive, mechanical, electrical, and many others use customary units. Only researchers at universities use metric for things.Pomade
@AJMansfield: Physics-related means "research" to me, because there are SI units, and imperial units are not part of them. If your industry produces blueprints, then of course they'll be in local units... anyway, this is offtopic.Stratopause
T
4

For those looking for a powerful compile-time type-safe unit library, but are hesitant about dragging in a boost dependency, check out units. The library is implemented as a single .h file with no dependencies, and comes with a project to build unit tests/documentation. It's tested with msvc2013, 2015, and gcc-4.9.2, and should work with later versions of those compilers as well.

Full Disclosure: I'm the author of the library

Thistledown answered 9/2, 2016 at 12:52 Comment(0)
S
1

Yes, it makes sense. Not only in physics, but in any discipline. In finance, e.g. interest rates are in units of inverse time intervals (typically express per year). Money has many different units. Converting between them can only be done with a cross-rate, has dimensions of one currency divided by another. Interest payments, dividend payments, principal payments, etc. ordinarily occur at a frequency.

It can prevent multiplying two values and ending up with an illegal value. It can prevent summing dollars and euros, etc.

Soviet answered 12/11, 2013 at 23:23 Comment(0)
G
1

I'm not saying you're wrong to do so, but we've gone overboard with that on the project I'm working on and frankly I doubt its benefits outweigh its hassle. Particularly if you're on a team, good variable naming (just spell the darn things out), code reviews, and unit testing will prevent any problems. On the other hand, if you can use Boost, units might be something to check into (I haven't).

Geoffreygeoffry answered 14/11, 2013 at 16:5 Comment(0)
E
1

To check for type safety, you can use a dedicated library.

The most wiedly use is boost::units, it works perfertly with no execution time overhead, a lot of features. If this library theoritically solve your problem. From a more practical point of vew, the interface is so awkward and badly documented that you may have problems. Morever the compilation time increase drastically with the number of dimensions, so clearly check that you can compile in a reasonable time a large project before using it.

doc : http://www.boost.org/doc/libs/1_56_0/doc/html/boost_units.html

An alternative is to use unit_lite. There are less features than the boost library but the compilation is faster, the interface simpler and errors messages are readables. This lib requires C++11.

code : https://github.com/pierreblavy2/unit_lite

The link to the doc is in the github description (I'm not allowed to post more than 2 links here !!!).

Exalt answered 15/9, 2015 at 16:37 Comment(0)
B
1

I gave a tutorial presentation at CPPcon 2015 on the Boost.Units library. It's a powerful library that every scientific application should be using. But it's hard to use due to poor documentation. Hopefully my tutorial helps with this. You can find the slides/code here:

Biolysis answered 10/10, 2015 at 21:2 Comment(0)
F
0

If you want to use a very light weight header-only library based on c++20, you can use TU (Typesafe Units). It supports manipulation (*, /, +, -, sqrt, pow to arbitrary floating point numbers, unary operations on scalar units) for all SI units. It supports units with floating point dimensions like length^(d) where d is a decimal number. Moreover it is simple to define your own units. It also comes with a test suite...

...and yes, I'm the author of this library.

Fives answered 5/2, 2022 at 9:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.