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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,18 @@ class i128;
class f32;
class f64;

// Every width is also available with a saturating or strict error policy
// in place of the default throwing one, e.g.:

class sat_u8; // clamps to the numeric limits instead of throwing
class strict_i32; // terminates on any error
class sat_f64; // raw IEEE 754 arithmetic with no checks

} // namespace boost::safe_numbers
```

These types operate much like the built-in numeric types but with far stricter behavior to enforce correctness.
This includes no implicit conversions, no mixed type operations, and throwing an exception on underflow, overflow, or other incorrect operation.
This includes no implicit conversions, no mixed type operations, and (by default) throwing an exception on underflow, overflow, or other incorrect operation.
A fully featured implementation analogous to the STL is included with `<cmath>`, `<charconv>`, `<format>`, etc. support.

Using these types is straightforward and can be learned by [example](https://develop.safe-numbers.cpp.al/examples.html).
Expand Down
2 changes: 2 additions & 0 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
** xref:examples.adoc#examples_checked[Checked Arithmetic]
** xref:examples.adoc#examples_strict[Strict Arithmetic]
** xref:examples.adoc#examples_generic[Generic Policy-Parameterized Arithmetic]
** xref:examples.adoc#examples_policy_types[Type-Level Policies]
** xref:examples.adoc#examples_user_defined_handler[User Defined Error Handlers]
** xref:examples.adoc#examples_safety_profile[Integer Safety Profile]
** xref:examples.adoc#examples_literals[Literals]
** xref:examples.adoc#examples_charconv[Character Conversion]
Expand Down
29 changes: 28 additions & 1 deletion doc/modules/ROOT/pages/api_reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ https://www.boost.org/LICENSE_1_0.txt

| xref:unsigned_integers.adoc[`u128`]
| Safe unsigned 128-bit integer

| xref:policies.adoc#policies_type_level[`sat_u8` ... `sat_u128`]
| Saturating counterparts of `u8` ... `u128` (clamp instead of throwing)

| xref:policies.adoc#policies_type_level[`strict_u8` ... `strict_u128`]
| Strict counterparts of `u8` ... `u128` (terminate on error; host only)

| xref:policies.adoc#policies_type_level[`basic_u8<H>` ... `basic_u128<H>`]
| Alias templates selecting the policy by type: a tag (`throwing`, `saturating`, `strict`) or a user defined handler
|===

=== Signed Integer Types
Expand All @@ -63,6 +72,15 @@ https://www.boost.org/LICENSE_1_0.txt

| xref:signed_integers.adoc[`i128`]
| Safe signed 128-bit integer

| xref:policies.adoc#policies_type_level[`sat_i8` ... `sat_i128`]
| Saturating counterparts of `i8` ... `i128` (clamp instead of throwing)

| xref:policies.adoc#policies_type_level[`strict_i8` ... `strict_i128`]
| Strict counterparts of `i8` ... `i128` (terminate on error; host only)

| xref:policies.adoc#policies_type_level[`basic_i8<H>` ... `basic_i128<H>`]
| Alias templates selecting the policy by type: a tag (`throwing`, `saturating`, `strict`) or a user defined handler
|===

=== Floating-Point Types
Expand All @@ -76,6 +94,12 @@ https://www.boost.org/LICENSE_1_0.txt

| xref:floats.adoc[`f64`]
| Safe double-precision (binary64) floating-point type

| xref:policies.adoc#policies_type_level[`sat_f32`, `sat_f64`]
| Raw IEEE 754 counterparts of `f32`, `f64` (no checks; overflow saturates to infinity)

| xref:policies.adoc#policies_type_level[`basic_f32<H>`, `basic_f64<H>`]
| Alias templates selecting the policy by type: a tag (`throwing`, `saturating`) or a user defined handler
|===

=== Bounded Types
Expand All @@ -101,7 +125,10 @@ https://www.boost.org/LICENSE_1_0.txt
| Type | Description

| xref:policies.adoc[`overflow_policy`]
| Enum class specifying the overflow handling policy for arithmetic operations
| Enum class specifying the overflow handling policy, at the call site or as the type's second template parameter

| xref:policies.adoc#policies_type_level[`error_kind`]
| Enum class identifying the failure reported to a user defined handler

| xref:cuda.adoc#cuda_device_exception_mode[`device_exception_mode`]
| Enum class controlling whether CUDA device errors trap the kernel or defer to the host
Expand Down
2 changes: 1 addition & 1 deletion doc/modules/ROOT/pages/comparisons.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The matrix below compares Boost.SafeNumbers against the closest C++ libraries (B
| Panics in debug, wraps in release; `checked_` / `saturating_` / `wrapping_` methods are explicit.

| *Alternative overflow handling*
| Per-operation functions: `saturating_*`, `checked_*`, `overflowing_*`, plus the throwing default.
| Both forms: per-operation functions (`saturating_*`, `checked_*`, `overflowing_*`) and opt-in policy-carrying types (`sat_u8`, `strict_i32`), plus the throwing default.
| Selected as a template parameter on the type (`safe<T, PP, EP>`); no per-operation functions.
| Prevented by construction; explicit clamp / wrap helpers.
| Arithmetic policy (undefined or checked) selectable.
Expand Down
22 changes: 14 additions & 8 deletions doc/modules/ROOT/pages/design.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ The programmer writes the same code regardless of whether it will be evaluated a

=== Type Safety as a First-Class Concern

The safe types (`u8`, `u16`, `u32`, `u64`, `u128`) are concrete, named types, not template wrappers around a policy.
The primary vocabulary of the library is a set of concrete, named aliases (`u8`, `u16`, `u32`, `u64`, `u128`, and their signed and floating-point counterparts) with throwing semantics.
This makes them easy to read, easy to teach, and easy to use as drop-in replacements for builtin types.
Overflow policies are expressed through named free functions (`saturating_add`, `checked_add`, etc.) rather than through type-level policy parameters.
This keeps the type system simple (and readable in a debugger) while still allowing full control over overflow behavior at each call site.
Alternative overflow behavior is available two ways: named free functions (`saturating_add`, `checked_add`, etc.) select the behavior at each call site, and an optional second template parameter on the types selects it for every operation on a value.
The policy-carrying aliases are just as concrete and named (`sat_u8`, `strict_i32`), the default remains `throw_exception`, and the debugger visualizers understand both forms.
Policies whose result is not the operand type (`checked`, `overflow_tuple`, `widen`) exist only as free functions, so a value of a safe type is always exactly its numeric value.

[source,c++]
----
Expand Down Expand Up @@ -92,10 +93,10 @@ clang-darwin.compile.c++ ../../../bin.v2/libs/safe_numbers/test/compile_fail_bas
../examples/compile_fail_basic_usage_constexpr.cpp:18:22: error: constexpr variable 'z' must be initialized by a constant expression
18 | constexpr u8 z {x + y};
| ^ ~~~~~~~
../../../boost/safe_numbers/detail/unsigned_integer_basis.hpp:397:17: note: subexpression not valid in a constant expression
397 | throw std::overflow_error("Overflow detected in u8 addition");
../../../boost/safe_numbers/detail/unsigned_integer_basis.hpp:790:17: note: subexpression not valid in a constant expression
790 | throw std::overflow_error("Overflow detected in u8 addition");
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../examples/compile_fail_basic_usage_constexpr.cpp:18:25: note: in call to 'operator+<unsigned char>({255}, {2})'
../examples/compile_fail_basic_usage_constexpr.cpp:18:25: note: in call to 'operator+<unsigned char, boost::safe_numbers::overflow_policy::throw_exception>({255}, {2})'
18 | constexpr u8 z {x + y};
| ^~~~~
1 error generated.
Expand Down Expand Up @@ -185,8 +186,9 @@ What changes is the failure model.
Integer overflow is a silent wrap that the library must detect, whereas IEEE 754 arithmetic never traps and always yields a value, saturating to a signed infinity on range escape and producing a NaN on an undefined form.
The safe floating-point types therefore do not detect a wrap: they run the native operation and classify its IEEE 754 result, turning saturation to positive or negative infinity into `std::overflow_error` or `std::underflow_error`, and the invalid operations, a NaN operand, and division by zero into `std::domain_error`.

For this reason the overflow policies described below do not apply to the floating-point types.
IEEE 754 already defines saturation to infinity and the propagation of NaN, so there is no `saturate` or `checked` variant to select; the safe types simply report those exceptional results rather than offering alternative numeric behavior.
For this reason most of the overflow policies described below do not apply to the floating-point types.
IEEE 754 already defines saturation to infinity and the propagation of NaN, so there is no `checked` or `strict` variant to select; the safe types simply report those exceptional results rather than offering alternative numeric behavior.
The one policy floats do support, as the type-level `saturate` (`sat_f32`, `sat_f64`), embraces that fact: it is raw IEEE 754 arithmetic with no classification at all, matching the value component of the `overflowing_*` functions.
The operation surface is also deliberately smaller than for the integer types: only `pass:[+]`, `-`, `pass:[*]`, and `/` are provided, with no compound assignment, increment, decrement, unary, bitwise, or remainder operators, so that every value change passes through a single point where the IEEE 754 result is checked.
The dual compile-time and runtime behavior is unchanged: an exceptional result reached during constant evaluation is a compile-time error, exactly as for the integer types.
See xref:floats.adoc[] for the full type behavior and xref:verification.adoc[] for the formal verification of the classification logic.
Expand Down Expand Up @@ -214,6 +216,10 @@ auto [c, overflow] = overflowing_add(u8{200}, u8{100}); // c == u8{44}, overflow

This approach is inspired by Rust's primitive type API, where `checked_add` and `saturating_add` are methods on integer types.

When a whole value or algorithm should follow one behavior rather than each call site choosing, the policy can live in the type instead: `sat_u8` saturates on every operator and `strict_u32` terminates on any error, mirroring Rust's `Wrapping<T>`-style wrapper types.
The value-returning policies remain call-site only because they change the result type.
See xref:policies.adoc#policies_type_level[Policies as Part of the Type].

For generic code that needs to be parameterized on the overflow policy, the library provides policy-parameterized free functions:

[source,c++]
Expand Down
46 changes: 46 additions & 0 deletions doc/modules/ROOT/pages/examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,52 @@ add<saturate>(max, 1) = 4294967295
----
====

[#examples_policy_types]
== Type-Level Policies

The overflow policy can also be part of the type itself: every operator on `sat_u8` saturates and every operator on `strict_u32` terminates on error, with no per-call ceremony.
See xref:policies.adoc#policies_type_level[Policies as Part of the Type] for the full model.

.This https://github.com/boostorg/safe_numbers/blob/develop/examples/policy_types.cpp[example] demonstrates the saturating type aliases, their equivalence with the named free functions, and their constexpr behavior.
====
[source, c++]
----
include::example$policy_types.cpp[]
----

Output:
----
sum of 100 tens in a sat_u8 = 255
sat_u8{200} + sat_u8{100} = 255
saturating_add(u8{200}, u8{100}) = 255
constexpr sat_u8{255} + sat_u8{1} = 255
sat_f32{FLT_MAX} + sat_f32{FLT_MAX} = inf
----
====

[#examples_user_defined_handler]
== User Defined Error Handlers

Any stateless type with a suitable `on_error` can be the policy, selected by name through the `basic_*` alias templates: `basic_u8<my_handler>`.
See xref:policies.adoc#policies_type_level[Policies as Part of the Type] for the handler contract.

.This https://github.com/boostorg/safe_numbers/blob/develop/examples/user_defined_handler.cpp[example] demonstrates a wrapping handler, a logging handler, and the tag types that select the built-in policies.
====
[source, c++]
----
include::example$user_defined_handler.cpp[]
----

Output:
----
wrap_u8{250} += 10 = 4
constexpr wrap_u8{255} + wrap_u8{1} = 0
recovered: Overflow detected in u8 addition
log_u8{200} + log_u8{200} = 144
ieee_f32{FLT_MAX} + ieee_f32{FLT_MAX} = inf
----
====

[#examples_safety_profile]
== Integer Safety Profile

Expand Down
12 changes: 9 additions & 3 deletions doc/modules/ROOT/pages/floats.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ namespace boost::safe_numbers {
using f32 = detail::float_basis<float>;
using f64 = detail::float_basis<double>;

template <compatible_float_type BasisType>
// Raw IEEE 754 semantics: overflow saturates to infinity, NaN propagates,
// and division by zero yields infinity. No checks run.
using sat_f32 = detail::float_basis<float, overflow_policy::saturate>;
using sat_f64 = detail::float_basis<double, overflow_policy::saturate>;

template <compatible_float_type BasisType, auto ErrorPolicy = overflow_policy::throw_exception>
class float_basis {

public:
Expand Down Expand Up @@ -276,9 +281,10 @@ The following operations are **not** provided:

Bitwise operators have no meaning for floating-point values, and the remaining operations are omitted to keep the type minimal: every value change goes through one of the five checked binary operators, so there is a single place where IEEE 754 exceptional results are intercepted.

The policy-based free functions offered for the integer types (`saturating_*`, `overflowing_*`, `checked_*`, `strict_*`, and `widening_*`) are likewise **not** provided for floating-point types.
Most of the policy-based free functions offered for the integer types (`saturating_*`, `checked_*`, `strict_*`, and `widening_*`) are likewise **not** provided for floating-point types; the one exception is the `overflowing_*` family documented above.
IEEE 754 already defines saturation to infinity and the propagation of NaN; the safe floating-point types intercept exactly those exceptional results and report them, rather than offering alternative numeric policies.
See xref:policies.adoc[] for the policy model as it applies to the integer types.
For code that wants the raw IEEE behavior on every operation, the type-level `saturate` policy (`sat_f32`, `sat_f64`) provides it with no per-call ceremony.
See xref:policies.adoc[] for the full policy model.

== Mixed-Width Operations

Expand Down
Loading
Loading