mirror of
https://github.com/mpusz/mp-units.git
synced 2025-07-30 02:17:16 +02:00
fmt support added
This commit is contained in:
115
src/include/units/bits/customization_points.h
Normal file
115
src/include/units/bits/customization_points.h
Normal file
@ -0,0 +1,115 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2018 Mateusz Pusz
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <cmath>
|
||||
|
||||
namespace units {
|
||||
|
||||
// treat_as_floating_point
|
||||
|
||||
template<typename Rep> // TODO Conceptify that
|
||||
inline constexpr bool treat_as_floating_point = std::is_floating_point_v<Rep>;
|
||||
|
||||
// isnan
|
||||
namespace isnan_impl {
|
||||
|
||||
// non-ADL lookup block
|
||||
void isnan(); // undefined
|
||||
|
||||
template<typename>
|
||||
inline constexpr bool has_customization = false;
|
||||
|
||||
template<typename T>
|
||||
requires requires(const T& t) {
|
||||
{ isnan(t) } -> bool;
|
||||
}
|
||||
inline constexpr bool has_customization<T> = true;
|
||||
|
||||
struct fn {
|
||||
template<typename T>
|
||||
constexpr bool operator()(const T&) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires treat_as_floating_point<T>
|
||||
constexpr bool operator()(const T& value) const
|
||||
{
|
||||
return std::isnan(value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires treat_as_floating_point<T> && has_customization<T>
|
||||
constexpr bool operator()(const T& value) const
|
||||
{
|
||||
return isnan(value); // uses ADL
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
inline constexpr isnan_impl::fn isnan{};
|
||||
|
||||
// isfinite
|
||||
namespace isfinite_impl {
|
||||
|
||||
// non-ADL lookup block
|
||||
void isfinite(); // undefined
|
||||
|
||||
template<typename>
|
||||
inline constexpr bool has_customization = false;
|
||||
|
||||
template<typename T>
|
||||
requires requires(const T& t) {
|
||||
{ isfinite(t) } -> bool;
|
||||
}
|
||||
inline constexpr bool has_customization<T> = true;
|
||||
|
||||
struct fn {
|
||||
template<typename T>
|
||||
constexpr bool operator()(const T&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires treat_as_floating_point<T>
|
||||
constexpr bool operator()(const T& value) const
|
||||
{
|
||||
return std::isfinite(value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires treat_as_floating_point<T> && has_customization<T>
|
||||
constexpr bool operator()(const T& value) const
|
||||
{
|
||||
return isfinite(value); // uses ADL
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
inline constexpr isfinite_impl::fn isfinite{};
|
||||
|
||||
}
|
@ -22,63 +22,347 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <units/bits/customization_points.h>
|
||||
#include <units/quantity.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
// units-format-spec:
|
||||
// fill-and-align[opt] width[opt] precision[opt] units-specs[opt]
|
||||
// units-specs:
|
||||
// conversion-spec
|
||||
// units-specs conversion-spec
|
||||
// units-specs literal-char
|
||||
// literal-char:
|
||||
// any character other than { or }
|
||||
// conversion-spec:
|
||||
// % modifier[opt] type
|
||||
// modifier: one of
|
||||
// E O
|
||||
// type: one of
|
||||
// q Q %
|
||||
namespace units {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<typename U, typename Rep, typename CharT>
|
||||
struct fmt::formatter<units::quantity<U, Rep>, CharT> {
|
||||
// units-format-spec:
|
||||
// fill-and-align[opt] width[opt] precision[opt] units-specs[opt]
|
||||
// units-specs:
|
||||
// conversion-spec
|
||||
// units-specs conversion-spec
|
||||
// units-specs literal-char
|
||||
// literal-char:
|
||||
// any character other than { or }
|
||||
// conversion-spec:
|
||||
// % modifier[opt] type
|
||||
// modifier: one of
|
||||
// E O
|
||||
// type: one of
|
||||
// n q Q t %
|
||||
|
||||
template<typename CharT, typename Handler>
|
||||
constexpr const CharT* parse_units_format(const CharT* begin, const CharT* end, Handler&& handler)
|
||||
{
|
||||
auto ptr = begin;
|
||||
while(ptr != end) {
|
||||
auto c = *ptr;
|
||||
if(c == '}')
|
||||
break;
|
||||
if(c != '%') {
|
||||
++ptr;
|
||||
continue;
|
||||
}
|
||||
if (begin != ptr)
|
||||
handler.on_text(begin, ptr);
|
||||
++ptr; // consume '%'
|
||||
if(ptr == end)
|
||||
throw fmt::format_error("invalid format");
|
||||
c = *ptr++;
|
||||
switch(c) {
|
||||
case '%':
|
||||
handler.on_text(ptr - 1, ptr);
|
||||
break;
|
||||
case 'n': {
|
||||
const char newline[] = "\n";
|
||||
handler.on_text(newline, newline + 1);
|
||||
break;
|
||||
}
|
||||
case 't': {
|
||||
const char tab[] = "\t";
|
||||
handler.on_text(tab, tab + 1);
|
||||
break;
|
||||
}
|
||||
case 'Q':
|
||||
handler.on_quantity_value();
|
||||
break;
|
||||
case 'q':
|
||||
handler.on_quantity_unit();
|
||||
break;
|
||||
default:
|
||||
throw fmt::format_error("invalid format");
|
||||
}
|
||||
begin = ptr;
|
||||
}
|
||||
if(begin != ptr)
|
||||
handler.on_text(begin, ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
struct units_format_checker {
|
||||
template<typename Char>
|
||||
void on_text(const Char*, const Char*) {}
|
||||
void on_quantity_value() {}
|
||||
void on_quantity_unit() {}
|
||||
};
|
||||
|
||||
template<typename Rep, typename OutputIt>
|
||||
inline OutputIt format_units_quantity_value(OutputIt out, const Rep& val, int precision)
|
||||
{
|
||||
if(precision >= 0)
|
||||
return format_to(out, "{:.{}f}", val, precision);
|
||||
return format_to(out, treat_as_floating_point<Rep> ? "{:g}" : "{}", val);
|
||||
}
|
||||
|
||||
template<typename Unit, typename OutputIt>
|
||||
inline static OutputIt format_units_quantity_unit(OutputIt out)
|
||||
{
|
||||
return format_to(out, "{}", unit_text<Unit>().c_str());
|
||||
}
|
||||
|
||||
// If T is an integral type, maps T to its unsigned counterpart, otherwise
|
||||
// leaves it unchanged (unlike std::make_unsigned).
|
||||
template<typename T, bool Integral = std::is_integral_v<T>>
|
||||
struct make_unsigned_or_unchanged {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct make_unsigned_or_unchanged<T, true> {
|
||||
using type = std::make_unsigned_t<T>;
|
||||
};
|
||||
|
||||
// converts value to int and checks that it's in the range [0, upper).
|
||||
template<typename T>
|
||||
requires std::is_integral_v<T>
|
||||
inline int to_nonnegative_int(T value, int upper)
|
||||
{
|
||||
FMT_ASSERT(value >= 0 && value <= upper, "invalid value");
|
||||
(void)upper;
|
||||
return static_cast<int>(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline int to_nonnegative_int(T value, int upper)
|
||||
{
|
||||
FMT_ASSERT(units::isnan(value) || (value >= 0 && value <= static_cast<T>(upper)), "invalid value");
|
||||
(void)upper;
|
||||
return static_cast<int>(value);
|
||||
}
|
||||
|
||||
template<typename FormatContext, typename OutputIt, typename Unit, typename Rep>
|
||||
struct units_formatter {
|
||||
FormatContext& context;
|
||||
OutputIt out;
|
||||
// rep is unsigned to avoid overflow.
|
||||
using rep = conditional<std::is_integral_v<Rep> && sizeof(Rep) < sizeof(int),
|
||||
unsigned, typename make_unsigned_or_unchanged<Rep>::type>;
|
||||
bool negative = false;
|
||||
rep val;
|
||||
int precision;
|
||||
|
||||
using char_type = FormatContext::char_type;
|
||||
|
||||
explicit units_formatter(FormatContext& ctx, OutputIt o, quantity<Unit, Rep> q, int prec):
|
||||
context(ctx), out(o), negative(q.count() < 0), val(negative ? -q.count() : q.count()), precision(prec)
|
||||
{
|
||||
}
|
||||
|
||||
// returns true if nan or inf, writes to out.
|
||||
bool handle_nan_inf()
|
||||
{
|
||||
if(units::isfinite(val)) {
|
||||
return false;
|
||||
}
|
||||
if(units::isnan(val)) {
|
||||
write_nan();
|
||||
return true;
|
||||
}
|
||||
// must be +-inf
|
||||
if(val > 0) {
|
||||
write_pinf();
|
||||
}
|
||||
else {
|
||||
write_ninf();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void write_sign()
|
||||
{
|
||||
if(negative) {
|
||||
*out++ = '-';
|
||||
negative = false;
|
||||
}
|
||||
}
|
||||
|
||||
void write(Rep value, int width)
|
||||
{
|
||||
write_sign();
|
||||
if(units::isnan(value))
|
||||
return write_nan();
|
||||
fmt::internal::uint32_or_64_t<int> n = fmt::internal::to_unsigned(to_nonnegative_int(value, std::numeric_limits<int>::max()));
|
||||
int num_digits = fmt::internal::count_digits(n);
|
||||
if(width > num_digits)
|
||||
out = std::fill_n(out, width - num_digits, '0');
|
||||
out = fmt::internal::format_decimal<char_type>(out, n, num_digits);
|
||||
}
|
||||
|
||||
void write_nan() { std::copy_n("nan", 3, out); }
|
||||
void write_pinf() { std::copy_n("inf", 3, out); }
|
||||
void write_ninf() { std::copy_n("-inf", 4, out); }
|
||||
|
||||
void on_text(const char_type* begin, const char_type* end)
|
||||
{
|
||||
std::copy(begin, end, out);
|
||||
}
|
||||
|
||||
void on_quantity_value()
|
||||
{
|
||||
if(handle_nan_inf())
|
||||
return;
|
||||
write_sign();
|
||||
out = format_units_quantity_value(out, val, precision);
|
||||
}
|
||||
|
||||
void on_quantity_unit()
|
||||
{
|
||||
out = format_units_quantity_unit<Unit>(out);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace units
|
||||
|
||||
template<typename Unit, typename Rep, typename CharT>
|
||||
struct fmt::formatter<units::quantity<Unit, Rep>, CharT> {
|
||||
private:
|
||||
using quantity = units::quantity<Unit, Rep>;
|
||||
using iterator = fmt::basic_parse_context<CharT>::iterator;
|
||||
using arg_ref_type = fmt::internal::arg_ref<CharT>;
|
||||
|
||||
fmt::basic_format_specs<CharT> specs;
|
||||
int precision = -1;
|
||||
using arg_ref_type = fmt::internal::arg_ref<CharT>;
|
||||
arg_ref_type width_ref;
|
||||
arg_ref_type precision_ref;
|
||||
mutable basic_string_view<CharT> format_str;
|
||||
using quantity = units::quantity<U, Rep>;
|
||||
fmt::basic_string_view<CharT> format_str;
|
||||
|
||||
// auto parse_unit_format() {
|
||||
// if (s != ctx.end() && *s == 'q') {
|
||||
// quantity = true;
|
||||
// return ++s;
|
||||
// }
|
||||
// }
|
||||
struct spec_handler {
|
||||
formatter& f;
|
||||
fmt::basic_parse_context<CharT>& context;
|
||||
fmt::basic_string_view<CharT> format_str;
|
||||
|
||||
template<typename Id>
|
||||
constexpr arg_ref_type make_arg_ref(Id arg_id)
|
||||
{
|
||||
context.check_arg_id(arg_id);
|
||||
return arg_ref_type(arg_id);
|
||||
}
|
||||
|
||||
constexpr arg_ref_type make_arg_ref(fmt::basic_string_view<CharT> arg_id)
|
||||
{
|
||||
context.check_arg_id(arg_id);
|
||||
const auto str_val = fmt::internal::string_view_metadata(format_str, arg_id);
|
||||
return arg_ref_type(str_val);
|
||||
}
|
||||
|
||||
constexpr arg_ref_type make_arg_ref(internal::auto_id)
|
||||
{
|
||||
return arg_ref_type(context.next_arg_id());
|
||||
}
|
||||
|
||||
void on_error(const char* msg) { throw fmt::format_error(msg); }
|
||||
void on_fill(CharT fill) { f.specs.fill[0] = fill; }
|
||||
void on_align(align_t align) { f.specs.align = align; }
|
||||
void on_width(unsigned width) { f.specs.width = width; }
|
||||
void on_precision(unsigned precision) { f.precision = precision; }
|
||||
void end_precision() {}
|
||||
|
||||
template<typename Id>
|
||||
void on_dynamic_width(Id arg_id)
|
||||
{
|
||||
f.width_ref = make_arg_ref(arg_id);
|
||||
}
|
||||
|
||||
template <typename Id> void on_dynamic_precision(Id arg_id)
|
||||
{
|
||||
f.precision_ref = make_arg_ref(arg_id);
|
||||
}
|
||||
};
|
||||
|
||||
struct parse_range {
|
||||
iterator begin;
|
||||
iterator end;
|
||||
};
|
||||
|
||||
constexpr parse_range do_parse(fmt::basic_parse_context<CharT>& ctx)
|
||||
{
|
||||
auto begin = ctx.begin(), end = ctx.end();
|
||||
if(begin == end || *begin == '}')
|
||||
return {begin, begin};
|
||||
|
||||
// handler to assign parsed data to formatter data members
|
||||
spec_handler handler{*this, ctx, format_str};
|
||||
|
||||
// parse alignment
|
||||
begin = fmt::internal::parse_align(begin, end, handler);
|
||||
if(begin == end)
|
||||
return {begin, begin};
|
||||
|
||||
// parse width
|
||||
begin = fmt::internal::parse_width(begin, end, handler);
|
||||
if(begin == end)
|
||||
return {begin, begin};
|
||||
|
||||
// parse precision if a floating point
|
||||
if(*begin == '.') {
|
||||
if constexpr(units::treat_as_floating_point<Rep>)
|
||||
begin = fmt::internal::parse_precision(begin, end, handler);
|
||||
else
|
||||
handler.on_error("precision not allowed for integral quantity representation");
|
||||
}
|
||||
|
||||
end = parse_units_format(begin, end, units::detail::units_format_checker());
|
||||
|
||||
return {begin, end};
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr auto parse(fmt::basic_parse_context<CharT>& ctx)
|
||||
{
|
||||
// [ctx.begin(), ctx.end()) is a range of CharTs containing format-specs,
|
||||
// e.g. in format("{:%Q %q}", ...) it is "%Q %q}" (format string after ':')
|
||||
// auto begin = ctx.begin(), end = ctx.end();
|
||||
// Look at do_parse in fmt/chrono.h and provide replacement for parse_chrono_format.
|
||||
// fill-and-align_opt ...
|
||||
// begin = fmt::internal::parse_align(begin, end, handler);
|
||||
// parse_unit_format();
|
||||
return ctx.end();
|
||||
auto range = do_parse(ctx);
|
||||
format_str = fmt::basic_string_view<CharT>(&*range.begin, fmt::internal::to_unsigned(range.end - range.begin));
|
||||
return range.end;
|
||||
}
|
||||
// format("{:{}}", 'x', 10)
|
||||
|
||||
template<typename FormatContext>
|
||||
auto format(const units::quantity<U, Rep>& q, FormatContext& ctx)
|
||||
auto format(const units::quantity<Unit, Rep>& q, FormatContext& ctx)
|
||||
{
|
||||
// ctx.out() - output iterator you write to.
|
||||
// auto s = format("{0:.{1}} {2}", q.count(), precision, unit(q));
|
||||
// return format_to(ctx.out(), "{:{}}", s, width);
|
||||
return format_to(ctx.out(), "{} {}", q.count(), units::detail::unit_text<typename quantity::unit>().c_str());
|
||||
auto begin = format_str.begin(), end = format_str.end();
|
||||
|
||||
// TODO Avoid extra copying if width is not specified
|
||||
fmt::basic_memory_buffer<CharT> buf;
|
||||
auto out = std::back_inserter(buf);
|
||||
|
||||
// process dynamic width and precision
|
||||
fmt::internal::handle_dynamic_spec<fmt::internal::width_checker>(specs.width, width_ref, ctx, format_str.begin());
|
||||
fmt::internal::handle_dynamic_spec<fmt::internal::precision_checker>(precision, precision_ref, ctx, format_str.begin());
|
||||
|
||||
// deal with quantity content
|
||||
if(begin == end || *begin == '}') {
|
||||
// default format should print value followed by the unit separeted with 1 space
|
||||
out = units::detail::format_units_quantity_value(out, q.count(), precision);
|
||||
*out++ = CharT(' ');
|
||||
units::detail::format_units_quantity_unit<Unit>(out);
|
||||
}
|
||||
else {
|
||||
// user provided format
|
||||
units::detail::units_formatter<FormatContext, decltype(out), Unit, Rep> f(ctx, out, q, precision);
|
||||
parse_units_format(begin, end, f);
|
||||
}
|
||||
|
||||
// form a final text
|
||||
using range = fmt::internal::output_range<decltype(ctx.out()), CharT>;
|
||||
fmt::internal::basic_writer<range> w(range(ctx.out()));
|
||||
w.write(buf.data(), buf.size(), specs);
|
||||
|
||||
return w.out();
|
||||
|
||||
// return format_to(ctx.out(), "{} {}", q.count(), units::detail::unit_text<typename quantity::unit>().c_str());
|
||||
}
|
||||
};
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <sstream>
|
||||
|
||||
using namespace units;
|
||||
using namespace Catch::Matchers;
|
||||
|
||||
TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
|
||||
{
|
||||
@ -562,6 +563,180 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("format string with only %Q should print quantity value only", "[text][fmt]")
|
||||
{
|
||||
SECTION("integral representation")
|
||||
{
|
||||
REQUIRE(fmt::format("{:%Q}", 123kmph) == "123");
|
||||
}
|
||||
|
||||
SECTION("floating-point representation")
|
||||
{
|
||||
SECTION("no precision specification")
|
||||
{
|
||||
REQUIRE(fmt::format("{:%Q}", 221.km / 2h) == "110.5");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("format string with only %q should print quantity unit symbol only", "[text][fmt]")
|
||||
{
|
||||
REQUIRE(fmt::format("{:%q}", 123kmph) == "km/h");
|
||||
}
|
||||
|
||||
TEST_CASE("%q an %Q can be put anywhere in a format string", "[text][fmt]")
|
||||
{
|
||||
SECTION("no space")
|
||||
{
|
||||
REQUIRE(fmt::format("{:%Q%q}", 123kmph) == "123km/h");
|
||||
}
|
||||
|
||||
SECTION("separator")
|
||||
{
|
||||
REQUIRE(fmt::format("{:%Q###%q}", 123kmph) == "123###km/h");
|
||||
}
|
||||
|
||||
SECTION("opposite order")
|
||||
{
|
||||
REQUIRE(fmt::format("{:%q %Q}", 123kmph) == "km/h 123");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("precision specification", "[text][fmt]")
|
||||
{
|
||||
SECTION("default format {} on a quantity")
|
||||
{
|
||||
SECTION("0")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.0}", 1.2345m) == "1 m");
|
||||
}
|
||||
|
||||
SECTION("1")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.1}", 1.2345m) == "1.2 m");
|
||||
}
|
||||
|
||||
SECTION("2")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.2}", 1.2345m) == "1.23 m");
|
||||
}
|
||||
|
||||
SECTION("3")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.3}", 1.2345m) == "1.235 m");
|
||||
}
|
||||
|
||||
SECTION("4")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.4}", 1.2345m) == "1.2345 m");
|
||||
}
|
||||
|
||||
SECTION("5")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.5}", 1.2345m) == "1.23450 m");
|
||||
}
|
||||
|
||||
SECTION("10")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.10}", 1.2345m) == "1.2345000000 m");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("full format {:%Q %q} on a quantity")
|
||||
{
|
||||
SECTION("0")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.0%Q %q}", 1.2345m) == "1 m");
|
||||
}
|
||||
|
||||
SECTION("1")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.1%Q %q}", 1.2345m) == "1.2 m");
|
||||
}
|
||||
|
||||
SECTION("2")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.2%Q %q}", 1.2345m) == "1.23 m");
|
||||
}
|
||||
|
||||
SECTION("3")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.3%Q %q}", 1.2345m) == "1.235 m");
|
||||
}
|
||||
|
||||
SECTION("4")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.4%Q %q}", 1.2345m) == "1.2345 m");
|
||||
}
|
||||
|
||||
SECTION("5")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.5%Q %q}", 1.2345m) == "1.23450 m");
|
||||
}
|
||||
|
||||
SECTION("10")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.10%Q %q}", 1.2345m) == "1.2345000000 m");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("value only format {:%Q} on a quantity")
|
||||
{
|
||||
SECTION("0")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.0%Q}", 1.2345m) == "1");
|
||||
}
|
||||
|
||||
SECTION("1")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.1%Q}", 1.2345m) == "1.2");
|
||||
}
|
||||
|
||||
SECTION("2")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.2%Q}", 1.2345m) == "1.23");
|
||||
}
|
||||
|
||||
SECTION("3")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.3%Q}", 1.2345m) == "1.235");
|
||||
}
|
||||
|
||||
SECTION("4")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.4%Q}", 1.2345m) == "1.2345");
|
||||
}
|
||||
|
||||
SECTION("5")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.5%Q}", 1.2345m) == "1.23450");
|
||||
}
|
||||
|
||||
SECTION("10")
|
||||
{
|
||||
REQUIRE(fmt::format("{:.10%Q}", 1.2345m) == "1.2345000000");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("precision specification for integral representation should throw", "[text][fmt][exception]")
|
||||
{
|
||||
SECTION("default format {} on a quantity")
|
||||
{
|
||||
REQUIRE_THROWS_MATCHES(fmt::format("{:.1}", 1m), fmt::format_error, Message("precision not allowed for integral quantity representation"));
|
||||
}
|
||||
|
||||
SECTION("full format {:%Q %q} on a quantity")
|
||||
{
|
||||
REQUIRE_THROWS_MATCHES(fmt::format("{:.1%Q %q}", 1m), fmt::format_error, Message("precision not allowed for integral quantity representation"));
|
||||
}
|
||||
|
||||
SECTION("value only format {:%Q} on a quantity")
|
||||
{
|
||||
REQUIRE_THROWS_MATCHES(fmt::format("{:.1%Q}", 1m), fmt::format_error, Message("precision not allowed for integral quantity representation"));
|
||||
}
|
||||
}
|
||||
|
||||
// Restate operator<< definitions in terms of std::format to make I/O manipulators apply to whole objects
|
||||
// rather than their parts
|
||||
|
||||
|
Reference in New Issue
Block a user