8 ms·
Improving our safety with a physical quantities and units library
- jeffreygoesto 3y agoMateusz delivers high quality C++ code since a long time. Having implemented a similar library with C++ 03 in 2008 I highly appreciate the amount of attention to detail he invests here. We had numerous bugs in formulas found by that library over the years.
- padmasampath 3y ago[flagged]
- michaelt 3y agostd::vector<quantity<si::milli<si::seconds>>> vec; I've seen efforts like this in the past, but in my experience there's a substantial readability cost when you replace a single-line calculation with a multi-line one to accommodate giant type definitions like this. And you just know anyone who's adopting this library is also going to have long variable names and a strict line length limit, both of which they will describe as "clean code" :)
- jeffreygoesto 3y agoShorthands for some heavily used units are not forbidden. ;) For vectors you can take that one step further even... https://cpponsea.uk/2022/sessions/taking-static-type-safety-to-the-next-level-physical-units-for-matrices.html https://cpponsea.uk/2022/sessions/taking-static-type-safety-...
- Y_Y 3y agoDoesn't your calculation happen after you've declared your types? I agree that this is needlessly long, but in my experience you can say what the physical units of your input variables and constants are up top and the let everything else be inferred and your computations should look the same as ever, but with extra checking at compile time.
- bombela 3y agoAll the motivating examples but one are about mixing units from different system. And of those, all but one are about errors in mixing US Customary units with the metric system. Maybe Americans should just stop using the US Customary Units as a start?
- throwaway5959 3y ago[flagged]
- amphitheatre 3y agoActual scientific institutions like NASA already do, and all of the imperial units have metric backings and definitions now, so it’s just ignorance, stubbornness, or apathy at this point.
- throwaway5959 3y agoOh boy, NASA, a scientific organization using metric. Lemme know when you’ve converted every recipe, ruler and wrench to metric. So ridiculously out of touch.
- WJW 3y agoYes that would work, but why stop there. Having hundreds of different spoken languages is wasteful, so why not standardize everyone on Esperanto while we're at it, unify all electric plug standards to the one used in the EU and change the last few countries over to driving on the right side of the road. Don't even get me started on all the duplicate work being done on text editors, programming languages, web servers and databases. /s of course.
- smitty1e 3y agoPracticality over purity. How are we to divide a meter into whole number thirds? Easily done with yards. Get stuffed, Talleyrand.
- 3y ago
- cxx 3y agoLove this! I had to implement a half-assed version of this a long time ago at a job because there was a class of subtle bugs that popped up due to poor naming/documentation/spaghetti code that would've been eliminated by things like this.
- baq 3y agoThis is the one area I most sorely miss in Rust. Not that I don’t miss it anywhere else (I absolutely do, currently writing lots of TS and trying to make do with nominal types), but that it’s a very important safety mechanism in all of engineering and a unit/system of measure built into the language would fit so well.
- adrianN 3y agoJust use a crate? First Google hit https://docs.rs/uom/latest/uom/ https://docs.rs/uom/latest/uom/
- hedora 3y agoThat looks nice, but appears to only handle SI.
- nequo 3y agoThe crate defines square feet etc.: https://docs.rs/uom/latest/uom/si/area/index.html https://docs.rs/uom/latest/uom/si/area/index.html Am I missing something?
- notamy 3y agoIt has feet, inches, yards, and other less-used(?) units like chains and rods. What units are missing? https://docs.rs/uom/latest/uom/si/length/index.html https://docs.rs/uom/latest/uom/si/length/index.html
- ajxs 3y agoAdacore developed an interesting related system for the GNAT Ada compiler: https://www.adacore.com/gems/gem-136-how-tall-is-a-kilogram https://www.adacore.com/gems/gem-136-how-tall-is-a-kilogram
- chriswarbo 3y agoScala's squants library is a nice implementation of units-of-measure/dimensional-analysis http://www.squants.com http://www.squants.com In particular it uses types for dimensions; whilst units are just constructors. Hence `Meters(2)` and `Microns(7)` have the same type (`Length`).
- bee_rider 3y agoWhat do you get if you add Meters(2) to Microns(7), out of curiosity?
- chriswarbo 3y agoEach of the dimensions has a "primary unit", which the constructors convert into. For Length it's Meters, so Meters(2) stores a 2, whilst Microns(7) will store a 0.000007 https://www.javadoc.io/doc/org.typelevel/squants_2.13/1.6.0/squants/space/Length$.html https://www.javadoc.io/doc/org.typelevel/squants_2.13/1.6.0/...
- EnigmaFlare 3y agoSurely that spoils some cases where you use different units to keep the values of reasonable magnitude? Sometimes, such as with electromagnetics, the exponents are so extreme they lead to numerical error. Sometimes also, people (inappropriately) use arbitrary epsilons to compare to zero and end up nuking micron-scale dimensions expressed in meters. A volume of 1 μm^3 is 1e-18m^3 which you might not want to operate on too hard.
- SloopJon 3y agoNote that this is heavily dependent on C++20. I'm not even caught up on C++17, so I was thrown off by statements like this: quantity q1 = 42 * J; where quantity is a class template: template<Reference auto R, RepresentationOf<get_quantity_spec(R).character> Rep = double> class quantity; I didn't know you could use auto in a template parameter. I gather that Reference is a concept, something else I've yet to explore. Just when I thought I'd gotten the hang of "modern" C++.
- muraiki 3y agoOne option for C++14 is the Au units library. Here's a comparison to other unit libraries, including mp-units: https://aurora-opensource.github.io/au/main/alternatives/ https://aurora-opensource.github.io/au/main/alternatives/
- hn_go_brrrrr 3y agoauto in this context creates a non-type template parameter (NTTP). They've been in the language since C++11 (std::array<int, 3>).
- ynik 3y agoNon-type template parameters (NTTP) were already in the original standard, they've existed since C++ had templates. Being able to use "auto" for the type of the NTTP was new in C++17; and using class types like std::array for NTTPs was only added in C++20.
- aaauaucuggaa 3y agoIf working in python, Pint is an excellent choice: https://pypi.org/project/Pint/ https://pypi.org/project/Pint/
- owlstuffing 3y agoThe manifold[1] project for Java lets you write unit expressions directly. Force force = 5kg * 9.807 m/s/s; 1. https://github.com/manifold-systems/manifold/tree/master/manifold-deps-parent/manifold-ext#unit-expressions https://github.com/manifold-systems/manifold/tree/master/man...
- SloopJon 3y agoThere is an FAQ for the mp-units project on which this proposal is based about why they chose not to use user-defined literals: https://mpusz.github.io/mp-units/latest/getting_started/faq/ https://mpusz.github.io/mp-units/latest/getting_started/faq/ If I understand correctly, though, the unit expressions that manifold implements aren't limited to literals, is that correct?
- owlstuffing 3y ago>the unit expressions that manifold implements aren't limited to literals, is that correct? Correct. Unit expressions with manifold are more expressive than with mp-units, as I understand it. With mp-units the unit types themselves are expressively inactive. For instance, with mp-units the type for velocity, _q_m_per_s (?), must be defined statically as a literal. But with manifold it may be defined as an expression in terms of the component unit types. As such the unit for Velocity, m/s, is an _expression_ of dimension units LengthUnit and TimeUnit. Velocity sixtyFiveMilesPerHour = 65 mi/hr; And this provides for user defined literals. VelocityUnit mph = mi/hr; . . . Length distance = 80mph * 3.5hr; It's also a convenience that applies to unitless expressions. For example, manifold defines a Rational number type, which defines a unit-based coercer: RationalCoercion r = RationalCoercion.INSTANCE; . . . var oneThird = 1r/3; Which satisfies other uses, such as: var sixMillionDollars = 6M USD;
- noobermin 3y agoWhile this is a nice thing to have I dread the day that codes start to require people get with this. Yes, it's a source of bugs, sometimes people need to accept that bugs naturally arise because of something called the undecideability and we need to stop shoehorning things into languages to make things safe. It makes sense in a host of features but the problem with these sorts of features is they make a lot of a assumptions, including the fact that codes with units will actually be better with units.
- deleted 3y ago[deleted]
- thfuran 3y agoYou think that a lot of arithmetic bugs are due to the math being undecidable?
- nraynaud 3y agoI am curious about people's experience with this kind of system, I have been thinking about it for years, but never actually tried it. I sometimes feel like finding the explicit unit of some sub-expressions in geometry might be complicated. I know "auto" avoids that particular problem, but I don't have a good policy for when to put "auto" to avoid expressing a complex type and when to explicit the type to block the propagation of errors. Also during debugging we might need to display some customary unit for sub expressions/watchpoints because the SI unit means nothing to humans in some fields (pressure comes to mind, some people use mm of water, mm of mercury, bars, etc.)
- nicklecompte 3y agoIt is built in to F# and I think it's a very underrated feature of the language for certain applications, although it comes with some overhead and tedium. There is a cost. I used F# for financial analysis in the power industry, where there is just an atrocious lack of unit standardization leading to tons of subtle bugs. It's not just stuff about volts and kilowatt hours: I have nightmares about a function that wanted on-peak hourly averages but the caller gave it overall hourly averages - or did it??? By itself, the debugger does not really help you figure out what's going on here unless you are eagle-eyed about time zones. But the compiler can complain if you gave it an `float<price/hour> array` instead of `float<peakPrice/hour> array`, which makes the source of the bug much more transparent, or prevents the bug from occurring at all. Implementing certain computations in F# instead of C# saved me a lot of time and headaches, while also making the code more descriptive to humans. And of course the compiler also verifies things like kilowatts versus megawatts, etc. It's a specific use case: many intricate floating-point computations with highly sophisticated business logic, where common bugs can be avoided by simple labelling of the units. It is all static, so there isn't boxing or performance penalty, but it does have syntactical overhead. And of course the compiler doesn't know physics - you can invalidly coerce things by arbitrarily multiplying with `1<newUnit/oldUnit>.` What you're saying about some subexpressions is true, but the units will only ever be in simple algebraic combinations so you won't get any truly confounding `decltype` head-scratchers. It might be tedious but it'll never be particularly deep. In practice, yes, it gets annoying. But in many cases I found the annoyance was a helpful check on my thinking - "somehow I got dollars^2/week, that doesn't make sense, what did I do?" Instead of `auto`, if the units are getting too tedious I think it's better to cast down to floats/etc and do things the old fashioned way, with helpful documentation, then cast to a unit further up the call stack, where things may be more transparent. You can see how it works in F# here: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/units-of-measure https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref... They are smaller examples, so it is hard to judge the safety-overhead from this alone. But I think they have a nice syntax, and clever ways to use generics. > Also during debugging we might need to display some customary unit for sub expressions/watchpoints because the SI unit means nothing to humans in some fields (pressure comes to mind, some people use mm of water, mm of mercury, bars, etc.) It seems like this would be in principle easy to add to a debugger, as long as all those units are in scope of the function. But it would be a very specific feature. And I am not sure if it's a good idea for software engineers to be debugging things in different units than the computer is currently working with - what if the bug is in the unit conversion itself? This just seems like a confusing practice.
- ahupp 3y agoIf you're interested in this kind of thing, I wrote a python package called "unit-syntax"(https://github.com/ahupp/unit-syntax https://github.com/ahupp/unit-syntax) that adds physical unit syntax to python: >>> speed = 5 meters/second >>> (2 seconds) * speed 10 meter I'd been using Jupyter notebooks as a calculator for engineering problems and was wishing for the clarity and type safety of real units, but with the succinctness of regular python. It works in both Jupyter notebooks (with an input transform) and stock python (with an import hook). The actual unit checks and conversions are handled by the excellent `pint` package.
- chrismorgan 3y agoI don’t understand why you’d go to the enormous trouble of extending the syntax when you can write it with perfectly normal syntax. A value-with-unit is, after all, just a scalar multiplied by the unit, and I suppose this would work with the underlying library pint: >>> speed = 5*meters/second >>> (2 * seconds) * speed 10 meter Given the ecosystem problems and other problems extending the syntax gets you, I don’t get why you’d do this at all, when all it gets you is the ability to write ' ' instead of '*'.
- tesdinger 3y agoYou neglected to say that this does automatically determine the type instead of type checking as in the article. You could also use python type hints for this stuff. Here are some links about units in python: https://socialcompare.com/en/comparison/python-units-quantities-packages https://socialcompare.com/en/comparison/python-units-quantit...
- ahupp 3y agoIMO using operator overloading for this kind of thing makes it hard to read, since I have to be extra careful to mentally parse whether that `*` is a multiplication or units, remember what variables are in scope etc. Notation matters, and if I didn't care about that I'd just write `pint.Quantity(5, "meters/second")` and be done with it. Or more likely, not go to the trouble of using them at all. > I don’t understand why you’d go to the enormous trouble But more importantly, it was just really fun to get it working.
- majorexception 3y agoI have a project for which I wrote a simple units library. I don't think I'd be able to write any physics-related project now without using it (or a similar library). My Quantity class has a set of 8 parameters (7 SI base units and a hack that allows conversion between Hz and radians) + additional Scale and Offset parameters. Scale allows representing units other than SI (like Nautical Miles), Offset is for units like Celsius, for which 0°C == 273.15 K. I can do things like: si::Length length = 15_m + 12_nm; // _nm for Nautical Miles si::Area area = 1_m * 1_km; // Equals to 1000_m2 si::Power power = 1_m / 1_sec / 1_sec; // Compilation error, 1_m/s² is not a si::Power I don't have every possible User-Defined Literal, of course, so I end up doing this for less common units: using SomeLocalTypeName = decltype(1_rpm / 1_V); Something to thing about, when designing such library: * What is 0_degC + 1_degC? 1_degC or… 273.15_K + 274.15_K = 547.3_K = 274.15_degC? I forbid operations between units if any of them has Offset parameter different than 0. I'm not sure if this is the good solution, though. * Nm (Newton-meters) is the same unit as Joules. ;-)
- pests 3y agoFor your first point about temp, the proper way would be to convert to Kelvin, do the addition, and back to Celsius.
- majorexception 3y agoYup. So I explicitly deleted operators for °C, so things like 5_degC + 10_degC won't compile.
- pests 3y agoBut cant you just implement operator+(celcius, celcius) that will do the conversion to kelvin and back, since you know for certain the conversion math?
- majorexception 3y agoI did that, but then adding 0_degC + 0_degC gave 273.15_degC. I think that would be surprising to many people. In reality I don't remember ever having to deal with physics formulas that used °C instead of Kelvins, so the only time I need to use °C is when presenting the temperature in the UI: temperature.in<si::Celsius>()
- nicklecompte 3y agoSurprised that I'm the only person so far mentioning F#, where units of measure have been built in to the language since 1.0: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/units-of-measure https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref... I wrote a longer comment in reply to nraynaud elsewhere describing how I found it useful for certain things at a past job. It's hardly a killer feature, but it's very nice to have the compiler (partially) verify the semantic correctness of certain floating-point computations. It's not just physics - finance has "units" as well, and the compiler can complain if you gave a function an array of dollars/week when it wanted dollars/month. If you don't mind writing some (admittedly tedious) overhead it can save a lot of headaches.
- tesdinger 3y agoGiven that months have varying amounts of days, does it even make sense to have both dollars/month and dollars/day in the same setting? I would figure you would use either one or the other.
- nicklecompte 3y ago> does it even make sense to have both dollars/month and dollars/day in the same setting? Parking downtown often has daily and monthly rates: is it cheaper to pay for two months, or one month and ten days? If you're doing the math yourself it's immediately obvious if you accidentally swap the rates, but not so if you write a program that swaps the variables. You wouldn't write a program at all for this example, but it's a legitimate use case. Your general point is correct: it typically doesn't make sense to have them in the same setting if by "setting" you mean "specific equation." But they could very well be in the same program.
- tesdinger 3y ago> Although it makes physical sense to add heights of daily climbs, there is no sense in adding altitudes. What if I want to compute the average altitude? This requires adding altitudes.
- vitaminCPP 3y agoDoes anybody know about a unit library in C?