Skip to content

Commit cd25d11

Browse files
authored
Support adding and subtracting Constant (#657)
Now that we have `UnitSum<...>` in place, it has become straightforward to get the labels we want. The trickiest bit here is when the constant fully cancels additively. Recall that we are grouping inputs according to the _unscaled_ units (roughly corresponding to "actual unit symbols"). When every unscaled unit additively cancels _individually_, the result is zero. When we end up with multiple unscaled units which nevertheless cancel together, we represent that as-is, and don't try to cancel it, because this is also how we handle input units in multiplication and division. But the result is still not-a-unit! And we have the static assert from the parent. From an external user's point of view, we simply recommend that they _not try to form_ any `Constant` that additively cancels, regardless of whether it produces `Zero` or a compiler error. This is a weird thing to do that probably doesn't have any actual use cases. We already don't support all combinations of inputs, and don't plan to, and this feels like a safe "starter policy" at the very least. Fixes #607.
1 parent fe4ede1 commit cd25d11

File tree

3 files changed

+244
-0
lines changed

3 files changed

+244
-0
lines changed

au/constant.hh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,4 +211,35 @@ constexpr Zero operator%(Zero, Constant<U>) {
211211
return {};
212212
}
213213

214+
// Addition (+) for `Constant`.
215+
template <typename U1, typename U2>
216+
constexpr auto operator+(Constant<U1>, Constant<U2>) {
217+
return make_constant(UnitSum<U1, U2>{});
218+
}
219+
220+
// Subtraction (-) for `Constant`.
221+
template <typename U1, typename U2>
222+
constexpr auto operator-(Constant<U1>, Constant<U2>) {
223+
using NegU2 = decltype(U2{} * Magnitude<Negative>{});
224+
return make_constant(UnitSum<U1, NegU2>{});
225+
}
226+
227+
// Arithmetic operators mixing `Constant` with `Zero`.
228+
template <typename U>
229+
constexpr Constant<U> operator+(Constant<U>, Zero) {
230+
return {};
231+
}
232+
template <typename U>
233+
constexpr Constant<U> operator+(Zero, Constant<U>) {
234+
return {};
235+
}
236+
template <typename U>
237+
constexpr Constant<U> operator-(Constant<U>, Zero) {
238+
return {};
239+
}
240+
template <typename U>
241+
constexpr auto operator-(Zero, Constant<U>) {
242+
return -Constant<U>{};
243+
}
244+
214245
} // namespace au

au/constant_test.cc

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,5 +523,174 @@ TEST(Constant, OrderingWorksWithScaledConstants) {
523523
EXPECT_THAT(double_c, Gt(half_c));
524524
}
525525

526+
TEST(ConstantAddition, SameConstantsDoubleTheValue) {
527+
constexpr auto one_foot = make_constant(feet);
528+
constexpr auto result = one_foot + one_foot;
529+
EXPECT_THAT(result,
530+
AllOf(Eq(make_constant(feet * mag<2>())), ConstantLabelIs(StrEq("[2 ft]"))));
531+
}
532+
533+
TEST(ConstantAddition, ResultTypeIsConstantOfUnitSum) {
534+
using OneFoot = Constant<Feet>;
535+
using OneInch = Constant<Inches>;
536+
using ResultUnit = UnitSum<Feet, Inches>;
537+
StaticAssertTypeEq<decltype(OneFoot{} + OneInch{}), Constant<ResultUnit>>();
538+
}
539+
540+
TEST(ConstantAddition, DifferentUnitsProduceUnitSum) {
541+
constexpr auto one_foot = make_constant(feet);
542+
constexpr auto one_inch = make_constant(inches);
543+
constexpr auto result = one_foot + one_inch;
544+
EXPECT_THAT(
545+
result,
546+
AllOf(Eq(inches(13)), ConstantLabelIs(AnyOf(StrEq("(ft + in)"), StrEq("(in + ft)")))));
547+
}
548+
549+
TEST(ConstantAddition, DifferentScalesOfSameUnitCombine) {
550+
constexpr auto two_feet = make_constant(feet * mag<2>());
551+
constexpr auto three_feet = make_constant(feet * mag<3>());
552+
constexpr auto result = two_feet + three_feet;
553+
EXPECT_THAT(result,
554+
AllOf(Eq(make_constant(feet * mag<5>())), ConstantLabelIs(StrEq("[5 ft]"))));
555+
}
556+
557+
TEST(ConstantAddition, OppositeConstantsCancelToZero) {
558+
constexpr auto one_foot = make_constant(feet);
559+
constexpr auto neg_foot = -one_foot;
560+
StaticAssertTypeEq<decltype(one_foot + neg_foot), Zero>();
561+
}
562+
563+
TEST(ConstantAddition, PartialCancellationLeavesRemainder) {
564+
constexpr auto three_feet = make_constant(feet * mag<3>());
565+
constexpr auto neg_two_feet = make_constant(feet * (-mag<2>()));
566+
constexpr auto result = three_feet + neg_two_feet;
567+
EXPECT_THAT(result, AllOf(Eq(make_constant(feet)), ConstantLabelIs(StrEq("ft"))));
568+
}
569+
570+
TEST(ConstantAddition, AddingZeroReturnsOriginal) {
571+
StaticAssertTypeEq<decltype(Constant<Feet>{} + ZERO), Constant<Feet>>();
572+
}
573+
574+
TEST(ConstantAddition, ZeroPlusConstantReturnsConstant) {
575+
StaticAssertTypeEq<decltype(ZERO + Constant<Feet>{}), Constant<Feet>>();
576+
}
577+
578+
TEST(ConstantAddition, MultipleUnitsCollectLikeTerms) {
579+
constexpr auto two_feet = make_constant(feet * mag<2>());
580+
constexpr auto one_inch = make_constant(inches);
581+
constexpr auto one_foot = make_constant(feet);
582+
constexpr auto result = two_feet + one_inch + one_foot;
583+
584+
EXPECT_THAT(
585+
result,
586+
AllOf(Eq(inches(37)), ConstantLabelIs(AnyOf(StrEq("(in + 3 ft)"), StrEq("(3 ft + in)")))));
587+
}
588+
589+
TEST(ConstantSubtraction, SameConstantsYieldZero) {
590+
constexpr auto one_foot = make_constant(feet);
591+
StaticAssertTypeEq<decltype(one_foot - one_foot), Zero>();
592+
}
593+
594+
TEST(ConstantSubtraction, ResultTypeIsConstantOfUnitSum) {
595+
using OneFoot = Constant<Feet>;
596+
using OneInch = Constant<Inches>;
597+
using NegInch = decltype(Inches{} * (-mag<1>()));
598+
using ResultUnit = UnitSum<Feet, NegInch>;
599+
StaticAssertTypeEq<decltype(OneFoot{} - OneInch{}), Constant<ResultUnit>>();
600+
}
601+
602+
TEST(ConstantSubtraction, DifferentUnitsProduceUnitSum) {
603+
constexpr auto one_foot = make_constant(feet);
604+
constexpr auto one_inch = make_constant(inches);
605+
constexpr auto result = one_foot - one_inch;
606+
607+
EXPECT_THAT(result, AllOf(Eq(inches(11)), ConstantLabelIs(StrEq("(ft - in)"))));
608+
}
609+
610+
TEST(ConstantSubtraction, DifferentScalesOfSameUnitCombine) {
611+
constexpr auto five_feet = make_constant(feet * mag<5>());
612+
constexpr auto two_feet = make_constant(feet * mag<2>());
613+
constexpr auto result = five_feet - two_feet;
614+
EXPECT_THAT(result,
615+
AllOf(Eq(make_constant(feet * mag<3>())), ConstantLabelIs(StrEq("[3 ft]"))));
616+
}
617+
618+
TEST(ConstantSubtraction, SubtractingZeroReturnsOriginal) {
619+
StaticAssertTypeEq<decltype(Constant<Feet>{} - ZERO), Constant<Feet>>();
620+
}
621+
622+
TEST(ConstantSubtraction, ZeroMinusConstantReturnsNegative) {
623+
StaticAssertTypeEq<decltype(ZERO - Constant<Feet>{}), decltype(-Constant<Feet>{})>();
624+
}
625+
626+
TEST(ConstantSubtraction, SubtractingNegativeAdds) {
627+
constexpr auto one_foot = make_constant(feet);
628+
constexpr auto neg_foot = -one_foot;
629+
constexpr auto result = one_foot - neg_foot;
630+
EXPECT_THAT(result,
631+
AllOf(Eq(make_constant(feet * mag<2>())), ConstantLabelIs(StrEq("[2 ft]"))));
632+
}
633+
634+
TEST(ConstantSubtraction, ResultCanBeNegative) {
635+
constexpr auto two_feet = make_constant(feet * mag<2>());
636+
constexpr auto five_feet = make_constant(feet * mag<5>());
637+
constexpr auto result = two_feet - five_feet;
638+
EXPECT_THAT(result,
639+
AllOf(Eq(make_constant(feet * (-mag<3>()))), ConstantLabelIs(StrEq("[-3 ft]"))));
640+
}
641+
642+
TEST(ConstantAddition, FractionalCoefficientsWork) {
643+
constexpr auto half_foot = make_constant(feet / mag<2>());
644+
constexpr auto quarter_foot = make_constant(feet / mag<4>());
645+
constexpr auto result = half_foot + quarter_foot;
646+
EXPECT_THAT(result,
647+
AllOf(Eq(make_constant(feet * mag<3>() / mag<4>())),
648+
ConstantLabelIs(StrEq("[(3 / 4) ft]"))));
649+
}
650+
651+
TEST(ConstantAddition, CommutativeProperty) {
652+
constexpr auto one_foot = make_constant(feet);
653+
constexpr auto one_meter = make_constant(meters);
654+
EXPECT_THAT(one_foot + one_meter, Eq(one_meter + one_foot));
655+
}
656+
657+
TEST(ConstantSubtraction, AntiCommutativeProperty) {
658+
constexpr auto one_foot = make_constant(feet);
659+
constexpr auto one_meter = make_constant(meters);
660+
EXPECT_THAT(one_foot - one_meter, Eq(-(one_meter - one_foot)));
661+
}
662+
663+
TEST(ConstantAddition, NegativeFirstTermShowsMinusPrefix) {
664+
constexpr auto neg_foot = make_constant(feet * (-mag<1>()));
665+
constexpr auto one_inch = make_constant(inches);
666+
667+
// Positive coefficients come before negative, so result is (in + (-ft)) = (in - ft).
668+
constexpr auto result = neg_foot + one_inch;
669+
EXPECT_THAT(result, ConstantLabelIs(StrEq("(in - ft)")));
670+
}
671+
672+
TEST(ConstantAddition, BothNegativeShowsMinusSigns) {
673+
constexpr auto neg_foot = make_constant(feet * (-mag<1>()));
674+
constexpr auto neg_inch = make_constant(inches * (-mag<1>()));
675+
676+
// Both negative, order depends on InOrderFor.
677+
constexpr auto result = neg_foot + neg_inch;
678+
EXPECT_THAT(result, ConstantLabelIs(AnyOf(StrEq("(-in - ft)"), StrEq("(-ft - in)"))));
679+
}
680+
681+
TEST(ConstantAddition, ScaledUnitsShowScaleInLabel) {
682+
constexpr auto two_feet = make_constant(feet * mag<2>());
683+
constexpr auto three_inches = make_constant(inches * mag<3>());
684+
constexpr auto result = two_feet + three_inches;
685+
EXPECT_THAT(result, ConstantLabelIs(AnyOf(StrEq("(2 ft + 3 in)"), StrEq("(3 in + 2 ft)"))));
686+
}
687+
688+
TEST(ConstantSubtraction, NegativeFractionalScaleShowsCorrectly) {
689+
constexpr auto one_foot = make_constant(feet);
690+
constexpr auto three_quarters_inch = make_constant(inches * mag<3>() / mag<4>());
691+
constexpr auto result = one_foot - three_quarters_inch;
692+
EXPECT_THAT(result, ConstantLabelIs(StrEq("(ft - (3 / 4) in)")));
693+
}
694+
526695
} // namespace
527696
} // namespace au

docs/reference/constant.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,3 +574,47 @@ constexpr auto seven_inches = make_constant(inches * mag<7>());
574574
// 5 feet = 60 inches; 60 % 7 = 4
575575
constexpr auto result = five_feet % seven_inches; // Constant representing 4 inches
576576
```
577+
578+
### Addition and subtraction †
579+
580+
Two `Constant` instances of the same dimension can be added or subtracted, producing a new
581+
`Constant`. Like other `Constant` operations, this is performed purely at compile time.
582+
583+
!!! warning
584+
Unlike "core" operations, such as powers and products, `Constant` is not fully closed under
585+
addition and subtraction. If the inputs completely additively cancel, the result will either be
586+
`Zero`, or else a compile time error.
587+
588+
Users should generally avoid forming this kind of `Constant`.
589+
590+
`Constant` also interacts with `Zero` for addition and subtraction:
591+
592+
- Adding `Zero` to a `Constant` (in either order) returns the original `Constant`.
593+
- Subtracting `Zero` from a `Constant` returns the original `Constant`.
594+
- Subtracting a `Constant` from `Zero` returns the negation of that `Constant`.
595+
596+
_This feature is subject to the same [compile-time arithmetic
597+
limitations](./magnitude.md#compile-time-arithmetic-limitations) as `Magnitude` arithmetic, because
598+
the computation is built on `Magnitude` operations._
599+
600+
**Syntax:**
601+
602+
For `Constant` instances `c1` and `c2` with the same dimension:
603+
604+
- `c1 + c2`
605+
- `c1 - c2`
606+
607+
**Result:** Whenever the result is nonzero, a new `Constant` representing the sum or difference.
608+
609+
**Example:**
610+
611+
```cpp
612+
constexpr auto one_foot = make_constant(feet);
613+
constexpr auto one_inch = make_constant(inches);
614+
615+
// Sum of 1 foot and 1 inch
616+
constexpr auto sum = one_foot + one_inch; // Constant representing (ft + in)
617+
618+
// Difference
619+
constexpr auto diff = one_foot - one_inch; // Constant representing (ft - in)
620+
```

0 commit comments

Comments
 (0)