implement fractional number format, continue implementing tests for builtin formats

This commit is contained in:
Thomas Fussell 2016-07-08 19:39:28 -04:00
parent 4aeab7bdfa
commit 5147a282af
3 changed files with 191 additions and 22 deletions

View File

@ -474,9 +474,29 @@ void number_format_parser::finalize()
bool exponent = false; bool exponent = false;
std::size_t exponent_index = 0; std::size_t exponent_index = 0;
bool fraction = false;
std::size_t fraction_denominator_index = 0;
std::size_t fraction_numerator_index = 0;
bool seconds = false;
bool fractional_seconds = false;
std::size_t seconds_index = 0;
for (std::size_t i = 0; i < code.parts.size(); ++i) for (std::size_t i = 0; i < code.parts.size(); ++i)
{ {
const auto &part = code.parts[i]; const auto &part = code.parts[i];
if (i > 0
&& i + 1 < code.parts.size()
&& part.type == template_part::template_type::text
&& part.string == "/"
&& code.parts[i - 1].placeholders.type == format_placeholders::placeholders_type::integer_part
&& code.parts[i + 1].placeholders.type == format_placeholders::placeholders_type::integer_part)
{
fraction = true;
fraction_numerator_index = i - 1;
fraction_denominator_index = i + 1;
}
if (part.placeholders.type == format_placeholders::placeholders_type::integer_part) if (part.placeholders.type == format_placeholders::placeholders_type::integer_part)
{ {
@ -498,6 +518,18 @@ void number_format_parser::finalize()
{ {
percentage = true; percentage = true;
} }
if (part.type == template_part::template_type::second
|| part.type == template_part::template_type::second_leading_zero)
{
seconds = true;
seconds_index = i;
}
if (seconds && part.placeholders.type == format_placeholders::placeholders_type::fractional_part)
{
fractional_seconds = true;
}
if (part.type == template_part::template_type::month_number if (part.type == template_part::template_type::month_number
|| part.type == template_part::template_type::month_number_leading_zero) || part.type == template_part::template_type::month_number_leading_zero)
@ -547,7 +579,8 @@ void number_format_parser::finalize()
if (integer_part && !fractional_part) if (integer_part && !fractional_part)
{ {
code.parts[integer_part_index].placeholders.type = format_placeholders::placeholders_type::integer_only; code.parts[integer_part_index].placeholders.type =
format_placeholders::placeholders_type::integer_only;
} }
if (integer_part && fractional_part && percentage) if (integer_part && fractional_part && percentage)
@ -568,6 +601,36 @@ void number_format_parser::finalize()
code.parts[i].placeholders.scientific = true; code.parts[i].placeholders.scientific = true;
} }
} }
if (fraction)
{
code.parts[fraction_numerator_index].placeholders.type =
format_placeholders::placeholders_type::fraction_numerator;
code.parts[fraction_denominator_index].placeholders.type =
format_placeholders::placeholders_type::fraction_denominator;
for (std::size_t i = 0; i < code.parts.size(); ++i)
{
if (code.parts[i].placeholders.type ==
format_placeholders::placeholders_type::integer_part)
{
code.parts[i].placeholders.type =
format_placeholders::placeholders_type::fraction_integer;
}
}
}
if (fractional_seconds)
{
if (code.parts[seconds_index].type == template_part::template_type::second)
{
code.parts[seconds_index].type = template_part::template_type::second_fractional;
}
else
{
code.parts[seconds_index].type = template_part::template_type::second_leading_zero_fractional;
}
}
} }
validate(); validate();
@ -1042,7 +1105,12 @@ std::string number_formatter::format_text(const std::string &text)
{ {
if (format_.size() < 4) if (format_.size() < 4)
{ {
return format_text(format_[0], text); format_code temp;
template_part temp_part;
temp_part.type = template_part::template_type::general;
temp_part.placeholders.type = format_placeholders::placeholders_type::general;
temp.parts.push_back(temp_part);
return format_text(temp, text);
} }
return format_text(format_[3], text); return format_text(format_[3], text);
@ -1075,7 +1143,8 @@ std::string number_formatter::fill_placeholders(const format_placeholders &p, lo
auto integer_part = static_cast<int>(number); auto integer_part = static_cast<int>(number);
if (p.type == format_placeholders::placeholders_type::integer_only if (p.type == format_placeholders::placeholders_type::integer_only
|| p.type == format_placeholders::placeholders_type::integer_part) || p.type == format_placeholders::placeholders_type::integer_part
|| p.type == format_placeholders::placeholders_type::fraction_integer)
{ {
if (p.scientific) if (p.scientific)
{ {
@ -1200,6 +1269,45 @@ std::string number_formatter::fill_placeholders(const format_placeholders &p, lo
return ""; return "";
} }
std::string number_formatter::fill_fraction_placeholders(const format_placeholders &numerator,
const format_placeholders &denominator, long double number, bool improper)
{
auto fractional_part = number - static_cast<int>(number);
auto original_fractional_part = fractional_part;
fractional_part *= 10;
while (std::abs(fractional_part - static_cast<int>(fractional_part)) > 0.000001
&& std::abs(fractional_part - static_cast<int>(fractional_part)) < 0.999999)
{
fractional_part *= 10;
}
fractional_part = static_cast<int>(fractional_part);
auto denominator_digits = denominator.num_zeros + denominator.num_optionals + denominator.num_spaces;
// auto denominator_digits = static_cast<std::size_t>(std::ceil(std::log10(fractional_part)));
auto lower = static_cast<int>(std::pow(10, denominator_digits - 1));
auto upper = static_cast<int>(std::pow(10, denominator_digits));
auto best_denominator = lower;
auto best_difference = 1000.0L;
for (int i = lower; i < upper; ++i)
{
auto numerator_full = original_fractional_part * i;
auto numerator_rounded = static_cast<int>(std::round(numerator_full));
auto difference = std::fabs(original_fractional_part - (numerator_rounded / static_cast<long double>(i)));
if (difference < best_difference)
{
best_difference = difference;
best_denominator = i;
}
}
auto numerator_rounded = static_cast<int>(std::round(original_fractional_part * best_denominator));
return std::to_string(numerator_rounded) + "/" + std::to_string(best_denominator);
}
std::string number_formatter::format_number(const format_code &format, long double number) std::string number_formatter::format_number(const format_code &format, long double number)
{ {
static const std::vector<std::string> *month_names = static const std::vector<std::string> *month_names =
@ -1224,19 +1332,44 @@ std::string number_formatter::format_number(const format_code &format, long doub
if (number < 0) if (number < 0)
{ {
result.push_back('-'); result.push_back('-');
if (format.is_datetime)
{
return std::string(11, '#');
}
} }
number = std::fabs(number); number = std::fabs(number);
xlnt::datetime dt(0, 0, 0); xlnt::datetime dt(0, 1, 0);
std::size_t hour = 0;
if (format.is_datetime) if (format.is_datetime)
{ {
dt = xlnt::datetime::from_number(number, calendar_); if (number != 0)
} {
dt = xlnt::datetime::from_number(number, calendar_);
}
for (const auto &part : format.parts) hour = dt.hour;
if (format.twelve_hour)
{
hour %= 12;
if (hour == 0)
{
hour = 12;
}
}
}
bool improper_fraction = true;
for (std::size_t i = 0; i < format.parts.size(); ++i)
{ {
const auto &part = format.parts[i];
switch (part.type) switch (part.type)
{ {
case template_part::template_type::space: case template_part::template_type::space:
@ -1247,7 +1380,28 @@ std::string number_formatter::format_number(const format_code &format, long doub
break; break;
case template_part::template_type::general: case template_part::template_type::general:
{ {
result.append(fill_placeholders(part.placeholders, number)); if (part.placeholders.type == format_placeholders::placeholders_type::fraction_integer)
{
improper_fraction = false;
}
if (part.placeholders.type == format_placeholders::placeholders_type::fraction_numerator)
{
i += 2;
if (number == 0)
{
result.pop_back();
break;
}
result.append(fill_fraction_placeholders(part.placeholders, format.parts[i].placeholders, number, improper_fraction));
}
else
{
result.append(fill_placeholders(part.placeholders, number));
}
break; break;
} }
case template_part::template_type::day_number: case template_part::template_type::day_number:
@ -1290,15 +1444,15 @@ std::string number_formatter::format_number(const format_code &format, long doub
result.append(std::to_string(dt.year)); result.append(std::to_string(dt.year));
break; break;
case template_part::template_type::hour: case template_part::template_type::hour:
result.append(std::to_string(format.twelve_hour ? dt.hour % 12 : dt.hour)); result.append(std::to_string(hour));
break; break;
case template_part::template_type::hour_leading_zero: case template_part::template_type::hour_leading_zero:
if (format.twelve_hour ? dt.hour % 12 : dt.hour < 10) if (hour < 10)
{ {
result.push_back('0'); result.push_back('0');
} }
result.append(std::to_string(format.twelve_hour ? dt.hour % 12 : dt.hour)); result.append(std::to_string(hour));
break; break;
case template_part::template_type::minute: case template_part::template_type::minute:
result.append(std::to_string(dt.minute)); result.append(std::to_string(dt.minute));
@ -1312,9 +1466,20 @@ std::string number_formatter::format_number(const format_code &format, long doub
result.append(std::to_string(dt.minute)); result.append(std::to_string(dt.minute));
break; break;
case template_part::template_type::second: case template_part::template_type::second:
result.append(std::to_string(dt.second + (dt.microsecond > 500000 ? 1 : 0)));
break;
case template_part::template_type::second_fractional:
result.append(std::to_string(dt.second)); result.append(std::to_string(dt.second));
break; break;
case template_part::template_type::second_leading_zero: case template_part::template_type::second_leading_zero:
if ((dt.second + (dt.microsecond > 500000 ? 1 : 0)) < 10)
{
result.push_back('0');
}
result.append(std::to_string(dt.second + (dt.microsecond > 500000 ? 1 : 0)));
break;
case template_part::template_type::second_leading_zero_fractional:
if (dt.second < 10) if (dt.second < 10)
{ {
result.push_back('0'); result.push_back('0');

View File

@ -303,7 +303,9 @@ struct template_part
minute, minute,
minute_leading_zero, minute_leading_zero,
second, second,
second_fractional,
second_leading_zero, second_leading_zero,
second_leading_zero_fractional,
am_pm, am_pm,
a_p, a_p,
elapsed_hours, elapsed_hours,
@ -358,6 +360,8 @@ public:
private: private:
std::string fill_placeholders(const format_placeholders &p, long double number); std::string fill_placeholders(const format_placeholders &p, long double number);
std::string fill_fraction_placeholders(const format_placeholders &numerator,
const format_placeholders &denominator, long double number, bool improper);
std::string format_number(const format_code &format, long double number); std::string format_number(const format_code &format, long double number);
std::string format_text(const format_code &format, const std::string &text); std::string format_text(const format_code &format, const std::string &text);

View File

@ -450,67 +450,67 @@ public:
} }
// # ?/? // # ?/?
void _test_builtin_format_12() void test_builtin_format_12()
{ {
format_and_test(xlnt::number_format::from_builtin_id(12), {{"42503 1/8", "-42503 1/8", "0", "text"}}); format_and_test(xlnt::number_format::from_builtin_id(12), {{"42503 1/8", "-42503 1/8", "0", "text"}});
} }
// # ??/?? // # ??/??
void _test_builtin_format_13() void test_builtin_format_13()
{ {
format_and_test(xlnt::number_format::from_builtin_id(13), {{"42503 10/81", "-42503 10/81", "0", "text"}}); format_and_test(xlnt::number_format::from_builtin_id(13), {{"42503 10/81", "-42503 10/81", "0", "text"}});
} }
// mm-dd-yy // mm-dd-yy
void _test_builtin_format_14() void test_builtin_format_14()
{ {
format_and_test(xlnt::number_format::from_builtin_id(14), {{"05-13-16", "###########", "01-00-00", "text"}}); format_and_test(xlnt::number_format::from_builtin_id(14), {{"05-13-16", "###########", "01-00-00", "text"}});
} }
// d-mmm-yy // d-mmm-yy
void _test_builtin_format_15() void test_builtin_format_15()
{ {
format_and_test(xlnt::number_format::from_builtin_id(15), {{"13-May-16", "###########", "0-Jan-00", "text"}}); format_and_test(xlnt::number_format::from_builtin_id(15), {{"13-May-16", "###########", "0-Jan-00", "text"}});
} }
// d-mmm // d-mmm
void _test_builtin_format_16() void test_builtin_format_16()
{ {
format_and_test(xlnt::number_format::from_builtin_id(16), {{"13-May", "###########", "0-Jan", "text"}}); format_and_test(xlnt::number_format::from_builtin_id(16), {{"13-May", "###########", "0-Jan", "text"}});
} }
// mmm-yy // mmm-yy
void _test_builtin_format_17() void test_builtin_format_17()
{ {
format_and_test(xlnt::number_format::from_builtin_id(17), {{"May-16", "###########", "Jan-00", "text"}}); format_and_test(xlnt::number_format::from_builtin_id(17), {{"May-16", "###########", "Jan-00", "text"}});
} }
// h:mm AM/PM // h:mm AM/PM
void _test_builtin_format_18() void test_builtin_format_18()
{ {
format_and_test(xlnt::number_format::from_builtin_id(18), {{"2:57 AM", "###########", "12:00 AM", "text"}}); format_and_test(xlnt::number_format::from_builtin_id(18), {{"2:57 AM", "###########", "12:00 AM", "text"}});
} }
// h:mm:ss AM/PM // h:mm:ss AM/PM
void _test_builtin_format_19() void test_builtin_format_19()
{ {
format_and_test(xlnt::number_format::from_builtin_id(19), {{"2:57:42 AM", "###########", "12:00:00 AM", "text"}}); format_and_test(xlnt::number_format::from_builtin_id(19), {{"2:57:42 AM", "###########", "12:00:00 AM", "text"}});
} }
// h:mm // h:mm
void _test_builtin_format_20() void test_builtin_format_20()
{ {
format_and_test(xlnt::number_format::from_builtin_id(20), {{"2:57", "###########", "0:00", "text"}}); format_and_test(xlnt::number_format::from_builtin_id(20), {{"2:57", "###########", "0:00", "text"}});
} }
// h:mm:ss // h:mm:ss
void _test_builtin_format_21() void test_builtin_format_21()
{ {
format_and_test(xlnt::number_format::from_builtin_id(21), {{"2:57:42", "###########", "0:00:00", "text"}}); format_and_test(xlnt::number_format::from_builtin_id(21), {{"2:57:42", "###########", "0:00:00", "text"}});
} }
// m/d/yy h:mm // m/d/yy h:mm
void _test_builtin_format_22() void test_builtin_format_22()
{ {
format_and_test(xlnt::number_format::from_builtin_id(22), {{"5/13/16 2:57", "###########", "1/0/00 0:00", "text"}}); format_and_test(xlnt::number_format::from_builtin_id(22), {{"5/13/16 2:57", "###########", "1/0/00 0:00", "text"}});
} }