Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 76 additions & 5 deletions doc/modules/ROOT/pages/literals.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ https://www.boost.org/LICENSE_1_0.txt

== Description

The library provides user-defined literal suffixes for concise construction of safe integer types.
The library provides user-defined literal suffixes for concise construction of safe numeric types.
The literals are defined in the `boost::safe_numbers::literals` namespace.
The behavior of the literals depends on the type:
The behavior of each literal depends on the target type:

- For `_u8`, `_u16`, and `_u32`, the literal value is range-checked and throws `std::overflow_error` if the value exceeds the target type's maximum.
- The `_u64` literal performs no range check since `unsigned long long` maps directly to `std::uint64_t` (statically verified).
- The `_u128` literal parses a string representation and throws `std::overflow_error` on overflow or `std::invalid_argument` on invalid input.
- The `_u128` literal accepts an integer-form token, parses the digit string, and throws `std::overflow_error` on overflow or `std::invalid_argument` on invalid input.
- For `_i8`, `_i16`, `_i32`, and `_i64`, the literal magnitude is range-checked against the signed maximum and throws `std::overflow_error` if it exceeds it.
Negative values are formed by applying the wrapper's unary `operator-` to the positive literal: the expression `-42_i32` parses as `-(42_i32)`.
- The `_i128` literal mirrors `_u128`: it parses an integer-form digit string into `int128_t` and throws on overflow or invalid input.
- For `_f32` and `_f64`, the literal is taken as `long double` and range-checked against the target's maximum, throwing `std::overflow_error` on overflow.

[source,c++]
----
Expand All @@ -30,6 +34,15 @@ constexpr auto operator ""_u32(unsigned long long int val) -> u32;
constexpr auto operator ""_u64(unsigned long long int val) noexcept -> u64;
constexpr auto operator ""_u128(const char* str) -> u128;

constexpr auto operator ""_i8(unsigned long long int val) -> i8;
constexpr auto operator ""_i16(unsigned long long int val) -> i16;
constexpr auto operator ""_i32(unsigned long long int val) -> i32;
constexpr auto operator ""_i64(unsigned long long int val) -> i64;
constexpr auto operator ""_i128(const char* str) -> i128;

constexpr auto operator ""_f32(long double val) -> f32;
constexpr auto operator ""_f64(long double val) -> f64;

} // namespace boost::safe_numbers::literals
----

Expand All @@ -56,7 +69,35 @@ constexpr auto operator ""_u128(const char* str) -> u128;

| `_u128`
| `u128`
| Parses string; throws `std::overflow_error` on overflow, `std::invalid_argument` on invalid input
| Parses digit string; throws `std::overflow_error` on overflow, `std::invalid_argument` on invalid input

| `_i8`
| `i8`
| Throws `std::overflow_error` if magnitude > 127

| `_i16`
| `i16`
| Throws `std::overflow_error` if magnitude > 32,767

| `_i32`
| `i32`
| Throws `std::overflow_error` if magnitude > 2,147,483,647

| `_i64`
| `i64`
| Throws `std::overflow_error` if magnitude > 9,223,372,036,854,775,807

| `_i128`
| `i128`
| Parses digit string; throws `std::overflow_error` on overflow, `std::invalid_argument` on invalid input

| `_f32`
| `f32`
| Throws `std::overflow_error` if value > `std::numeric_limits<float>::max()`

| `_f64`
| `f64`
| Throws `std::overflow_error` if value > `std::numeric_limits<double>::max()`
|===

== Usage
Expand All @@ -72,14 +113,37 @@ constexpr auto b {1000_u16};
constexpr auto c {100000_u32};
constexpr auto d {9999999999_u64};
constexpr auto e {340282366920938463463374607431768211455_u128};

constexpr auto f {-42_i8};
constexpr auto g {32767_i16};
constexpr auto h {-2147483647_i32};
constexpr auto i {9223372036854775807_i64};
constexpr auto j {-170141183460469231731687303715884105727_i128};

constexpr auto k {3.14_f32};
constexpr auto l {2.718281828459045_f64};
----

Literals are `constexpr` and can be used in compile-time contexts.
When used in a `constexpr` context, an out-of-range value produces a compile error rather than a runtime exception.

== Negative Signed Literals

C++ user-defined literal operators never see a leading minus sign as part of their input.
The expression `-42_i32` is parsed as `-(42_i32)`: the literal returns a positive `i32` and unary `operator-` on the wrapper produces the negative result.
Because the literal's range check is against the positive maximum (`INT_MAX`), the asymmetric `INT_MIN` value is not directly expressible through the literal: writing `-128_i8` parses as `-(128_i8)`, and the inner literal throws because `128 > INT8_MAX`.
Use direct construction for that one value:

[source,c++]
----
constexpr auto min_i8 {i8{std::numeric_limits<std::int8_t>::min()}};
----

The same asymmetry applies to `_i16`, `_i32`, `_i64`, and `_i128`.

== Examples

.This https://github.com/boostorg/safe_numbers/blob/develop/examples/literals.cpp[example] demonstrates how to use user-defined literals with safe integer types.
.This https://github.com/boostorg/safe_numbers/blob/develop/examples/literals.cpp[example] demonstrates how to use user-defined literals with safe numeric types.
====
[source, c++]
----
Expand All @@ -93,6 +157,13 @@ Output:
100000_u32 = 100000
9999999999_u64 = 9999999999
max_u128 = 340282366920938463463374607431768211455
-42_i8 = -42
max_i16 = 32767
min+1_i32 = -2147483647
max_i64 = 9223372036854775807
min+1_i128 = -170141183460469231731687303715884105727
pi_f32 = 3.14
e_f64 = 2.71828
100_u32 + 50_u32 = 150
6_u32 * 7_u32 = 42
constexpr 255_u8 = 255
Expand Down
34 changes: 32 additions & 2 deletions examples/literals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
// https://www.boost.org/LICENSE_1_0.txt

// This example demonstrates the use of user-defined literals for
// constructing safe integer types. The literals provide a concise
// constructing safe numeric types. The literals provide a concise
// syntax and perform compile-time range checking when possible.

#include <boost/safe_numbers/unsigned_integers.hpp>
#include <boost/safe_numbers/signed_integers.hpp>
#include <boost/safe_numbers/floats.hpp>
#include <boost/safe_numbers/literals.hpp>
#include <boost/safe_numbers/iostream.hpp>
#include <iostream>
Expand All @@ -16,7 +18,7 @@ int main()
using namespace boost::safe_numbers;
using namespace boost::safe_numbers::literals;

// Construct safe integers using literal suffixes
// Construct safe unsigned integers using literal suffixes
{
constexpr auto a {42_u8};
constexpr auto b {1000_u16};
Expand All @@ -31,6 +33,32 @@ int main()
std::cout << "max_u128 = " << e << std::endl;
}

// Signed integer literals. Negative values are formed by applying unary
// minus to a positive literal: -42_i32 parses as -(42_i32).
{
constexpr auto a {-42_i8};
constexpr auto b {32767_i16};
constexpr auto c {-2147483647_i32};
constexpr auto d {9223372036854775807_i64};
constexpr auto e {-170141183460469231731687303715884105727_i128};

std::cout << "-42_i8 = " << a << std::endl;
std::cout << "max_i16 = " << b << std::endl;
std::cout << "min+1_i32 = " << c << std::endl;
std::cout << "max_i64 = " << d << std::endl;
std::cout << "min+1_i128 = " << e << std::endl;
}

// Floating-point literals. The literal accepts a long double and is
// range-checked against the target type's maximum.
{
constexpr auto pi {3.14_f32};
constexpr auto e {2.718281828459045_f64};

std::cout << "pi_f32 = " << pi << std::endl;
std::cout << "e_f64 = " << e << std::endl;
}

// Literals work naturally in expressions
{
const auto sum {100_u32 + 50_u32};
Expand All @@ -56,6 +84,8 @@ int main()
//
// auto bad = 256_u8; // throws std::overflow_error (> UINT8_MAX)
// auto bad = 70000_u16; // throws std::overflow_error (> UINT16_MAX)
// auto bad = 128_i8; // throws std::overflow_error (> INT8_MAX)
// auto bad = 1.0e40_f32; // throws std::overflow_error (> FLT_MAX)

return 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_HOST_DEVICE BOOST_SAFE_NUMBERS_DETAIL_INT128_FO
return;
}

#else
#endif

if (rhs <= UINT32_MAX)
{
Expand All @@ -467,8 +467,6 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_HOST_DEVICE BOOST_SAFE_NUMBERS_DETAIL_INT128_FO
quotient = impl::from_words<T>(q);
remainder = impl::from_words<T>(u);
}

#endif
}

template <typename T>
Expand Down
71 changes: 39 additions & 32 deletions include/boost/safe_numbers/detail/int128/detail/mini_from_chars.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_HOST_DEVICE constexpr int from_chars_integer_im


overflow_value /= unsigned_base;
overflow_value <<= 1;
max_digit %= unsigned_base;

// If the only character was a sign abort now
Expand All @@ -138,48 +137,56 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_HOST_DEVICE constexpr int from_chars_integer_im

bool overflowed = false;

std::ptrdiff_t nc = last - next;
constexpr std::ptrdiff_t nd = std::numeric_limits<Integer>::digits10;
const std::ptrdiff_t nc = last - next;

// For bases 2..10 the first digits10 characters always fit in the unsigned
// target type without overflow, so the per-iteration check is unnecessary
// there: 10^digits10 <= 2^bits, and any base <= 10 packs no more value per
// digit than base 10. For bases above 10 the safe window is shorter than
// digits10 (e.g. only ~24 digits in base 36 fit in 128 bits), so the check
// must run on every iteration.
const std::ptrdiff_t nd {
base <= 10
? static_cast<std::ptrdiff_t>(std::numeric_limits<Integer>::digits10)
: std::ptrdiff_t{0}
};

const std::ptrdiff_t fast_limit {nd < nc ? nd : nc};
std::ptrdiff_t i = 0;

for (; i < fast_limit; ++i)
{
std::ptrdiff_t i = 0;
const auto current_digit = static_cast<Unsigned_Integer>(digit_from_char(*next));

for( ; i < nd && i < nc; ++i )
if (current_digit >= unsigned_base)
{
// overflow is not possible in the first nd characters
break;
}

const auto current_digit = static_cast<Unsigned_Integer>(digit_from_char(*next));
result = static_cast<Unsigned_Integer>(result * unsigned_base + current_digit);
++next;
}

if (current_digit >= unsigned_base)
{
break;
}
for (; i < nc; ++i)
{
const auto current_digit = static_cast<Unsigned_Integer>(digit_from_char(*next));

result = static_cast<Unsigned_Integer>(result * unsigned_base + current_digit);
++next;
if (current_digit >= unsigned_base)
{
break;
}

for( ; i < nc; ++i )
if (result < overflow_value || (result == overflow_value && current_digit <= max_digit))
{
const auto current_digit = static_cast<Unsigned_Integer>(digit_from_char(*next));

if (current_digit >= unsigned_base)
{
break;
}

if (result < overflow_value || (result == overflow_value && current_digit <= max_digit))
{
result = static_cast<Unsigned_Integer>(result * unsigned_base + current_digit);
}
else
{
overflowed = true;
break;
}

++next;
result = static_cast<Unsigned_Integer>(result * unsigned_base + current_digit);
}
else
{
overflowed = true;
break;
}

++next;
}

// Return the parsed value, adding the sign back if applicable
Expand Down
Loading
Loading