Numbers
JSON numbers are represented using std::int64_t
, std::uint64_t
, and
double
. When a value
is constructed from an unsigned integer, its
kind
will be kind::uint64
. Likewise, a value
constructed
from a signed integer will have kind::int64
, or kind::double_
if
constructed from a floating-point type:
// construction from int
value jv1 = 1;
assert( jv1.is_int64() );
// construction from unsigned int
value jv2 = 2u;
assert( jv2.is_uint64() );
// construction from double
value jv3 = 3.0;
assert( jv3.is_double() );
When accessing a number contained within a value
, the function used
must match the value’s kind
exactly; no conversions will be performed.
For example if as_double
is called on a value
that contains
a std::uint64_t
, an exception is thrown. Similarly, the function if_double
will return nullptr
and calling get_double
will result in undefined
behavior:
value jv = 1;
assert( jv.is_int64() );
// jv.kind() != kind::uint64; throws
std::uint64_t r1 = jv.as_uint64();
// jv.kind() != kind::uint64; the behavior is undefined
std::uint64_t r2 = jv.get_uint64();
// if_double will always return nullptr, branch is not taken
if(double* d = jv.if_double())
assert( false );
In cases where you know that a value
contains a number but don’t know
its kind
, value::to_number
can be used to convert the
value
to an arithmetic type:
value jv = 1;
assert( jv.to_number< int >() == 1 );
If the value
does not contain a number, or if the conversion is to an
integer type T
and the number cannot be represented exactly by T
, the
conversion will fail. Otherwise, the result is the number converted to T
as-if by static_cast
:
value jv1 = 404;
assert( jv1.is_int64() );
// ok, identity conversion
std::int64_t r1 = jv1.to_number< std::int64_t >( );
// loss of data, throws system_error
char r2 = jv1.to_number< char >();
// ok, no loss of data
double r3 = jv1.to_number< double >();
value jv2 = 1.23;
assert( jv1.is_double() );
// ok, same as static_cast<float>( jv2.get_double() )
float r4 = jv2.to_number< float >();
// not exact, throws system_error
int r5 = jv2.to_number< int >();
value jv3 = {1, 2, 3};
assert( ! jv3.is_number() );
// not a number, throws system_error
int r6 = jv3.to_number< int >();
In settings where exceptions cannot be used, an overload of value::to_number
accepting error_code
can be used instead with identical semantics to its
throwing counterpart:
value jv = 10.5;
boost::system::error_code ec;
// ok, conversion is exact
float r1 = jv.to_number< float >( ec );
assert( ! ec );
// error, conversion is non-exact
int r2 = jv.to_number< int >( ec );
assert( ec == error::not_exact );
When parsing a JSON document, the type used to represent a number is not
explicitly specified and must be determined from its value. In general, the
parser will choose the best type which can accurately store the number as it
appears in the document. Integers (i.e. numbers without decimals or exponents)
that cannot be represented by std::uint64_t
and std::int64_t
will be
represented as double
to preserve their magnitude:
value jv = parse("[-42, 100, 10.25, -299999999999999999998, 2e32]");
array ja = jv.as_array();
// represented by std::int64_t
assert( ja[0].is_int64() );
// represented by std::int64_t
assert( ja[1].is_int64() );
// contains decimal point, represented as double
assert( ja[2].is_double() );
// less than INT64_MIN, represented as double
assert( ja[3].is_double() );
// contains exponent, represented as double
assert( ja[4].is_double() );
More formally, if the number:
-
contains a decimal point, or
-
contains an exponent, or
-
is negative and its value is less than
INT64_MIN
, or -
is positive and its value is greater than
UINT64_MAX
,
then its type is double
. Otherwise, if the number is positive and its value
is greater than INT64_MAX
, then its type is std::uint64_t
. All other
numbers are parsed as std::int64_t
.