Skip to content

Commit a5821e1

Browse files
authored
Merge pull request #4 from Siderust/impl-formating
Refactor unit traits in mass, power, and time headers; add comprehens…
2 parents 3612a8f + 71ec843 commit a5821e1

6 files changed

Lines changed: 701 additions & 4 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Build directories
2-
build/
2+
build*/
3+
coverage*/
34
cmake-build-*/
45
out/
56
gen_cpp_units/target/

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ set(TEST_FFI_SOURCES
158158
tests/test_dimension_safety.cpp
159159
tests/test_precision.cpp
160160
tests/test_serialization.cpp
161+
tests/test_formatting.cpp
161162
)
162163

163164
add_executable(test_ffi ${TEST_FFI_SOURCES})

include/qtty/ffi_core.hpp

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
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

327396
template <typename UnitTag>
328397
std::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

Comments
 (0)