Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
F
fmt
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Libraries
fmt
Commits
ded1e767
Commit
ded1e767
authored
Nov 21, 2019
by
Victor Zverovich
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor floating point formatting
parent
c7edd8e5
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
176 additions
and
211 deletions
+176
-211
include/fmt/format-inl.h
include/fmt/format-inl.h
+84
-81
include/fmt/format.h
include/fmt/format.h
+86
-124
src/format.cc
src/format.cc
+5
-5
support/Vagrantfile
support/Vagrantfile
+1
-1
No files found.
include/fmt/format-inl.h
View file @
ded1e767
...
@@ -51,12 +51,8 @@
...
@@ -51,12 +51,8 @@
// Dummy implementations of strerror_r and strerror_s called if corresponding
// Dummy implementations of strerror_r and strerror_s called if corresponding
// system functions are not available.
// system functions are not available.
inline
fmt
::
internal
::
null
<>
strerror_r
(
int
,
char
*
,
...)
{
inline
fmt
::
internal
::
null
<>
strerror_r
(
int
,
char
*
,
...)
{
return
{};
}
return
{};
inline
fmt
::
internal
::
null
<>
strerror_s
(
char
*
,
std
::
size_t
,
...)
{
return
{};
}
}
inline
fmt
::
internal
::
null
<>
strerror_s
(
char
*
,
std
::
size_t
,
...)
{
return
{};
}
FMT_BEGIN_NAMESPACE
FMT_BEGIN_NAMESPACE
namespace
internal
{
namespace
internal
{
...
@@ -250,20 +246,6 @@ template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) {
...
@@ -250,20 +246,6 @@ template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) {
return
i
>=
0
?
i
*
char_digits
+
count_digits
<
4
,
unsigned
>
(
n
.
value
[
i
])
:
1
;
return
i
>=
0
?
i
*
char_digits
+
count_digits
<
4
,
unsigned
>
(
n
.
value
[
i
])
:
1
;
}
}
template
<
typename
T
>
int
format_float
(
char
*
buf
,
std
::
size_t
size
,
const
char
*
format
,
int
precision
,
T
value
)
{
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if
(
precision
>
100000
)
throw
std
::
runtime_error
(
"fuzz mode - avoid large allocation inside snprintf"
);
#endif
// Suppress the warning about nonliteral format string.
auto
snprintf_ptr
=
FMT_SNPRINTF
;
return
precision
<
0
?
snprintf_ptr
(
buf
,
size
,
format
,
value
)
:
snprintf_ptr
(
buf
,
size
,
format
,
precision
,
value
);
}
template
<
typename
T
>
template
<
typename
T
>
const
char
basic_data
<
T
>::
digits
[]
=
const
char
basic_data
<
T
>::
digits
[]
=
"0001020304050607080910111213141516171819"
"0001020304050607080910111213141516171819"
...
@@ -276,9 +258,9 @@ template <typename T>
...
@@ -276,9 +258,9 @@ template <typename T>
const
char
basic_data
<
T
>::
hex_digits
[]
=
"0123456789abcdef"
;
const
char
basic_data
<
T
>::
hex_digits
[]
=
"0123456789abcdef"
;
#define FMT_POWERS_OF_10(factor) \
#define FMT_POWERS_OF_10(factor) \
factor * 10, (factor)
* 100, (factor) * 1000, (factor) * 10000, (factor) *
100000, \
factor * 10, (factor)
*100, (factor)*1000, (factor)*10000, (factor)*
100000, \
(factor)
* 1000000, (factor) * 10000000, (factor) *
100000000, \
(factor)
*1000000, (factor)*10000000, (factor)*
100000000, \
(factor)
*
1000000000
(factor)
*
1000000000
template
<
typename
T
>
template
<
typename
T
>
const
uint64_t
basic_data
<
T
>::
powers_of_10_64
[]
=
{
const
uint64_t
basic_data
<
T
>::
powers_of_10_64
[]
=
{
...
@@ -1121,78 +1103,99 @@ bool grisu_format(Double value, buffer<char>& buf, int precision,
...
@@ -1121,78 +1103,99 @@ bool grisu_format(Double value, buffer<char>& buf, int precision,
return
true
;
return
true
;
}
}
template
<
typename
Double
>
template
<
typename
Float
>
char
*
sprintf_format
(
Double
value
,
internal
::
buffer
<
char
>&
buf
,
int
sprintf_format
(
Float
value
,
int
precision
,
float_spec
spec
,
sprintf_specs
specs
)
{
buffer
<
char
>&
buf
)
{
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
FMT_ASSERT
(
buf
.
capacity
()
!=
0
,
"empty buffer"
);
FMT_ASSERT
(
buf
.
capacity
()
>
buf
.
size
(),
"empty buffer"
);
// Subtract 1 to account for the difference in precision since we use %e for
// both general and exponent format.
if
(
spec
.
format
==
float_format
::
general
)
precision
=
(
precision
>=
0
?
precision
:
6
)
-
1
;
// Build format string.
// Build
the
format string.
enum
{
max_format_size
=
10
};
// longest format: %#-*.*Lg
enum
{
max_format_size
=
7
};
// Ths longest format is "%#.*Le".
char
format
[
max_format_size
];
char
format
[
max_format_size
];
char
*
format_ptr
=
format
;
char
*
format_ptr
=
format
;
*
format_ptr
++
=
'%'
;
*
format_ptr
++
=
'%'
;
if
(
spec
s
.
alt
||
!
specs
.
type
)
*
format_ptr
++
=
'#'
;
if
(
spec
.
alt
)
*
format_ptr
++
=
'#'
;
if
(
specs
.
precision
>=
0
)
{
if
(
precision
>
0
)
{
*
format_ptr
++
=
'.'
;
*
format_ptr
++
=
'.'
;
*
format_ptr
++
=
'*'
;
*
format_ptr
++
=
'*'
;
}
}
if
(
std
::
is_same
<
Double
,
long
double
>::
value
)
*
format_ptr
++
=
'L'
;
if
(
std
::
is_same
<
Float
,
long
double
>
())
*
format_ptr
++
=
'L'
;
*
format_ptr
++
=
spec
.
format
!=
float_format
::
hex
char
type
=
specs
.
type
;
?
(
spec
.
format
==
float_format
::
fixed
?
'f'
:
'e'
)
:
(
spec
.
upper
?
'A'
:
'a'
);
if
(
type
==
'%'
)
type
=
'f'
;
else
if
(
type
==
0
||
type
==
'n'
)
type
=
'g'
;
if
(
FMT_MSC_VER
&&
type
==
'F'
)
type
=
'f'
;
// // MSVC's printf doesn't support 'F'.
*
format_ptr
++
=
type
;
*
format_ptr
=
'\0'
;
*
format_ptr
=
'\0'
;
// Format using snprintf.
// Format using snprintf.
char
*
start
=
nullptr
;
auto
offset
=
buf
.
size
();
char
*
decimal_point_pos
=
nullptr
;
for
(;;)
{
for
(;;)
{
std
::
size_t
buffer_size
=
buf
.
capacity
();
auto
begin
=
buf
.
data
()
+
offset
;
start
=
&
buf
[
0
];
auto
capacity
=
buf
.
capacity
()
-
offset
;
int
result
=
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
format_float
(
start
,
buffer_size
,
format
,
specs
.
precision
,
value
);
if
(
precision
>
100000
)
if
(
result
>=
0
)
{
throw
std
::
runtime_error
(
unsigned
n
=
internal
::
to_unsigned
(
result
);
"fuzz mode - avoid large allocation inside snprintf"
);
if
(
n
<
buf
.
capacity
())
{
#endif
// Find the decimal point.
// Suppress the warning about a nonliteral format string.
auto
p
=
buf
.
data
(),
end
=
p
+
n
;
auto
snprintf_ptr
=
FMT_SNPRINTF
;
if
(
*
p
==
'+'
||
*
p
==
'-'
)
++
p
;
int
result
=
precision
>
0
if
(
specs
.
type
!=
'a'
&&
specs
.
type
!=
'A'
)
{
?
snprintf_ptr
(
begin
,
capacity
,
format
,
precision
,
value
)
while
(
p
<
end
&&
*
p
>=
'0'
&&
*
p
<=
'9'
)
++
p
;
:
snprintf_ptr
(
begin
,
capacity
,
format
,
value
);
if
(
p
<
end
&&
*
p
!=
'e'
&&
*
p
!=
'E'
)
{
if
(
result
<
0
)
{
decimal_point_pos
=
p
;
buf
.
reserve
(
capacity
+
1
);
// The buffer will grow exponentially.
if
(
!
specs
.
type
)
{
continue
;
// Keep only one trailing zero after the decimal point.
}
++
p
;
unsigned
size
=
to_unsigned
(
result
);
if
(
*
p
==
'0'
)
++
p
;
if
(
size
>
capacity
)
{
while
(
p
!=
end
&&
*
p
>=
'1'
&&
*
p
<=
'9'
)
++
p
;
buf
.
reserve
(
size
+
1
);
// Add 1 for the terminating '\0'.
char
*
where
=
p
;
continue
;
while
(
p
!=
end
&&
*
p
==
'0'
)
++
p
;
}
if
(
p
==
end
||
*
p
<
'0'
||
*
p
>
'9'
)
{
auto
is_digit
=
[](
char
c
)
{
return
c
>=
'0'
&&
c
<=
'9'
;
};
if
(
p
!=
end
)
std
::
memmove
(
where
,
p
,
to_unsigned
(
end
-
p
));
if
(
spec
.
format
==
float_format
::
fixed
)
{
n
-=
static_cast
<
unsigned
>
(
p
-
where
);
// Find and remove the decimal point.
}
auto
end
=
begin
+
size
,
p
=
end
;
}
do
{
}
--
p
;
}
}
while
(
is_digit
(
*
p
));
buf
.
resize
(
n
);
int
fraction_size
=
static_cast
<
int
>
(
end
-
p
-
1
);
break
;
// The buffer is large enough - continue with formatting.
std
::
memmove
(
p
,
p
+
1
,
fraction_size
);
}
buf
.
resize
(
size
-
1
);
buf
.
reserve
(
n
+
1
);
return
-
fraction_size
;
}
else
{
}
// If result is negative we ask to increase the capacity by at least 1,
if
(
spec
.
format
==
float_format
::
hex
)
{
// but as std::vector, the buffer grows exponentially.
buf
.
resize
(
size
+
offset
);
buf
.
reserve
(
buf
.
capacity
()
+
1
);
return
0
;
}
// Find and parse the exponent.
auto
end
=
begin
+
size
,
exp_pos
=
end
;
do
{
--
exp_pos
;
}
while
(
*
exp_pos
!=
'e'
);
char
sign
=
exp_pos
[
1
];
assert
(
sign
==
'+'
||
sign
==
'-'
);
int
exp
=
0
;
auto
p
=
exp_pos
+
2
;
// Skip 'e' and sign.
do
{
assert
(
is_digit
(
*
p
));
exp
=
exp
*
10
+
(
*
p
++
-
'0'
);
}
while
(
p
!=
end
);
if
(
sign
==
'-'
)
exp
=
-
exp
;
if
(
exp_pos
!=
begin
+
1
)
{
// Remove trailing zeros.
auto
fraction_end
=
exp_pos
-
1
;
while
(
*
fraction_end
==
'0'
)
--
fraction_end
;
// Move the fractional part left to get rid of the decimal point.
int
fraction_size
=
static_cast
<
int
>
(
fraction_end
-
begin
-
1
);
std
::
memmove
(
begin
+
1
,
begin
+
2
,
fraction_size
);
buf
.
resize
(
fraction_size
+
offset
+
1
);
exp
-=
fraction_size
;
}
}
return
exp
;
}
}
return
decimal_point_pos
;
}
}
}
// namespace internal
}
// namespace internal
...
...
include/fmt/format.h
View file @
ded1e767
...
@@ -1074,6 +1074,7 @@ struct float_spec {
...
@@ -1074,6 +1074,7 @@ struct float_spec {
bool
upper
;
bool
upper
;
bool
locale
;
bool
locale
;
bool
percent
;
bool
percent
;
bool
alt
;
};
};
struct
gen_digits_params
{
struct
gen_digits_params
{
...
@@ -1086,7 +1087,7 @@ struct gen_digits_params {
...
@@ -1086,7 +1087,7 @@ struct gen_digits_params {
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
template
<
typename
Char
,
typename
It
>
It
write_exponent
(
int
exp
,
It
it
)
{
template
<
typename
Char
,
typename
It
>
It
write_exponent
(
int
exp
,
It
it
)
{
FMT_ASSERT
(
-
1000
<
exp
&&
exp
<
1
000
,
"exponent out of range"
);
FMT_ASSERT
(
-
1000
0
<
exp
&&
exp
<
10
000
,
"exponent out of range"
);
if
(
exp
<
0
)
{
if
(
exp
<
0
)
{
*
it
++
=
static_cast
<
Char
>
(
'-'
);
*
it
++
=
static_cast
<
Char
>
(
'-'
);
exp
=
-
exp
;
exp
=
-
exp
;
...
@@ -1094,7 +1095,9 @@ template <typename Char, typename It> It write_exponent(int exp, It it) {
...
@@ -1094,7 +1095,9 @@ template <typename Char, typename It> It write_exponent(int exp, It it) {
*
it
++
=
static_cast
<
Char
>
(
'+'
);
*
it
++
=
static_cast
<
Char
>
(
'+'
);
}
}
if
(
exp
>=
100
)
{
if
(
exp
>=
100
)
{
*
it
++
=
static_cast
<
Char
>
(
static_cast
<
char
>
(
'0'
+
exp
/
100
));
const
char
*
top
=
data
::
digits
+
(
exp
/
100
)
*
2
;
if
(
exp
>=
1000
)
*
it
++
=
static_cast
<
Char
>
(
top
[
0
]);
*
it
++
=
static_cast
<
Char
>
(
top
[
1
]);
exp
%=
100
;
exp
%=
100
;
}
}
const
char
*
d
=
data
::
digits
+
exp
*
2
;
const
char
*
d
=
data
::
digits
+
exp
*
2
;
...
@@ -1103,7 +1106,7 @@ template <typename Char, typename It> It write_exponent(int exp, It it) {
...
@@ -1103,7 +1106,7 @@ template <typename Char, typename It> It write_exponent(int exp, It it) {
return
it
;
return
it
;
}
}
template
<
typename
Char
>
class
grisu
_writer
{
template
<
typename
Char
>
class
float
_writer
{
private:
private:
// The number is given as v = digits_ * pow(10, exp_).
// The number is given as v = digits_ * pow(10, exp_).
const
char
*
digits_
;
const
char
*
digits_
;
...
@@ -1177,7 +1180,7 @@ template <typename Char> class grisu_writer {
...
@@ -1177,7 +1180,7 @@ template <typename Char> class grisu_writer {
}
}
public:
public:
grisu
_writer
(
const
char
*
digits
,
int
num_digits
,
int
exp
,
float
_writer
(
const
char
*
digits
,
int
num_digits
,
int
exp
,
gen_digits_params
params
,
Char
decimal_point
)
gen_digits_params
params
,
Char
decimal_point
)
:
digits_
(
digits
),
:
digits_
(
digits
),
num_digits_
(
num_digits
),
num_digits_
(
num_digits
),
...
@@ -1217,26 +1220,15 @@ inline bool grisu_format(Double, buffer<char>&, int, unsigned, int&) {
...
@@ -1217,26 +1220,15 @@ inline bool grisu_format(Double, buffer<char>&, int, unsigned, int&) {
return
false
;
return
false
;
}
}
struct
sprintf_specs
{
template
<
typename
Float
>
int
precision
;
int
sprintf_format
(
Float
value
,
int
precision
,
float_spec
spec
,
char
type
;
buffer
<
char
>&
buf
);
bool
alt
:
1
;
template
<
typename
Char
>
constexpr
sprintf_specs
(
basic_format_specs
<
Char
>
specs
)
:
precision
(
specs
.
precision
),
type
(
specs
.
type
),
alt
(
specs
.
alt
)
{}
constexpr
bool
has_precision
()
const
{
return
precision
>=
0
;
}
};
template
<
typename
Double
>
char
*
sprintf_format
(
Double
,
internal
::
buffer
<
char
>&
,
sprintf_specs
);
template
<
>
template
<
>
inline
char
*
sprintf_format
<
float
>
(
float
value
,
internal
::
buffer
<
char
>&
buf
,
inline
int
sprintf_format
<
float
>
(
float
value
,
int
precision
,
float_spec
spec
,
sprintf_specs
specs
)
{
buffer
<
char
>&
buf
)
{
//
printf does not have a float format specifier, it only supports
double.
//
sprintf does not support float so convert to
double.
return
sprintf_format
<
double
>
(
value
,
buf
,
specs
);
return
sprintf_format
<
double
>
(
value
,
precision
,
spec
,
buf
);
}
}
template
<
typename
Handler
>
template
<
typename
Handler
>
...
@@ -1422,7 +1414,7 @@ template <typename Range> class basic_writer {
...
@@ -1422,7 +1414,7 @@ template <typename Range> class basic_writer {
private:
private:
iterator
out_
;
// Output iterator.
iterator
out_
;
// Output iterator.
internal
::
locale_ref
locale_
;
locale_ref
locale_
;
// Attempts to reserve space for n extra characters in the output range.
// Attempts to reserve space for n extra characters in the output range.
// Returns a pointer to the reserved range or a reference to out_.
// Returns a pointer to the reserved range or a reference to out_.
...
@@ -1442,7 +1434,7 @@ template <typename Range> class basic_writer {
...
@@ -1442,7 +1434,7 @@ template <typename Range> class basic_writer {
template
<
typename
It
>
void
operator
()(
It
&&
it
)
const
{
template
<
typename
It
>
void
operator
()(
It
&&
it
)
const
{
if
(
prefix
.
size
()
!=
0
)
if
(
prefix
.
size
()
!=
0
)
it
=
internal
::
copy_str
<
char_type
>
(
prefix
.
begin
(),
prefix
.
end
(),
it
);
it
=
copy_str
<
char_type
>
(
prefix
.
begin
(),
prefix
.
end
(),
it
);
it
=
std
::
fill_n
(
it
,
padding
,
fill
);
it
=
std
::
fill_n
(
it
,
padding
,
fill
);
f
(
it
);
f
(
it
);
}
}
...
@@ -1453,18 +1445,18 @@ template <typename Range> class basic_writer {
...
@@ -1453,18 +1445,18 @@ template <typename Range> class basic_writer {
// where <digits> are written by f(it).
// where <digits> are written by f(it).
template
<
typename
F
>
template
<
typename
F
>
void
write_int
(
int
num_digits
,
string_view
prefix
,
format_specs
specs
,
F
f
)
{
void
write_int
(
int
num_digits
,
string_view
prefix
,
format_specs
specs
,
F
f
)
{
std
::
size_t
size
=
prefix
.
size
()
+
internal
::
to_unsigned
(
num_digits
);
std
::
size_t
size
=
prefix
.
size
()
+
to_unsigned
(
num_digits
);
char_type
fill
=
specs
.
fill
[
0
];
char_type
fill
=
specs
.
fill
[
0
];
std
::
size_t
padding
=
0
;
std
::
size_t
padding
=
0
;
if
(
specs
.
align
==
align
::
numeric
)
{
if
(
specs
.
align
==
align
::
numeric
)
{
auto
unsiged_width
=
internal
::
to_unsigned
(
specs
.
width
);
auto
unsiged_width
=
to_unsigned
(
specs
.
width
);
if
(
unsiged_width
>
size
)
{
if
(
unsiged_width
>
size
)
{
padding
=
unsiged_width
-
size
;
padding
=
unsiged_width
-
size
;
size
=
unsiged_width
;
size
=
unsiged_width
;
}
}
}
else
if
(
specs
.
precision
>
num_digits
)
{
}
else
if
(
specs
.
precision
>
num_digits
)
{
size
=
prefix
.
size
()
+
internal
::
to_unsigned
(
specs
.
precision
);
size
=
prefix
.
size
()
+
to_unsigned
(
specs
.
precision
);
padding
=
internal
::
to_unsigned
(
specs
.
precision
-
num_digits
);
padding
=
to_unsigned
(
specs
.
precision
-
num_digits
);
fill
=
static_cast
<
char_type
>
(
'0'
);
fill
=
static_cast
<
char_type
>
(
'0'
);
}
}
if
(
specs
.
align
==
align
::
none
)
specs
.
align
=
align
::
right
;
if
(
specs
.
align
==
align
::
none
)
specs
.
align
=
align
::
right
;
...
@@ -1474,14 +1466,13 @@ template <typename Range> class basic_writer {
...
@@ -1474,14 +1466,13 @@ template <typename Range> class basic_writer {
// Writes a decimal integer.
// Writes a decimal integer.
template
<
typename
Int
>
void
write_decimal
(
Int
value
)
{
template
<
typename
Int
>
void
write_decimal
(
Int
value
)
{
auto
abs_value
=
static_cast
<
uint32_or_64_or_128_t
<
Int
>>
(
value
);
auto
abs_value
=
static_cast
<
uint32_or_64_or_128_t
<
Int
>>
(
value
);
bool
is_negative
=
internal
::
is_negative
(
value
);
bool
negative
=
is_negative
(
value
);
// Don't do -abs_value since it trips unsigned-integer-overflow sanitizer.
// Don't do -abs_value since it trips unsigned-integer-overflow sanitizer.
if
(
is_negative
)
abs_value
=
~
abs_value
+
1
;
if
(
negative
)
abs_value
=
~
abs_value
+
1
;
int
num_digits
=
internal
::
count_digits
(
abs_value
);
int
num_digits
=
count_digits
(
abs_value
);
auto
&&
it
=
auto
&&
it
=
reserve
((
negative
?
1
:
0
)
+
static_cast
<
size_t
>
(
num_digits
));
reserve
((
is_negative
?
1
:
0
)
+
static_cast
<
size_t
>
(
num_digits
));
if
(
negative
)
*
it
++
=
static_cast
<
char_type
>
(
'-'
);
if
(
is_negative
)
*
it
++
=
static_cast
<
char_type
>
(
'-'
);
it
=
format_decimal
<
char_type
>
(
it
,
abs_value
,
num_digits
);
it
=
internal
::
format_decimal
<
char_type
>
(
it
,
abs_value
,
num_digits
);
}
}
// The handle_int_type_spec handler that writes an integer.
// The handle_int_type_spec handler that writes an integer.
...
@@ -1501,7 +1492,7 @@ template <typename Range> class basic_writer {
...
@@ -1501,7 +1492,7 @@ template <typename Range> class basic_writer {
specs
(
s
),
specs
(
s
),
abs_value
(
static_cast
<
unsigned_type
>
(
value
)),
abs_value
(
static_cast
<
unsigned_type
>
(
value
)),
prefix_size
(
0
)
{
prefix_size
(
0
)
{
if
(
i
nternal
::
i
s_negative
(
value
))
{
if
(
is_negative
(
value
))
{
prefix
[
0
]
=
'-'
;
prefix
[
0
]
=
'-'
;
++
prefix_size
;
++
prefix_size
;
abs_value
=
0
-
abs_value
;
abs_value
=
0
-
abs_value
;
...
@@ -1521,7 +1512,7 @@ template <typename Range> class basic_writer {
...
@@ -1521,7 +1512,7 @@ template <typename Range> class basic_writer {
};
};
void
on_dec
()
{
void
on_dec
()
{
int
num_digits
=
internal
::
count_digits
(
abs_value
);
int
num_digits
=
count_digits
(
abs_value
);
writer
.
write_int
(
num_digits
,
get_prefix
(),
specs
,
writer
.
write_int
(
num_digits
,
get_prefix
(),
specs
,
dec_writer
{
abs_value
,
num_digits
});
dec_writer
{
abs_value
,
num_digits
});
}
}
...
@@ -1531,8 +1522,8 @@ template <typename Range> class basic_writer {
...
@@ -1531,8 +1522,8 @@ template <typename Range> class basic_writer {
int
num_digits
;
int
num_digits
;
template
<
typename
It
>
void
operator
()(
It
&&
it
)
const
{
template
<
typename
It
>
void
operator
()(
It
&&
it
)
const
{
it
=
internal
::
format_uint
<
4
,
char_type
>
(
it
,
self
.
abs_value
,
num_digits
,
it
=
format_uint
<
4
,
char_type
>
(
it
,
self
.
abs_value
,
num_digits
,
self
.
specs
.
type
!=
'x'
);
self
.
specs
.
type
!=
'x'
);
}
}
};
};
...
@@ -1541,7 +1532,7 @@ template <typename Range> class basic_writer {
...
@@ -1541,7 +1532,7 @@ template <typename Range> class basic_writer {
prefix
[
prefix_size
++
]
=
'0'
;
prefix
[
prefix_size
++
]
=
'0'
;
prefix
[
prefix_size
++
]
=
specs
.
type
;
prefix
[
prefix_size
++
]
=
specs
.
type
;
}
}
int
num_digits
=
internal
::
count_digits
<
4
>
(
abs_value
);
int
num_digits
=
count_digits
<
4
>
(
abs_value
);
writer
.
write_int
(
num_digits
,
get_prefix
(),
specs
,
writer
.
write_int
(
num_digits
,
get_prefix
(),
specs
,
hex_writer
{
*
this
,
num_digits
});
hex_writer
{
*
this
,
num_digits
});
}
}
...
@@ -1551,7 +1542,7 @@ template <typename Range> class basic_writer {
...
@@ -1551,7 +1542,7 @@ template <typename Range> class basic_writer {
int
num_digits
;
int
num_digits
;
template
<
typename
It
>
void
operator
()(
It
&&
it
)
const
{
template
<
typename
It
>
void
operator
()(
It
&&
it
)
const
{
it
=
internal
::
format_uint
<
BITS
,
char_type
>
(
it
,
abs_value
,
num_digits
);
it
=
format_uint
<
BITS
,
char_type
>
(
it
,
abs_value
,
num_digits
);
}
}
};
};
...
@@ -1560,13 +1551,13 @@ template <typename Range> class basic_writer {
...
@@ -1560,13 +1551,13 @@ template <typename Range> class basic_writer {
prefix
[
prefix_size
++
]
=
'0'
;
prefix
[
prefix_size
++
]
=
'0'
;
prefix
[
prefix_size
++
]
=
static_cast
<
char
>
(
specs
.
type
);
prefix
[
prefix_size
++
]
=
static_cast
<
char
>
(
specs
.
type
);
}
}
int
num_digits
=
internal
::
count_digits
<
1
>
(
abs_value
);
int
num_digits
=
count_digits
<
1
>
(
abs_value
);
writer
.
write_int
(
num_digits
,
get_prefix
(),
specs
,
writer
.
write_int
(
num_digits
,
get_prefix
(),
specs
,
bin_writer
<
1
>
{
abs_value
,
num_digits
});
bin_writer
<
1
>
{
abs_value
,
num_digits
});
}
}
void
on_oct
()
{
void
on_oct
()
{
int
num_digits
=
internal
::
count_digits
<
3
>
(
abs_value
);
int
num_digits
=
count_digits
<
3
>
(
abs_value
);
if
(
specs
.
alt
&&
specs
.
precision
<=
num_digits
&&
abs_value
!=
0
)
{
if
(
specs
.
alt
&&
specs
.
precision
<=
num_digits
&&
abs_value
!=
0
)
{
// Octal prefix '0' is counted as a digit, so only add it if precision
// Octal prefix '0' is counted as a digit, so only add it if precision
// is not greater than the number of digits.
// is not greater than the number of digits.
...
@@ -1590,7 +1581,7 @@ template <typename Range> class basic_writer {
...
@@ -1590,7 +1581,7 @@ template <typename Range> class basic_writer {
// index 0.
// index 0.
unsigned
digit_index
=
0
;
unsigned
digit_index
=
0
;
std
::
string
::
const_iterator
group
=
groups
.
cbegin
();
std
::
string
::
const_iterator
group
=
groups
.
cbegin
();
it
=
internal
::
format_decimal
<
char_type
>
(
it
=
format_decimal
<
char_type
>
(
it
,
abs_value
,
size
,
it
,
abs_value
,
size
,
[
this
,
s
,
&
group
,
&
digit_index
](
char_type
*&
buffer
)
{
[
this
,
s
,
&
group
,
&
digit_index
](
char_type
*&
buffer
)
{
if
(
*
group
<=
0
||
++
digit_index
%
*
group
!=
0
||
if
(
*
group
<=
0
||
++
digit_index
%
*
group
!=
0
||
...
@@ -1602,17 +1593,17 @@ template <typename Range> class basic_writer {
...
@@ -1602,17 +1593,17 @@ template <typename Range> class basic_writer {
}
}
buffer
-=
s
.
size
();
buffer
-=
s
.
size
();
std
::
uninitialized_copy
(
s
.
data
(),
s
.
data
()
+
s
.
size
(),
std
::
uninitialized_copy
(
s
.
data
(),
s
.
data
()
+
s
.
size
(),
internal
::
make_checked
(
buffer
,
s
.
size
()));
make_checked
(
buffer
,
s
.
size
()));
});
});
}
}
};
};
void
on_num
()
{
void
on_num
()
{
std
::
string
groups
=
internal
::
grouping
<
char_type
>
(
writer
.
locale_
);
std
::
string
groups
=
grouping
<
char_type
>
(
writer
.
locale_
);
if
(
groups
.
empty
())
return
on_dec
();
if
(
groups
.
empty
())
return
on_dec
();
auto
sep
=
internal
::
thousands_sep
<
char_type
>
(
writer
.
locale_
);
auto
sep
=
thousands_sep
<
char_type
>
(
writer
.
locale_
);
if
(
!
sep
)
return
on_dec
();
if
(
!
sep
)
return
on_dec
();
int
num_digits
=
internal
::
count_digits
(
abs_value
);
int
num_digits
=
count_digits
(
abs_value
);
int
size
=
num_digits
;
int
size
=
num_digits
;
std
::
string
::
const_iterator
group
=
groups
.
cbegin
();
std
::
string
::
const_iterator
group
=
groups
.
cbegin
();
while
(
group
!=
groups
.
cend
()
&&
num_digits
>
*
group
&&
*
group
>
0
&&
while
(
group
!=
groups
.
cend
()
&&
num_digits
>
*
group
&&
*
group
>
0
&&
...
@@ -1632,38 +1623,17 @@ template <typename Range> class basic_writer {
...
@@ -1632,38 +1623,17 @@ template <typename Range> class basic_writer {
}
}
};
};
struct
double_writer
{
sign_t
sign
;
internal
::
buffer
<
char
>&
buffer
;
char
*
decimal_point_pos
;
char_type
decimal_point
;
size_t
size
()
const
{
return
buffer
.
size
()
+
(
sign
?
1
:
0
);
}
size_t
width
()
const
{
return
size
();
}
template
<
typename
It
>
void
operator
()(
It
&&
it
)
{
if
(
sign
)
*
it
++
=
static_cast
<
char_type
>
(
data
::
signs
[
sign
]);
auto
begin
=
buffer
.
begin
();
if
(
decimal_point_pos
)
{
it
=
internal
::
copy_str
<
char_type
>
(
begin
,
decimal_point_pos
,
it
);
*
it
++
=
decimal_point
;
begin
=
decimal_point_pos
+
1
;
}
it
=
internal
::
copy_str
<
char_type
>
(
begin
,
buffer
.
end
(),
it
);
}
};
template
<
typename
Char
>
struct
str_writer
{
template
<
typename
Char
>
struct
str_writer
{
const
Char
*
s
;
const
Char
*
s
;
size_t
size_
;
size_t
size_
;
size_t
size
()
const
{
return
size_
;
}
size_t
size
()
const
{
return
size_
;
}
size_t
width
()
const
{
size_t
width
()
const
{
return
internal
::
count_code_points
(
basic_string_view
<
Char
>
(
s
,
size_
));
return
count_code_points
(
basic_string_view
<
Char
>
(
s
,
size_
));
}
}
template
<
typename
It
>
void
operator
()(
It
&&
it
)
const
{
template
<
typename
It
>
void
operator
()(
It
&&
it
)
const
{
it
=
internal
::
copy_str
<
char_type
>
(
s
,
s
+
size_
,
it
);
it
=
copy_str
<
char_type
>
(
s
,
s
+
size_
,
it
);
}
}
};
};
...
@@ -1677,13 +1647,12 @@ template <typename Range> class basic_writer {
...
@@ -1677,13 +1647,12 @@ template <typename Range> class basic_writer {
template
<
typename
It
>
void
operator
()(
It
&&
it
)
const
{
template
<
typename
It
>
void
operator
()(
It
&&
it
)
const
{
*
it
++
=
static_cast
<
char_type
>
(
'0'
);
*
it
++
=
static_cast
<
char_type
>
(
'0'
);
*
it
++
=
static_cast
<
char_type
>
(
'x'
);
*
it
++
=
static_cast
<
char_type
>
(
'x'
);
it
=
internal
::
format_uint
<
4
,
char_type
>
(
it
,
value
,
num_digits
);
it
=
format_uint
<
4
,
char_type
>
(
it
,
value
,
num_digits
);
}
}
};
};
public:
public:
explicit
basic_writer
(
Range
out
,
explicit
basic_writer
(
Range
out
,
locale_ref
loc
=
locale_ref
())
internal
::
locale_ref
loc
=
internal
::
locale_ref
())
:
out_
(
out
.
begin
()),
locale_
(
loc
)
{}
:
out_
(
out
.
begin
()),
locale_
(
loc
)
{}
iterator
out
()
const
{
return
out_
;
}
iterator
out
()
const
{
return
out_
;
}
...
@@ -1727,15 +1696,13 @@ template <typename Range> class basic_writer {
...
@@ -1727,15 +1696,13 @@ template <typename Range> class basic_writer {
void
write
(
uint128_t
value
)
{
write_decimal
(
value
);
}
void
write
(
uint128_t
value
)
{
write_decimal
(
value
);
}
#endif
#endif
// Writes a formatted integer.
template
<
typename
T
,
typename
Spec
>
template
<
typename
T
,
typename
Spec
>
void
write_int
(
T
value
,
const
Spec
&
spec
)
{
void
write_int
(
T
value
,
const
Spec
&
spec
)
{
internal
::
handle_int_type_spec
(
spec
.
type
,
handle_int_type_spec
(
spec
.
type
,
int_writer
<
T
,
Spec
>
(
*
this
,
value
,
spec
));
int_writer
<
T
,
Spec
>
(
*
this
,
value
,
spec
));
}
}
template
<
typename
T
,
FMT_ENABLE_IF
(
std
::
is_floating_point
<
T
>
::
value
)
>
template
<
typename
T
,
FMT_ENABLE_IF
(
std
::
is_floating_point
<
T
>
::
value
)
>
void
write
(
T
value
,
const
format_specs
&
specs
=
{})
{
void
write
(
T
value
,
format_specs
specs
=
{})
{
auto
sign
=
specs
.
sign
;
auto
sign
=
specs
.
sign
;
if
(
std
::
signbit
(
value
))
{
// value < 0 is false for NaN so use signbit.
if
(
std
::
signbit
(
value
))
{
// value < 0 is false for NaN so use signbit.
sign
=
sign
::
minus
;
sign
=
sign
::
minus
;
...
@@ -1751,61 +1718,61 @@ template <typename Range> class basic_writer {
...
@@ -1751,61 +1718,61 @@ template <typename Range> class basic_writer {
return
write_padded
(
specs
,
inf_or_nan_writer
<
char_type
>
{
sign
,
str
});
return
write_padded
(
specs
,
inf_or_nan_writer
<
char_type
>
{
sign
,
str
});
}
}
if
(
specs
.
align
==
align
::
numeric
)
{
if
(
sign
)
{
auto
&&
it
=
reserve
(
1
);
*
it
++
=
static_cast
<
char_type
>
(
data
::
signs
[
sign
]);
sign
=
sign
::
none
;
if
(
specs
.
width
!=
0
)
--
specs
.
width
;
}
specs
.
align
=
align
::
right
;
}
else
if
(
specs
.
align
==
align
::
none
)
{
specs
.
align
=
align
::
right
;
}
memory_buffer
buffer
;
memory_buffer
buffer
;
int
exp
=
0
;
if
(
fspec
.
format
==
float_format
::
hex
)
{
if
(
sign
)
buffer
.
push_back
(
data
::
signs
[
sign
]);
fspec
.
alt
=
specs
.
alt
;
sprintf_format
(
value
,
specs
.
precision
,
fspec
,
buffer
);
write_padded
(
specs
,
str_writer
<
char
>
{
buffer
.
data
(),
buffer
.
size
()});
return
;
}
int
precision
=
specs
.
precision
>=
0
||
!
specs
.
type
?
specs
.
precision
:
6
;
int
precision
=
specs
.
precision
>=
0
||
!
specs
.
type
?
specs
.
precision
:
6
;
int
num_digits
=
int
num_digits
=
fspec
.
format
==
float_format
::
exp
?
precision
+
1
:
precision
;
fspec
.
format
==
float_format
::
exp
?
precision
+
1
:
precision
;
unsigned
options
=
0
;
unsigned
options
=
0
;
if
(
fspec
.
format
==
float_format
::
fixed
)
options
|=
grisu_options
::
fixed
;
if
(
fspec
.
format
==
float_format
::
fixed
)
options
|=
grisu_options
::
fixed
;
if
(
const_check
(
s
izeof
(
value
)
==
sizeof
(
float
)))
if
(
const_check
(
s
td
::
is_same
<
T
,
float
>
(
)))
options
|=
grisu_options
::
binary32
;
options
|=
grisu_options
::
binary32
;
if
(
const_check
(
FMT_DEPRECATED_PERCENT
)
&&
fspec
.
percent
)
value
*=
100
;
if
(
const_check
(
FMT_DEPRECATED_PERCENT
)
&&
fspec
.
percent
)
value
*=
100
;
int
exp
=
0
;
bool
use_grisu
=
internal
::
use_grisu
<
T
>
()
&&
bool
use_grisu
=
internal
::
use_grisu
<
T
>
()
&&
(
specs
.
type
!=
'a'
&&
specs
.
type
!=
'A'
)
&&
grisu_format
(
static_cast
<
double
>
(
value
),
buffer
,
grisu_format
(
static_cast
<
double
>
(
value
),
buffer
,
num_digits
,
options
,
exp
);
num_digits
,
options
,
exp
);
char
*
decimal_point_pos
=
nullptr
;
if
(
!
use_grisu
)
exp
=
sprintf_format
(
value
,
precision
,
fspec
,
buffer
);
if
(
!
use_grisu
)
decimal_point_pos
=
sprintf_format
(
value
,
buffer
,
specs
);
if
(
const_check
(
FMT_DEPRECATED_PERCENT
)
&&
fspec
.
percent
)
{
if
(
const_check
(
FMT_DEPRECATED_PERCENT
)
&&
fspec
.
percent
)
{
buffer
.
push_back
(
'%'
);
buffer
.
push_back
(
'%'
);
--
exp
;
// Adjust decimal place position.
--
exp
;
// Adjust decimal place position.
}
}
format_specs
as
=
specs
;
auto
params
=
gen_digits_params
();
if
(
specs
.
align
==
align
::
numeric
)
{
params
.
sign
=
sign
;
if
(
sign
)
{
params
.
format
=
fspec
.
format
;
auto
&&
it
=
reserve
(
1
);
params
.
num_digits
=
num_digits
;
*
it
++
=
static_cast
<
char_type
>
(
data
::
signs
[
sign
]);
params
.
trailing_zeros
=
sign
=
sign
::
none
;
(
precision
!=
0
&&
if
(
as
.
width
)
--
as
.
width
;
(
!
specs
.
type
||
fspec
.
format
==
float_format
::
fixed
||
}
fspec
.
format
==
float_format
::
exp
))
||
as
.
align
=
align
::
right
;
specs
.
alt
;
}
else
if
(
specs
.
align
==
align
::
none
)
{
params
.
upper
=
fspec
.
upper
;
as
.
align
=
align
::
right
;
num_digits
=
static_cast
<
int
>
(
buffer
.
size
());
}
char_type
point
=
fspec
.
locale
?
decimal_point
<
char_type
>
(
locale_
)
char_type
point
=
fspec
.
locale
?
decimal_point
<
char_type
>
(
locale_
)
:
static_cast
<
char_type
>
(
'.'
);
:
static_cast
<
char_type
>
(
'.'
);
if
(
use_grisu
)
{
write_padded
(
specs
,
float_writer
<
char_type
>
(
buffer
.
data
(),
num_digits
,
exp
,
auto
params
=
gen_digits_params
();
params
,
point
));
params
.
sign
=
sign
;
params
.
format
=
fspec
.
format
;
params
.
num_digits
=
num_digits
;
params
.
trailing_zeros
=
(
precision
!=
0
&&
(
!
specs
.
type
||
fspec
.
format
==
float_format
::
fixed
||
fspec
.
format
==
float_format
::
exp
))
||
specs
.
alt
;
params
.
upper
=
fspec
.
upper
;
num_digits
=
static_cast
<
int
>
(
buffer
.
size
());
write_padded
(
as
,
grisu_writer
<
char_type
>
(
buffer
.
data
(),
num_digits
,
exp
,
params
,
point
));
}
else
{
write_padded
(
as
,
double_writer
{
sign
,
buffer
,
decimal_point_pos
,
point
});
}
}
}
// Writes a character to the buffer.
void
write
(
char
value
)
{
void
write
(
char
value
)
{
auto
&&
it
=
reserve
(
1
);
auto
&&
it
=
reserve
(
1
);
*
it
++
=
value
;
*
it
++
=
value
;
...
@@ -1817,10 +1784,9 @@ template <typename Range> class basic_writer {
...
@@ -1817,10 +1784,9 @@ template <typename Range> class basic_writer {
*
it
++
=
value
;
*
it
++
=
value
;
}
}
// Writes value to the buffer.
void
write
(
string_view
value
)
{
void
write
(
string_view
value
)
{
auto
&&
it
=
reserve
(
value
.
size
());
auto
&&
it
=
reserve
(
value
.
size
());
it
=
internal
::
copy_str
<
char_type
>
(
value
.
begin
(),
value
.
end
(),
it
);
it
=
copy_str
<
char_type
>
(
value
.
begin
(),
value
.
end
(),
it
);
}
}
void
write
(
wstring_view
value
)
{
void
write
(
wstring_view
value
)
{
static_assert
(
std
::
is_same
<
char_type
,
wchar_t
>::
value
,
""
);
static_assert
(
std
::
is_same
<
char_type
,
wchar_t
>::
value
,
""
);
...
@@ -1828,26 +1794,23 @@ template <typename Range> class basic_writer {
...
@@ -1828,26 +1794,23 @@ template <typename Range> class basic_writer {
it
=
std
::
copy
(
value
.
begin
(),
value
.
end
(),
it
);
it
=
std
::
copy
(
value
.
begin
(),
value
.
end
(),
it
);
}
}
// Writes a formatted string.
template
<
typename
Char
>
template
<
typename
Char
>
void
write
(
const
Char
*
s
,
std
::
size_t
size
,
const
format_specs
&
specs
)
{
void
write
(
const
Char
*
s
,
std
::
size_t
size
,
const
format_specs
&
specs
)
{
write_padded
(
specs
,
str_writer
<
Char
>
{
s
,
size
});
write_padded
(
specs
,
str_writer
<
Char
>
{
s
,
size
});
}
}
template
<
typename
Char
>
template
<
typename
Char
>
void
write
(
basic_string_view
<
Char
>
s
,
void
write
(
basic_string_view
<
Char
>
s
,
const
format_specs
&
specs
=
{})
{
const
format_specs
&
specs
=
format_specs
())
{
const
Char
*
data
=
s
.
data
();
const
Char
*
data
=
s
.
data
();
std
::
size_t
size
=
s
.
size
();
std
::
size_t
size
=
s
.
size
();
if
(
specs
.
precision
>=
0
&&
internal
::
to_unsigned
(
specs
.
precision
)
<
size
)
if
(
specs
.
precision
>=
0
&&
to_unsigned
(
specs
.
precision
)
<
size
)
size
=
size
=
code_point_index
(
s
,
to_unsigned
(
specs
.
precision
));
internal
::
code_point_index
(
s
,
internal
::
to_unsigned
(
specs
.
precision
));
write
(
data
,
size
,
specs
);
write
(
data
,
size
,
specs
);
}
}
template
<
typename
UIntPtr
>
template
<
typename
UIntPtr
>
void
write_pointer
(
UIntPtr
value
,
const
format_specs
*
specs
)
{
void
write_pointer
(
UIntPtr
value
,
const
format_specs
*
specs
)
{
int
num_digits
=
internal
::
count_digits
<
4
>
(
value
);
int
num_digits
=
count_digits
<
4
>
(
value
);
auto
pw
=
pointer_writer
<
UIntPtr
>
{
value
,
num_digits
};
auto
pw
=
pointer_writer
<
UIntPtr
>
{
value
,
num_digits
};
if
(
!
specs
)
return
pw
(
reserve
(
to_unsigned
(
num_digits
)
+
2
));
if
(
!
specs
)
return
pw
(
reserve
(
to_unsigned
(
num_digits
)
+
2
));
format_specs
specs_copy
=
*
specs
;
format_specs
specs_copy
=
*
specs
;
...
@@ -3477,8 +3440,7 @@ inline std::basic_string<Char> internal::vformat(
...
@@ -3477,8 +3440,7 @@ inline std::basic_string<Char> internal::vformat(
*/
*/
template
<
typename
...
Args
>
template
<
typename
...
Args
>
inline
std
::
size_t
formatted_size
(
string_view
format_str
,
const
Args
&
...
args
)
{
inline
std
::
size_t
formatted_size
(
string_view
format_str
,
const
Args
&
...
args
)
{
auto
it
=
format_to
(
internal
::
counting_iterator
(),
format_str
,
args
...);
return
format_to
(
internal
::
counting_iterator
(),
format_str
,
args
...).
count
();
return
it
.
count
();
}
}
#if FMT_USE_USER_DEFINED_LITERALS
#if FMT_USE_USER_DEFINED_LITERALS
...
...
src/format.cc
View file @
ded1e767
...
@@ -36,11 +36,11 @@ template FMT_API std::string internal::vformat<char>(
...
@@ -36,11 +36,11 @@ template FMT_API std::string internal::vformat<char>(
template
FMT_API
format_context
::
iterator
internal
::
vformat_to
(
template
FMT_API
format_context
::
iterator
internal
::
vformat_to
(
internal
::
buffer
<
char
>
&
,
string_view
,
basic_format_args
<
format_context
>);
internal
::
buffer
<
char
>
&
,
string_view
,
basic_format_args
<
format_context
>);
template
FMT_API
char
*
internal
::
sprintf_format
(
double
,
internal
::
buffer
<
char
>
&
,
template
FMT_API
int
internal
::
sprintf_format
(
double
,
int
,
internal
::
float_spec
,
sprintf_specs
);
internal
::
buffer
<
char
>
&
);
template
FMT_API
char
*
internal
::
sprintf_format
(
long
double
,
template
FMT_API
int
internal
::
sprintf_format
(
long
double
,
int
,
internal
::
buffer
<
char
>
&
,
internal
::
float_spec
,
sprintf_specs
);
internal
::
buffer
<
char
>
&
);
// Explicit instantiations for wchar_t.
// Explicit instantiations for wchar_t.
...
...
support/Vagrantfile
View file @
ded1e767
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
# A vagrant config for testing against gcc-4.8.
# A vagrant config for testing against gcc-4.8.
Vagrant
.
configure
(
"2"
)
do
|
config
|
Vagrant
.
configure
(
"2"
)
do
|
config
|
config
.
vm
.
box
=
"ubuntu/
trusty
64"
config
.
vm
.
box
=
"ubuntu/
xenial
64"
config
.
vm
.
provider
"virtualbox"
do
|
vb
|
config
.
vm
.
provider
"virtualbox"
do
|
vb
|
vb
.
memory
=
"4096"
vb
.
memory
=
"4096"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment