Skip to content

Commit e3fe70d

Browse files
authored
Merge pull request #208 from cppalliance/206
Add signed and floating point literals
2 parents 495c76c + de52a9b commit e3fe70d

11 files changed

Lines changed: 525 additions & 43 deletions

File tree

doc/modules/ROOT/pages/literals.adoc

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ https://www.boost.org/LICENSE_1_0.txt
1010

1111
== Description
1212

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

1717
- 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.
1818
- The `_u64` literal performs no range check since `unsigned long long` maps directly to `std::uint64_t` (statically verified).
19-
- The `_u128` literal parses a string representation and throws `std::overflow_error` on overflow or `std::invalid_argument` on invalid input.
19+
- 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.
20+
- 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.
21+
Negative values are formed by applying the wrapper's unary `operator-` to the positive literal: the expression `-42_i32` parses as `-(42_i32)`.
22+
- The `_i128` literal mirrors `_u128`: it parses an integer-form digit string into `int128_t` and throws on overflow or invalid input.
23+
- 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.
2024

2125
[source,c++]
2226
----
@@ -30,6 +34,15 @@ constexpr auto operator ""_u32(unsigned long long int val) -> u32;
3034
constexpr auto operator ""_u64(unsigned long long int val) noexcept -> u64;
3135
constexpr auto operator ""_u128(const char* str) -> u128;
3236
37+
constexpr auto operator ""_i8(unsigned long long int val) -> i8;
38+
constexpr auto operator ""_i16(unsigned long long int val) -> i16;
39+
constexpr auto operator ""_i32(unsigned long long int val) -> i32;
40+
constexpr auto operator ""_i64(unsigned long long int val) -> i64;
41+
constexpr auto operator ""_i128(const char* str) -> i128;
42+
43+
constexpr auto operator ""_f32(long double val) -> f32;
44+
constexpr auto operator ""_f64(long double val) -> f64;
45+
3346
} // namespace boost::safe_numbers::literals
3447
----
3548

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

5770
| `_u128`
5871
| `u128`
59-
| Parses string; throws `std::overflow_error` on overflow, `std::invalid_argument` on invalid input
72+
| Parses digit string; throws `std::overflow_error` on overflow, `std::invalid_argument` on invalid input
73+
74+
| `_i8`
75+
| `i8`
76+
| Throws `std::overflow_error` if magnitude > 127
77+
78+
| `_i16`
79+
| `i16`
80+
| Throws `std::overflow_error` if magnitude > 32,767
81+
82+
| `_i32`
83+
| `i32`
84+
| Throws `std::overflow_error` if magnitude > 2,147,483,647
85+
86+
| `_i64`
87+
| `i64`
88+
| Throws `std::overflow_error` if magnitude > 9,223,372,036,854,775,807
89+
90+
| `_i128`
91+
| `i128`
92+
| Parses digit string; throws `std::overflow_error` on overflow, `std::invalid_argument` on invalid input
93+
94+
| `_f32`
95+
| `f32`
96+
| Throws `std::overflow_error` if value > `std::numeric_limits<float>::max()`
97+
98+
| `_f64`
99+
| `f64`
100+
| Throws `std::overflow_error` if value > `std::numeric_limits<double>::max()`
60101
|===
61102

62103
== Usage
@@ -72,14 +113,37 @@ constexpr auto b {1000_u16};
72113
constexpr auto c {100000_u32};
73114
constexpr auto d {9999999999_u64};
74115
constexpr auto e {340282366920938463463374607431768211455_u128};
116+
117+
constexpr auto f {-42_i8};
118+
constexpr auto g {32767_i16};
119+
constexpr auto h {-2147483647_i32};
120+
constexpr auto i {9223372036854775807_i64};
121+
constexpr auto j {-170141183460469231731687303715884105727_i128};
122+
123+
constexpr auto k {3.14_f32};
124+
constexpr auto l {2.718281828459045_f64};
75125
----
76126

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

130+
== Negative Signed Literals
131+
132+
C++ user-defined literal operators never see a leading minus sign as part of their input.
133+
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.
134+
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`.
135+
Use direct construction for that one value:
136+
137+
[source,c++]
138+
----
139+
constexpr auto min_i8 {i8{std::numeric_limits<std::int8_t>::min()}};
140+
----
141+
142+
The same asymmetry applies to `_i16`, `_i32`, `_i64`, and `_i128`.
143+
80144
== Examples
81145

82-
.This https://github.com/boostorg/safe_numbers/blob/develop/examples/literals.cpp[example] demonstrates how to use user-defined literals with safe integer types.
146+
.This https://github.com/boostorg/safe_numbers/blob/develop/examples/literals.cpp[example] demonstrates how to use user-defined literals with safe numeric types.
83147
====
84148
[source, c++]
85149
----
@@ -93,6 +157,13 @@ Output:
93157
100000_u32 = 100000
94158
9999999999_u64 = 9999999999
95159
max_u128 = 340282366920938463463374607431768211455
160+
-42_i8 = -42
161+
max_i16 = 32767
162+
min+1_i32 = -2147483647
163+
max_i64 = 9223372036854775807
164+
min+1_i128 = -170141183460469231731687303715884105727
165+
pi_f32 = 3.14
166+
e_f64 = 2.71828
96167
100_u32 + 50_u32 = 150
97168
6_u32 * 7_u32 = 42
98169
constexpr 255_u8 = 255

examples/literals.cpp

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
// https://www.boost.org/LICENSE_1_0.txt
44

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

99
#include <boost/safe_numbers/unsigned_integers.hpp>
10+
#include <boost/safe_numbers/signed_integers.hpp>
11+
#include <boost/safe_numbers/floats.hpp>
1012
#include <boost/safe_numbers/literals.hpp>
1113
#include <boost/safe_numbers/iostream.hpp>
1214
#include <iostream>
@@ -16,7 +18,7 @@ int main()
1618
using namespace boost::safe_numbers;
1719
using namespace boost::safe_numbers::literals;
1820

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

36+
// Signed integer literals. Negative values are formed by applying unary
37+
// minus to a positive literal: -42_i32 parses as -(42_i32).
38+
{
39+
constexpr auto a {-42_i8};
40+
constexpr auto b {32767_i16};
41+
constexpr auto c {-2147483647_i32};
42+
constexpr auto d {9223372036854775807_i64};
43+
constexpr auto e {-170141183460469231731687303715884105727_i128};
44+
45+
std::cout << "-42_i8 = " << a << std::endl;
46+
std::cout << "max_i16 = " << b << std::endl;
47+
std::cout << "min+1_i32 = " << c << std::endl;
48+
std::cout << "max_i64 = " << d << std::endl;
49+
std::cout << "min+1_i128 = " << e << std::endl;
50+
}
51+
52+
// Floating-point literals. The literal accepts a long double and is
53+
// range-checked against the target type's maximum.
54+
{
55+
constexpr auto pi {3.14_f32};
56+
constexpr auto e {2.718281828459045_f64};
57+
58+
std::cout << "pi_f32 = " << pi << std::endl;
59+
std::cout << "e_f64 = " << e << std::endl;
60+
}
61+
3462
// Literals work naturally in expressions
3563
{
3664
const auto sum {100_u32 + 50_u32};
@@ -56,6 +84,8 @@ int main()
5684
//
5785
// auto bad = 256_u8; // throws std::overflow_error (> UINT8_MAX)
5886
// auto bad = 70000_u16; // throws std::overflow_error (> UINT16_MAX)
87+
// auto bad = 128_i8; // throws std::overflow_error (> INT8_MAX)
88+
// auto bad = 1.0e40_f32; // throws std::overflow_error (> FLT_MAX)
5989

6090
return 0;
6191
}

include/boost/safe_numbers/detail/int128/detail/common_div.hpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_HOST_DEVICE BOOST_SAFE_NUMBERS_DETAIL_INT128_FO
447447
return;
448448
}
449449

450-
#else
450+
#endif
451451

452452
if (rhs <= UINT32_MAX)
453453
{
@@ -467,8 +467,6 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_HOST_DEVICE BOOST_SAFE_NUMBERS_DETAIL_INT128_FO
467467
quotient = impl::from_words<T>(q);
468468
remainder = impl::from_words<T>(u);
469469
}
470-
471-
#endif
472470
}
473471

474472
template <typename T>

include/boost/safe_numbers/detail/int128/detail/mini_from_chars.hpp

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_HOST_DEVICE constexpr int from_chars_integer_im
127127

128128

129129
overflow_value /= unsigned_base;
130-
overflow_value <<= 1;
131130
max_digit %= unsigned_base;
132131

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

139138
bool overflowed = false;
140139

141-
std::ptrdiff_t nc = last - next;
142-
constexpr std::ptrdiff_t nd = std::numeric_limits<Integer>::digits10;
140+
const std::ptrdiff_t nc = last - next;
143141

142+
// For bases 2..10 the first digits10 characters always fit in the unsigned
143+
// target type without overflow, so the per-iteration check is unnecessary
144+
// there: 10^digits10 <= 2^bits, and any base <= 10 packs no more value per
145+
// digit than base 10. For bases above 10 the safe window is shorter than
146+
// digits10 (e.g. only ~24 digits in base 36 fit in 128 bits), so the check
147+
// must run on every iteration.
148+
const std::ptrdiff_t nd {
149+
base <= 10
150+
? static_cast<std::ptrdiff_t>(std::numeric_limits<Integer>::digits10)
151+
: std::ptrdiff_t{0}
152+
};
153+
154+
const std::ptrdiff_t fast_limit {nd < nc ? nd : nc};
155+
std::ptrdiff_t i = 0;
156+
157+
for (; i < fast_limit; ++i)
144158
{
145-
std::ptrdiff_t i = 0;
159+
const auto current_digit = static_cast<Unsigned_Integer>(digit_from_char(*next));
146160

147-
for( ; i < nd && i < nc; ++i )
161+
if (current_digit >= unsigned_base)
148162
{
149-
// overflow is not possible in the first nd characters
163+
break;
164+
}
150165

151-
const auto current_digit = static_cast<Unsigned_Integer>(digit_from_char(*next));
166+
result = static_cast<Unsigned_Integer>(result * unsigned_base + current_digit);
167+
++next;
168+
}
152169

153-
if (current_digit >= unsigned_base)
154-
{
155-
break;
156-
}
170+
for (; i < nc; ++i)
171+
{
172+
const auto current_digit = static_cast<Unsigned_Integer>(digit_from_char(*next));
157173

158-
result = static_cast<Unsigned_Integer>(result * unsigned_base + current_digit);
159-
++next;
174+
if (current_digit >= unsigned_base)
175+
{
176+
break;
160177
}
161178

162-
for( ; i < nc; ++i )
179+
if (result < overflow_value || (result == overflow_value && current_digit <= max_digit))
163180
{
164-
const auto current_digit = static_cast<Unsigned_Integer>(digit_from_char(*next));
165-
166-
if (current_digit >= unsigned_base)
167-
{
168-
break;
169-
}
170-
171-
if (result < overflow_value || (result == overflow_value && current_digit <= max_digit))
172-
{
173-
result = static_cast<Unsigned_Integer>(result * unsigned_base + current_digit);
174-
}
175-
else
176-
{
177-
overflowed = true;
178-
break;
179-
}
180-
181-
++next;
181+
result = static_cast<Unsigned_Integer>(result * unsigned_base + current_digit);
182182
}
183+
else
184+
{
185+
overflowed = true;
186+
break;
187+
}
188+
189+
++next;
183190
}
184191

185192
// Return the parsed value, adding the sign back if applicable

0 commit comments

Comments
 (0)