blob: da8f69cf8c82a8745babd5943a93428b9a9e73f9 [file] [log] [blame]
/*
* Copyright 2015 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/Format.h>
#include <double-conversion/double-conversion.h>
namespace folly {
namespace detail {
extern const FormatArg::Align formatAlignTable[];
extern const FormatArg::Sign formatSignTable[];
template <typename T>
inline constexpr
T bespoke_max(const T a, const T b)
{
return (a > b) ? a : b;
}
template <typename T>
inline constexpr
T bespoke_max(const T a, const T b, const T c)
{
return bespoke_max(bespoke_max(a, b), c);
}
} // namespace detail
using namespace folly::detail;
void FormatValue<double>::formatHelper(
fbstring& piece, int& prefixLen, FormatArg& arg) const {
using ::double_conversion::DoubleToStringConverter;
using ::double_conversion::StringBuilder;
arg.validate(FormatArg::Type::FLOAT);
if (arg.presentation == FormatArg::kDefaultPresentation) {
arg.presentation = 'g';
}
const char* infinitySymbol = isupper(arg.presentation) ? "INF" : "inf";
const char* nanSymbol = isupper(arg.presentation) ? "NAN" : "nan";
char exponentSymbol = isupper(arg.presentation) ? 'E' : 'e';
if (arg.precision == FormatArg::kDefaultPrecision) {
arg.precision = 6;
}
// 2+: for null terminator and optional sign shenanigans.
constexpr unsigned buflen = 2 + bespoke_max(
(2 + DoubleToStringConverter::kMaxFixedDigitsBeforePoint +
DoubleToStringConverter::kMaxFixedDigitsAfterPoint),
(8 + DoubleToStringConverter::kMaxExponentialDigits),
(7 + DoubleToStringConverter::kMaxPrecisionDigits));
char buf[buflen];
StringBuilder builder(buf + 1, static_cast<int> (sizeof(buf) - 1));
char plusSign;
switch (arg.sign) {
case FormatArg::Sign::PLUS_OR_MINUS:
plusSign = '+';
break;
case FormatArg::Sign::SPACE_OR_MINUS:
plusSign = ' ';
break;
default:
plusSign = '\0';
break;
};
auto flags =
DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN |
(arg.trailingDot ? DoubleToStringConverter::EMIT_TRAILING_DECIMAL_POINT
: 0);
double val = val_;
switch (arg.presentation) {
case '%':
val *= 100;
case 'f':
case 'F':
{
if (arg.precision >
DoubleToStringConverter::kMaxFixedDigitsAfterPoint) {
arg.precision = DoubleToStringConverter::kMaxFixedDigitsAfterPoint;
}
DoubleToStringConverter conv(flags,
infinitySymbol,
nanSymbol,
exponentSymbol,
-4,
arg.precision,
0,
0);
arg.enforce(conv.ToFixed(val, arg.precision, &builder),
"fixed double conversion failed");
}
break;
case 'e':
case 'E':
{
if (arg.precision > DoubleToStringConverter::kMaxExponentialDigits) {
arg.precision = DoubleToStringConverter::kMaxExponentialDigits;
}
DoubleToStringConverter conv(flags,
infinitySymbol,
nanSymbol,
exponentSymbol,
-4,
arg.precision,
0,
0);
arg.enforce(conv.ToExponential(val, arg.precision, &builder));
}
break;
case 'n': // should be locale-aware, but isn't
case 'g':
case 'G':
{
if (arg.precision < DoubleToStringConverter::kMinPrecisionDigits) {
arg.precision = DoubleToStringConverter::kMinPrecisionDigits;
} else if (arg.precision >
DoubleToStringConverter::kMaxPrecisionDigits) {
arg.precision = DoubleToStringConverter::kMaxPrecisionDigits;
}
DoubleToStringConverter conv(flags,
infinitySymbol,
nanSymbol,
exponentSymbol,
-4,
arg.precision,
0,
0);
arg.enforce(conv.ToShortest(val, &builder));
}
break;
default:
arg.error("invalid specifier '", arg.presentation, "'");
}
int len = builder.position();
builder.Finalize();
DCHECK_GT(len, 0);
// Add '+' or ' ' sign if needed
char* p = buf + 1;
// anything that's neither negative nor nan
prefixLen = 0;
if (plusSign && (*p != '-' && *p != 'n' && *p != 'N')) {
*--p = plusSign;
++len;
prefixLen = 1;
} else if (*p == '-') {
prefixLen = 1;
}
piece = fbstring(p, len);
}
void FormatArg::initSlow() {
auto b = fullArgString.begin();
auto end = fullArgString.end();
// Parse key
auto p = static_cast<const char*>(memchr(b, ':', end - b));
if (!p) {
key_ = StringPiece(b, end);
return;
}
key_ = StringPiece(b, p);
if (*p == ':') {
// parse format spec
if (++p == end) return;
// fill/align, or just align
Align a;
if (p + 1 != end &&
(a = formatAlignTable[static_cast<unsigned char>(p[1])]) !=
Align::INVALID) {
fill = *p;
align = a;
p += 2;
if (p == end) return;
} else if ((a = formatAlignTable[static_cast<unsigned char>(*p)]) !=
Align::INVALID) {
align = a;
if (++p == end) return;
}
Sign s;
unsigned char uSign = static_cast<unsigned char>(*p);
if ((s = formatSignTable[uSign]) != Sign::INVALID) {
sign = s;
if (++p == end) return;
}
if (*p == '#') {
basePrefix = true;
if (++p == end) return;
}
if (*p == '0') {
enforce(align == Align::DEFAULT, "alignment specified twice");
fill = '0';
align = Align::PAD_AFTER_SIGN;
if (++p == end) return;
}
auto readInt = [&] {
auto const b = p;
do {
++p;
} while (p != end && *p >= '0' && *p <= '9');
return to<int>(StringPiece(b, p));
};
if (*p == '*') {
width = kDynamicWidth;
++p;
if (p == end) return;
if (*p >= '0' && *p <= '9') widthIndex = readInt();
if (p == end) return;
} else if (*p >= '0' && *p <= '9') {
width = readInt();
if (p == end) return;
}
if (*p == ',') {
thousandsSeparator = true;
if (++p == end) return;
}
if (*p == '.') {
auto b = ++p;
while (p != end && *p >= '0' && *p <= '9') {
++p;
}
if (p != b) {
precision = to<int>(StringPiece(b, p));
if (p != end && *p == '.') {
trailingDot = true;
++p;
}
} else {
trailingDot = true;
}
if (p == end) return;
}
presentation = *p;
if (++p == end) return;
}
error("extra characters in format string");
}
void FormatArg::validate(Type type) const {
enforce(keyEmpty(), "index not allowed");
switch (type) {
case Type::INTEGER:
enforce(precision == kDefaultPrecision,
"precision not allowed on integers");
break;
case Type::FLOAT:
enforce(!basePrefix,
"base prefix ('#') specifier only allowed on integers");
enforce(!thousandsSeparator,
"thousands separator (',') only allowed on integers");
break;
case Type::OTHER:
enforce(align != Align::PAD_AFTER_SIGN,
"'='alignment only allowed on numbers");
enforce(sign == Sign::DEFAULT,
"sign specifier only allowed on numbers");
enforce(!basePrefix,
"base prefix ('#') specifier only allowed on integers");
enforce(!thousandsSeparator,
"thousands separator (',') only allowed on integers");
break;
}
}
namespace detail {
void insertThousandsGroupingUnsafe(char* start_buffer, char** end_buffer) {
uint32_t remaining_digits = *end_buffer - start_buffer;
uint32_t separator_size = (remaining_digits - 1) / 3;
uint32_t result_size = remaining_digits + separator_size;
*end_buffer = *end_buffer + separator_size;
// get the end of the new string with the separators
uint32_t buffer_write_index = result_size - 1;
uint32_t buffer_read_index = remaining_digits - 1;
start_buffer[buffer_write_index + 1] = 0;
bool done = false;
uint32_t next_group_size = 3;
while (!done) {
uint32_t current_group_size = std::max<uint32_t>(1,
std::min<uint32_t>(remaining_digits, next_group_size));
// write out the current group's digits to the buffer index
for (uint32_t i = 0; i < current_group_size; i++) {
start_buffer[buffer_write_index--] = start_buffer[buffer_read_index--];
}
// if not finished, write the separator before the next group
if (buffer_write_index < buffer_write_index + 1) {
start_buffer[buffer_write_index--] = ',';
} else {
done = true;
}
remaining_digits -= current_group_size;
}
}
} // detail
} // namespace folly