Au 103: Unit Conversions Exercise¶
Note
This is the exercise for the concepts in the tutorial page Au 103: Unit Conversions. We’ll assume you’re familiar with everything in that page, so if you’re not, you’ll want to read it first.
Tip
Before you start, make sure you’ve followed the developer’s guide so you’re able to build and test the code!
Introduction¶
This exercise is a chance to practice unit conversions.
The file we’ll be working in can be found here, relative to the repository root:
"tutorial/103_unit_conversions_test.cc"
The following command executes the tests.
Exercise 1: ad hoc conversions¶
We’ll practice writing some ad hoc conversions that can replace their more labor-intensive, error-prone manual counterparts.
Since these both start and end with raw numeric types, we’ll use .in
rather than .as
, even
though the latter is generally preferred whenever we have a choice. (Perhaps a future refactoring
could replace these raw numeric types with quantities in the rest of this hypothetical
codebase. )
Open up the file, "tutorial/103_unit_conversions_test.cc"
, and scroll down to the EXERCISE 1
section. This takes you to two unit tests in the AdHocConversions
group: DegreesToRadians
, and
MilesPerHourToMetersPerSecond
.
In each test, find the unit conversion, which is currently done with manual conversion factors. Replace it with an ad hoc inline conversion based on Au.
Exercise 1(a)
Convert the DegreesToRadians
test:
TEST(AdHocConversions, DegreesToRadians) {
constexpr double angle_deg = 135.0;
constexpr double RAD_PER_DEG = M_PI / 180.0;
// TODO: replace `angle_rad` computation with an ad hoc conversion, using Au.
constexpr double angle_rad = angle_deg * RAD_PER_DEG;
EXPECT_DOUBLE_EQ(angle_rad, 3.0 * M_PI / 4.0);
}
A possible solution:
TEST(AdHocConversions, DegreesToRadians) {
constexpr double angle_deg = 135.0;
constexpr double angle_rad = degrees(angle_deg).in(radians);
EXPECT_DOUBLE_EQ(angle_rad, 3.0 * M_PI / 4.0);
}
Tip
Instead of deleting the obsolete lines, we replaced them with blank lines. This lets you go back and forth, using the left and right arrow keys, to see exactly what changed.
We start with a unit-safe handoff, degrees(angle_deg)
, from the _deg
suffix to the
degrees()
function. Then we end with another unit-safe handoff: from .in(radians)
to
the _rad
“suffix” in the variable name.
Overall, we can see that this conversion is correct by just reading this single line of code. That’s unit safety!
Exercise 1(b)
Convert the MilesPerHourToMetersPerSecond
test:
TEST(AdHocConversions, MilesPerHourToMetersPerSecond) {
constexpr double speed_mph = 65.0;
// Carefully compute conversion factor manually.
constexpr double M_PER_CM = 0.01;
constexpr double CM_PER_INCH = 2.54;
constexpr double INCHES_PER_FEET = 12.0;
constexpr double FEET_PER_MILE = 5280.0;
constexpr double M_PER_MILE = M_PER_CM * CM_PER_INCH * INCHES_PER_FEET * FEET_PER_MILE;
constexpr double S_PER_H = 3600.0;
constexpr double MPS_PER_MPH = M_PER_MILE / S_PER_H;
// TODO: replace `speed_mps` computation with an ad hoc conversion, using Au.
constexpr double speed_mps = speed_mph * MPS_PER_MPH;
EXPECT_DOUBLE_EQ(speed_mps, 29.0576);
}
A possible solution:
TEST(AdHocConversions, MilesPerHourToMetersPerSecond) {
constexpr double speed_mph = 65.0;
constexpr double speed_mps = (miles / hour)(speed_mph).in(meters / second);
EXPECT_DOUBLE_EQ(speed_mps, 29.0576);
}
Tip
Instead of deleting the obsolete lines, we replaced them with blank lines. This lets you go back and forth, using the left and right arrow keys, to see exactly what changed.
As before, we start and end our conversion with unit-safe handoffs. The units themselves are a little more complicated than before because they’re compound units, but overall this Au-based conversion has similar readability to the one from the previous example.
We can’t say the same for the code it replaced, however — it’s much more complicated
than the previous example! In order to write it in a form which is clearly correct, we need
to chain together a string of carefully named elementary conversion factors. Even once we
do, it probably takes some squinting to convince yourself that MPS_PER_MPH
really is
“meters-per-mile” divided by “seconds-per-hour”.
The Au-based conversion is a huge improvement in readability and simplicity, as evidenced by all the whitespace above (which we can now remove).
Exercise 2: decomposing inches onto feet-and-inches¶
Now we want to take a height in inches, and decompose it so we can represent it as some integer
number of feet, plus some integer number of inches which measures less than a foot. Scroll down to
the function decompose_height()
, and fill in the PLACEHOLDER
instances with correct expressions
that will make the test below pass.
You may find it useful to use the explicit-Rep overload to force a conversion that is generally risky, but in this instance is known to be OK. You should not need to use floating point numbers at all.
Exercise 1(b)
Replace PLACEHOLDER
instances with correct expressions.
// Decompose a height, given in inches, into the largest whole number of feet, plus the leftover
// inches. For example, `inches(17)` would be decomposed into `Height{feet(1), inches(5)}`.
Height decompose_height(QuantityU32<Inches> total_height) {
Height h;
h.feet = PLACEHOLDER;
h.inches = PLACEHOLDER;
return h;
}
A possible solution:
// Decompose a height, given in inches, into the largest whole number of feet, plus the leftover
// inches. For example, `inches(17)` would be decomposed into `Height{feet(1), inches(5)}`.
Height decompose_height(QuantityU32<Inches> total_height) {
Height h;
h.feet = total_height.coerce_as(feet); // NOTE: truncation is intended.
h.inches = total_height - h.feet.as(inches);
return h;
}
If we convert the total height from inches to feet, and force it to remain an integer, it will truncate. In this case, that’s actually the desired behaviour! (Of course, it’s nice to leave a comment to reassure the reader.) We can then reduce the total height by this integral number of feet, and we obtain the leftover inches.
Tip
As it happens, the .as(inches)
is unnecessary. You can subtract a QuantityU32<Feet>
from a QuantityU32<Inches>
directly! If you do, the quantity of feet will be
automatically converted to inches, because inches is a “common unit” in which we can
express both quantities. However, common units will be the topic of a later, 200-level
tutorial, so we wrote the example solution using only constructs you’ve already seen.
Summary¶
This exercise gave a few worked examples of unit conversions. We saw how Au can immediately replace
crufty, error-prone manual conversions, making code more readable. We also saw a practical example
of quantity-to-quantity conversion using .as(...)
, including one example where the forcing
“explicit-Rep” form is clearly safe and correct to use.
Return to the main tutorial page.