@@ -52,7 +52,24 @@ import std;
5252namespace mp_units ::detail {
5353
5454template <typename T>
55- concept MagArg = std::integral<T> || is_mag_constant<T>;
55+ concept MagArg = std::integral<T> || is_mag_constant<T> || is_same_v<T, ratio>;
56+
57+ /* *
58+ * @brief A sentinel type representing the factor (-1) in a unit magnitude.
59+ *
60+ * Always appears as the first element in a pack when present. Two occurrences cancel each other out.
61+ * This enables support for negative magnitudes in named constants.
62+ */
63+ struct negative_tag {};
64+
65+ template <auto H, auto ...>
66+ consteval auto first_mag_arg ()
67+ {
68+ return H;
69+ }
70+
71+ template <typename M>
72+ constexpr bool is_negative_tag = is_same_v<MP_UNITS_REMOVE_CONST(M), negative_tag>;
5673
5774/* *
5875 * @brief Any type which can be used as a basis vector in a power_v.
@@ -91,10 +108,18 @@ template<typename T>
91108 return get_base_value (T::base);
92109 else if constexpr (is_mag_constant<T>)
93110 return element._value_ ;
111+ else if constexpr (is_negative_tag<T>)
112+ return std::intmax_t {-1 }; // sorts before all positive prime bases
94113 else
95114 return element;
96115}
97116
117+ template <MagArg auto V>
118+ constexpr bool is_nonzero_mag_arg = get_base_value(V) != 0 ;
119+
120+ template <MagArg auto V>
121+ constexpr bool is_positive_mag_arg = get_base_value(V) > 0 ;
122+
98123template <auto V, ratio R>
99124[[nodiscard]] consteval auto power_v_or_T ()
100125{
@@ -129,6 +154,13 @@ template<typename T>
129154 //
130155 // Note that since this function should only be called at compile time, the point of these
131156 // terminations is to act as "static_assert substitutes", not to actually terminate at runtime.
157+
158+ // The negative_tag sentinel represents the factor (-1).
159+ if constexpr (is_negative_tag<decltype (get_base (el))>) {
160+ static_assert (!std::is_unsigned_v<T>, " Cannot represent a negative magnitude value in an unsigned type" );
161+ return widen_t <T>{-1 };
162+ }
163+
132164 const auto exp = get_exponent (el);
133165
134166 if (exp.num < 0 ) {
@@ -156,18 +188,28 @@ template<typename T>
156188
157189[[nodiscard]] consteval bool is_rational_impl (auto element)
158190{
159- return std::is_integral_v<decltype (get_base (element))> && get_exponent (element).den == 1 ;
191+ if constexpr (is_negative_tag<decltype (element)>)
192+ return true ; // (-1) is a rational number
193+ else
194+ return std::is_integral_v<decltype (get_base (element))> && get_exponent (element).den == 1 ;
160195}
161196
162197[[nodiscard]] consteval bool is_integral_impl (auto element)
163198{
164- return is_rational_impl (element) && get_exponent (element).num > 0 ;
199+ if constexpr (is_negative_tag<decltype (element)>)
200+ return true ; // (-1) is an integer
201+ else
202+ return is_rational_impl (element) && get_exponent (element).num > 0 ;
165203}
166204
167205[[nodiscard]] consteval bool is_positive_integral_power_impl (auto element)
168206{
169- auto exp = get_exponent (element);
170- return exp.den == 1 && exp.num > 0 ;
207+ if constexpr (is_negative_tag<decltype (element)>)
208+ return false ; // (-1) is not a positive factor
209+ else {
210+ auto exp = get_exponent (element);
211+ return exp.den == 1 && exp.num > 0 ;
212+ }
171213}
172214
173215// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -195,9 +237,19 @@ template<auto M>
195237[[nodiscard]] consteval auto only_negative_mag_constants (unit_magnitude<M> m);
196238
197239template <MagArg auto Base, int Num, int Den = 1 >
198- requires (get_base_value(Base) > 0 )
240+ requires is_positive_mag_arg<Base>
199241[[nodiscard]] consteval UnitMagnitude auto mag_power_lazy ();
200242
243+ // Forward declarations; fully defined after unit_magnitude
244+ template <auto H, auto ... Rest>
245+ [[nodiscard]] consteval auto abs_magnitude (unit_magnitude<H, Rest...>);
246+ [[nodiscard]] consteval auto abs_magnitude (unit_magnitude<>);
247+
248+ template <int Num, int Den>
249+ [[nodiscard]] consteval auto pow_magnitude (unit_magnitude<>);
250+ template <int Num, int Den, auto H, auto ... Rest>
251+ [[nodiscard]] consteval auto pow_magnitude (unit_magnitude<H, Rest...>);
252+
201253template <typename T>
202254struct magnitude_base {};
203255
@@ -218,7 +270,10 @@ struct magnitude_base<unit_magnitude<H, T...>> {
218270 } else {
219271 if constexpr (is_same_v<decltype (get_base (H)), decltype (get_base (H2))>) {
220272 constexpr auto partial_product = unit_magnitude<T...>{} * unit_magnitude<T2...>{};
221- if constexpr (get_exponent (H) + get_exponent (H2) == 0 ) {
273+ if constexpr (is_negative_tag<decltype (get_base (H))>) {
274+ // (-1) * (-1) = 1: two negatives cancel each other out
275+ return partial_product;
276+ } else if constexpr (get_exponent (H) + get_exponent (H2) == 0 ) {
222277 return partial_product;
223278 } else {
224279 // Make a new power_v with the common base of H and H2, whose power is their powers' sum.
@@ -436,7 +491,7 @@ struct unit_magnitude : magnitude_base<unit_magnitude<Ms...>> {
436491 if constexpr (Num == 0 ) {
437492 return unit_magnitude<>{};
438493 } else {
439- return unit_magnitude<power_v_or_T< get_base (Ms), get_exponent (Ms) * ratio{ Num, Den}>()...>{} ;
494+ return pow_magnitude< Num, Den>(unit_magnitude{}) ;
440495 }
441496 }
442497
@@ -447,7 +502,10 @@ struct unit_magnitude : magnitude_base<unit_magnitude<Ms...>> {
447502 return (mp_units::detail::integer_part (unit_magnitude<Ms>{}) * ... * unit_magnitude<>{});
448503 }
449504
450- [[nodiscard]] friend consteval auto denominator (unit_magnitude) { return numerator (pow<-1 >(unit_magnitude{})); }
505+ [[nodiscard]] friend consteval auto denominator (unit_magnitude)
506+ {
507+ return numerator (pow<-1 >(mp_units::detail::abs_magnitude (unit_magnitude{})));
508+ }
451509
452510 [[nodiscard]] friend consteval auto remove_positive_powers (unit_magnitude)
453511 {
@@ -476,7 +534,13 @@ struct unit_magnitude : magnitude_base<unit_magnitude<Ms...>> {
476534 template <typename T>
477535 [[nodiscard]] friend consteval ratio get_power ([[maybe_unused]] T base, unit_magnitude)
478536 {
479- return ((get_base_value (Ms) == base ? get_exponent (Ms) : ratio{0 }) + ... + ratio{0 });
537+ [[maybe_unused]] auto is_base = [&](auto element) consteval {
538+ if constexpr (is_negative_tag<decltype (element)>)
539+ return false ; // (-1) is not a base, it's a special sentinel
540+ else
541+ return get_base_value (element) == base;
542+ };
543+ return ((is_base (Ms) ? get_exponent (Ms) : ratio{0 }) + ... + ratio{0 });
480544 }
481545
482546 [[nodiscard]] friend consteval std::intmax_t extract_power_of_10 (unit_magnitude mag)
@@ -492,36 +556,71 @@ struct unit_magnitude : magnitude_base<unit_magnitude<Ms...>> {
492556 template <typename CharT, std::output_iterator<CharT> Out>
493557 [[nodiscard]] friend constexpr Out magnitude_symbol (Out out, unit_magnitude, const unit_symbol_formatting& fmt)
494558 {
495- if constexpr (unit_magnitude{} == unit_magnitude< 1 >{} ) {
559+ if constexpr (sizeof ...(Ms) == 0 ) {
496560 return out;
497561 } else {
498- constexpr auto extract_res = extract_components (unit_magnitude{});
499- constexpr UnitMagnitude auto ratio = std::get<0 >(extract_res);
500- constexpr UnitMagnitude auto num_constants = std::get<1 >(extract_res);
501- constexpr UnitMagnitude auto den_constants = std::get<2 >(extract_res);
502- constexpr std::intmax_t exp10 = extract_power_of_10 (ratio);
503- if constexpr (abs (exp10) < 3 ) {
504- // print the value as a regular number (without exponent)
505- constexpr UnitMagnitude auto num = numerator (unit_magnitude{});
506- constexpr UnitMagnitude auto den = denominator (unit_magnitude{});
507- // TODO address the below
508- static_assert (ratio == num / den, " Printing rational powers not yet supported" );
509- return magnitude_symbol_impl<CharT, num, den, num_constants, den_constants, 0 >(out, fmt);
562+ // If the magnitude is negative (starts with negative_tag), prepend '-' and delegate.
563+ constexpr bool is_negative = sizeof ...(Ms) > 0 && is_negative_tag<decltype (first_mag_arg<Ms...>())>;
564+ if constexpr (is_negative) {
565+ *out++ = ' -' ;
566+ constexpr UnitMagnitude auto abs_mag = mp_units::detail::abs_magnitude (unit_magnitude{});
567+ return magnitude_symbol<CharT>(out, abs_mag, fmt);
510568 } else {
511- // print the value as a number with exponent
512- // if user wanted a regular number for this magnitude then probably a better scaled unit should be used
513- constexpr UnitMagnitude auto base = ratio / mag_power_lazy<10 , exp10>();
514- constexpr UnitMagnitude auto num = numerator (base);
515- constexpr UnitMagnitude auto den = denominator (base);
516-
517- // TODO address the below
518- static_assert (base == num / den, " Printing rational powers not yet supported" );
519- return magnitude_symbol_impl<CharT, num, den, num_constants, den_constants, exp10>(out, fmt);
569+ constexpr auto extract_res = extract_components (unit_magnitude{});
570+ constexpr UnitMagnitude auto ratio = std::get<0 >(extract_res);
571+ constexpr UnitMagnitude auto num_constants = std::get<1 >(extract_res);
572+ constexpr UnitMagnitude auto den_constants = std::get<2 >(extract_res);
573+ constexpr std::intmax_t exp10 = extract_power_of_10 (ratio);
574+ if constexpr (detail::abs (exp10) < 3 ) {
575+ // print the value as a regular number (without exponent)
576+ constexpr UnitMagnitude auto num = numerator (unit_magnitude{});
577+ constexpr UnitMagnitude auto den = denominator (unit_magnitude{});
578+ // TODO address the below
579+ static_assert (ratio == num / den, " Printing rational powers not yet supported" );
580+ return magnitude_symbol_impl<CharT, num, den, num_constants, den_constants, 0 >(out, fmt);
581+ } else {
582+ // print the value as a number with exponent
583+ // if user wanted a regular number for this magnitude then probably a better scaled unit should be used
584+ constexpr UnitMagnitude auto base = ratio / mp_units::detail::mag_power_lazy<10 , exp10>();
585+ constexpr UnitMagnitude auto num = numerator (base);
586+ constexpr UnitMagnitude auto den = denominator (base);
587+
588+ // TODO address the below
589+ static_assert (base == num / den, " Printing rational powers not yet supported" );
590+ return magnitude_symbol_impl<CharT, num, den, num_constants, den_constants, exp10>(out, fmt);
591+ }
520592 }
521593 }
522594 }
523595};
524596
597+ template <int Num, int Den>
598+ [[nodiscard]] consteval auto pow_magnitude (unit_magnitude<>)
599+ {
600+ return unit_magnitude<>{};
601+ }
602+
603+ template <int Num, int Den, auto H, auto ... Rest>
604+ [[nodiscard]] consteval auto pow_magnitude (unit_magnitude<H, Rest...>)
605+ {
606+ if constexpr (is_negative_tag<decltype (H)>) {
607+ // Raising (-1) to the power Num/Den (in lowest terms):
608+ // - even denominator → taking an even root of a negative number → hard error
609+ // - even numerator → result is positive (+1), negative_tag cancels out
610+ // - odd numerator → result is negative (-1), negative_tag is preserved
611+ static_assert (ratio{Num, Den}.den % 2 == 1 , " Cannot take even root of negative magnitude" );
612+ constexpr auto rest_powered = pow_magnitude<Num, Den>(unit_magnitude<Rest...>{});
613+ if constexpr (ratio{Num, Den}.num % 2 == 0 )
614+ return rest_powered;
615+ else
616+ return unit_magnitude<H>{} * rest_powered;
617+ } else {
618+ // No negative_tag at front: apply power uniformly to all elements.
619+ return unit_magnitude<power_v_or_T<get_base (H), get_exponent (H) * ratio{Num, Den}>(),
620+ power_v_or_T<get_base (Rest), get_exponent (Rest) * ratio{Num, Den}>()...>{};
621+ }
622+ }
623+
525624[[nodiscard]] consteval auto common_magnitude (unit_magnitude<>, UnitMagnitude auto m)
526625{
527626 return remove_positive_powers (m);
@@ -532,26 +631,64 @@ struct unit_magnitude : magnitude_base<unit_magnitude<Ms...>> {
532631}
533632[[nodiscard]] consteval auto common_magnitude (unit_magnitude<> m, unit_magnitude<>) { return m; }
534633
634+ // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
635+ // `abs_magnitude` — strips the leading negative_tag if present, leaving the absolute value.
636+
637+ template <auto H, auto ... Rest>
638+ [[nodiscard]] consteval auto abs_magnitude (unit_magnitude<H, Rest...>)
639+ {
640+ if constexpr (is_negative_tag<decltype (H)>)
641+ return unit_magnitude<Rest...>{};
642+ else
643+ return unit_magnitude<H, Rest...>{};
644+ }
645+
646+ [[nodiscard]] consteval auto abs_magnitude (unit_magnitude<>) { return unit_magnitude<>{}; }
647+
648+ // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
649+ // `magnitude_is_positive` — checks whether a magnitude is positive (i.e., has no negative_tag).
650+
651+ // Uses a consteval function rather than `is_same_v<decltype(M), decltype(abs_magnitude(M))>` because GCC 12
652+ // incorrectly evaluates the latter to `false` for genuinely positive magnitudes in some constexpr contexts.
653+ [[nodiscard]] consteval bool check_magnitude_is_positive (unit_magnitude<>) { return true ; }
654+
655+ template <auto H, auto ... Rest>
656+ [[nodiscard]] consteval bool check_magnitude_is_positive (unit_magnitude<H, Rest...>)
657+ {
658+ return !is_negative_tag<decltype (H)>;
659+ }
660+
661+ template <UnitMagnitude auto M>
662+ constexpr bool magnitude_is_positive = check_magnitude_is_positive(M);
535663
536664// The largest integer which can be extracted from any magnitude with only a single basis vector.
537665template <auto M>
538666[[nodiscard]] consteval auto integer_part (unit_magnitude<M>)
539667{
540- constexpr auto power_num = get_exponent (M).num ;
541- constexpr auto power_den = get_exponent (M).den ;
542-
543- if constexpr (std::is_integral_v<decltype (get_base (M))> && (power_num >= power_den)) {
544- // largest integer power
545- return unit_magnitude<power_v_or_T<get_base (M), power_num / power_den>()>{}; // Note: integer division intended
668+ // The negative_tag is the integer (-1): include it in the integer part.
669+ // The else is required so that the rest of the body (which calls get_exponent/get_base on M)
670+ // is not instantiated for negative_tag, avoiding conflicting return type deductions.
671+ if constexpr (is_negative_tag<decltype (M)>) {
672+ return unit_magnitude<M>{};
546673 } else {
547- return unit_magnitude<>{};
674+ constexpr auto power_num = get_exponent (M).num ;
675+ constexpr auto power_den = get_exponent (M).den ;
676+
677+ if constexpr (std::is_integral_v<decltype (get_base (M))> && (power_num >= power_den)) {
678+ // largest integer power
679+ return unit_magnitude<power_v_or_T<get_base (M), power_num / power_den>()>{}; // Note: integer division intended
680+ } else {
681+ return unit_magnitude<>{};
682+ }
548683 }
549684}
550685
551686template <auto M>
552687[[nodiscard]] consteval auto remove_positive_power (unit_magnitude<M> m)
553688{
554- if constexpr (get_exponent (M).num < 0 ) {
689+ if constexpr (is_negative_tag<decltype (M)>)
690+ return unit_magnitude<>{}; // negative_tag is a sign sentinel, not a basis element; exclude it
691+ else if constexpr (get_exponent (M).num < 0 ) {
555692 return m;
556693 } else {
557694 return unit_magnitude<>{};
@@ -561,7 +698,9 @@ template<auto M>
561698template <auto M>
562699[[nodiscard]] consteval auto remove_mag_constants (unit_magnitude<M> m)
563700{
564- if constexpr (is_mag_constant<decltype (get_base (M))>)
701+ if constexpr (is_negative_tag<decltype (M)>)
702+ return m; // negative_tag is a sign marker, not a mag_constant; keep it in the ratio part
703+ else if constexpr (is_mag_constant<decltype (get_base (M))>)
565704 return unit_magnitude<>{};
566705 else
567706 return m;
@@ -570,7 +709,9 @@ template<auto M>
570709template <auto M>
571710[[nodiscard]] consteval auto only_positive_mag_constants (unit_magnitude<M> m)
572711{
573- if constexpr (is_mag_constant<decltype (get_base (M))> && get_exponent (M) >= 0 )
712+ if constexpr (is_negative_tag<decltype (M)>)
713+ return unit_magnitude<>{};
714+ else if constexpr (is_mag_constant<decltype (get_base (M))> && get_exponent (M) >= 0 )
574715 return m;
575716 else
576717 return unit_magnitude<>{};
@@ -579,7 +720,9 @@ template<auto M>
579720template <auto M>
580721[[nodiscard]] consteval auto only_negative_mag_constants (unit_magnitude<M> m)
581722{
582- if constexpr (is_mag_constant<decltype (get_base (M))> && get_exponent (M) < 0 )
723+ if constexpr (is_negative_tag<decltype (M)>)
724+ return unit_magnitude<>{};
725+ else if constexpr (is_mag_constant<decltype (get_base (M))> && get_exponent (M) < 0 )
583726 return m;
584727 else
585728 return unit_magnitude<>{};
@@ -621,9 +764,28 @@ constexpr auto prime_factorization_v = prime_factorization<N>::value;
621764template <MagArg auto V>
622765[[nodiscard]] consteval UnitMagnitude auto make_magnitude ()
623766{
624- if constexpr (is_mag_constant<MP_UNITS_REMOVE_CONST (decltype (V))>)
767+ using mag_arg_type = MP_UNITS_REMOVE_CONST (decltype (V));
768+
769+ if constexpr (is_mag_constant<mag_arg_type>)
625770 return unit_magnitude<V>{};
626- else
771+ else if constexpr (is_same_v<mag_arg_type, ratio>) {
772+ // ratio{num, den}: factor out the sign, then factorize num and den
773+ constexpr ratio abs_v{V.num < 0 ? -V.num : V.num , V.den };
774+ constexpr bool negative = V.num < 0 ;
775+ constexpr UnitMagnitude auto abs_mag = prime_factorization_v<abs_v.num > / prime_factorization_v<abs_v.den >;
776+ if constexpr (negative)
777+ return unit_magnitude<negative_tag{}>{} * abs_mag;
778+ else
779+ return abs_mag;
780+ } else if constexpr (std::integral<mag_arg_type>) {
781+ // Integer NTTP: factor out the sign, then prime-factorize the absolute value
782+ constexpr auto abs_v = V < 0 ? -V : V;
783+ constexpr UnitMagnitude auto abs_mag = prime_factorization_v<abs_v>;
784+ if constexpr (V < 0 )
785+ return unit_magnitude<negative_tag{}>{} * abs_mag;
786+ else
787+ return abs_mag;
788+ } else
627789 return prime_factorization_v<V>;
628790}
629791
0 commit comments