99 */
1010
1111#include < cmath>
12+ #include < iomanip>
1213#include < iostream>
1314#include < ostream>
15+ #include < sstream>
1416#include < stdexcept>
1517#include < string>
1618#include < string_view>
@@ -316,13 +318,80 @@ template <typename UnitTag> class Quantity {
316318 Quantity operator -() const { return Quantity (-m_value); }
317319
318320 Quantity abs () const { return Quantity (std::abs (m_value)); }
321+
322+ // ========================================================================
323+ // String Formatting
324+ // ========================================================================
325+ // Format the quantity as a human-readable string, mirroring Rust's format
326+ // annotations. The mapping is:
327+ //
328+ // Rust C++
329+ // {} format()
330+ // {:.2} format(2)
331+ // {:e} format(-1, QTTY_FMT_LOWER_EXP)
332+ // {:.4e} format(4, QTTY_FMT_LOWER_EXP)
333+ // {:E} format(-1, QTTY_FMT_UPPER_EXP)
334+ // {:.4E} format(4, QTTY_FMT_UPPER_EXP)
335+ //
336+ // The formatting logic lives in the Rust qtty-ffi library, so precision
337+ // semantics are identical on both sides of the FFI boundary.
338+
339+ /* *
340+ * @brief Format this quantity as a string.
341+ *
342+ * Delegates to the Rust qtty-ffi `qtty_quantity_format` function so that
343+ * C++ and Rust produce identical output for the same parameters.
344+ *
345+ * @param precision Digits after the decimal point. Pass a negative value
346+ * (default) for the shortest exact representation.
347+ * @param flags Notation selector:
348+ * - `QTTY_FMT_DEFAULT` (0): decimal (e.g. `"1234.57 m"`)
349+ * - `QTTY_FMT_LOWER_EXP` (1): scientific lower-case `e`
350+ * - `QTTY_FMT_UPPER_EXP` (2): scientific upper-case `E`
351+ * @return Formatted string, e.g. `"1234.57 m"` or `"1.23e3 m"`.
352+ * @throws QttyException on formatting failure.
353+ */
354+ std::string format (int precision = -1 ,
355+ uint32_t flags = QTTY_FMT_DEFAULT) const {
356+ qtty_quantity_t qty;
357+ int32_t make_status = qtty_quantity_make (m_value, unit_id (), &qty);
358+ check_status (make_status, " format: creating quantity" );
359+
360+ char buf[512 ];
361+ int32_t result =
362+ qtty_quantity_format (qty, precision, flags, buf, sizeof (buf));
363+ if (result == QTTY_ERR_BUFFER_TOO_SMALL) {
364+ // Retry with a generous large buffer (quantities should never need this)
365+ char big_buf[4096 ];
366+ result =
367+ qtty_quantity_format (qty, precision, flags, big_buf, sizeof (big_buf));
368+ if (result < 0 ) {
369+ throw QttyException (" format: buffer too small even at 4096 bytes" );
370+ }
371+ return std::string (big_buf);
372+ }
373+ if (result < 0 ) {
374+ check_status (result, " format: formatting quantity" );
375+ }
376+ return std::string (buf);
377+ }
319378};
320379
321380// ============================================================================
322381// Stream Insertion Operator
323382// ============================================================================
324- // Prints a quantity's value (with unit symbol support for units that define
325- // it).
383+ // Prints a quantity with its unit symbol, e.g., "1500 m" or "42.5 km".
384+ //
385+ // Because this streams `q.value()` (a plain double) directly into the
386+ // `std::ostream`, all standard stream format manipulators are respected:
387+ //
388+ // std::cout << std::fixed << std::setprecision(2) << qty; // "1234.57 m"
389+ // std::cout << std::scientific << qty; // "1.23457e+003
390+ // m" std::cout << std::scientific << std::setprecision(4)
391+ // << qty; // "1.2346e+003
392+ // m"
393+ //
394+ // For `std::format` (C++20) see the std::formatter specialisation below.
326395
327396template <typename UnitTag>
328397std::ostream &operator <<(std::ostream &os, const Quantity<UnitTag> &q) {
@@ -331,3 +400,48 @@ std::ostream &operator<<(std::ostream &os, const Quantity<UnitTag> &q) {
331400}
332401
333402} // namespace qtty
403+
404+ // ============================================================================
405+ // C++20 std::formatter specialisation
406+ // ============================================================================
407+ // Allows `std::format` and `std::print` to be used with any Quantity type,
408+ // honouring the same format specifiers as std::formatter<double>:
409+ //
410+ // std::format("{}", qty) → "1234.56789 s"
411+ // std::format("{:.2f}", qty) → "1234.57 s"
412+ // std::format("{:e}", qty) → "1.23457e+03 s"
413+ // std::format("{:.4e}", qty) → "1.2346e+03 s"
414+ // std::format("{:E}", qty) → "1.23457E+03 s"
415+ // std::format("{:>15.2f}", qty) → " 1234.57 s" (number padded, not
416+ // symbol)
417+ //
418+ // Note: width / fill / align specifications are applied to the numeric part
419+ // only; the unit symbol is always appended directly after without padding.
420+ // This mirrors the behaviour of the Rust Display/LowerExp/UpperExp impls.
421+
422+ #if __cplusplus >= 202002L
423+ #include < format>
424+
425+ namespace std {
426+
427+ template <typename UnitTag> struct formatter <qtty::Quantity<UnitTag>> {
428+ private:
429+ std::formatter<double > double_fmt_;
430+
431+ public:
432+ // / Parse the format specification (e.g. ".2f", "e", ".4e").
433+ template <typename ParseContext> constexpr auto parse (ParseContext &ctx) {
434+ return double_fmt_.parse (ctx);
435+ }
436+
437+ // / Format the quantity: apply the parsed spec to the value, then append the
438+ // / unit symbol.
439+ template <typename FormatContext>
440+ auto format (const qtty::Quantity<UnitTag> &qty, FormatContext &ctx) const {
441+ auto out = double_fmt_.format (qty.value (), ctx);
442+ return std::format_to (out, " {}" , qtty::UnitTraits<UnitTag>::symbol ());
443+ }
444+ };
445+
446+ } // namespace std
447+ #endif // __cplusplus >= 202002L
0 commit comments