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
1 change: 1 addition & 0 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
** xref:api_reference.adoc#api_headers[Headers]
* xref:policies.adoc[]
* xref:unsigned_integers.adoc[]
* xref:signed_integers.adoc[]
* xref:bounded_uint.adoc[]
* xref:cuda.adoc[]
* xref:literals.adoc[]
Expand Down
25 changes: 25 additions & 0 deletions doc/modules/ROOT/pages/api_reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,28 @@ https://www.boost.org/LICENSE_1_0.txt
| Safe unsigned 128-bit integer
|===

=== Signed Integer Types

[cols="1,2", options="header"]
|===
| Type | Description

| xref:signed_integers.adoc[`i8`]
| Safe signed 8-bit integer

| xref:signed_integers.adoc[`i16`]
| Safe signed 16-bit integer

| xref:signed_integers.adoc[`i32`]
| Safe signed 32-bit integer

| xref:signed_integers.adoc[`i64`]
| Safe signed 64-bit integer

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

=== Bounded Unsigned Integer Type

[cols="1,2", options="header"]
Expand Down Expand Up @@ -351,6 +373,9 @@ This header is not included in the convenience header since it requires external
| `<boost/safe_numbers/overflow_policy.hpp>`
| The `overflow_policy` enum class

| `<boost/safe_numbers/signed_integers.hpp>`
| All signed safe integer types (`i8`, `i16`, `i32`, `i64`, `i128`)

| `<boost/safe_numbers/unsigned_integers.hpp>`
| All unsigned safe integer types (`u8`, `u16`, `u32`, `u64`, `u128`)

Expand Down
150 changes: 150 additions & 0 deletions doc/modules/ROOT/pages/signed_integers.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
////
Copyright 2026 Matt Borland
Distributed under the Boost Software License, Version 1.0.
https://www.boost.org/LICENSE_1_0.txt
////

[#signed_integers]

= Signed Integer Types

== Description

The library provides safe signed integer types that detect overflow, underflow, division by zero, and other undefined behavior at runtime.
These types are drop-in replacements for the standard signed integer types with added safety guarantees.

|===
| Type | Underlying Type | Width | Min | Max

| `i8` | `std::int8_t` | 8 bits | -128 | 127
| `i16` | `std::int16_t` | 16 bits | -32,768 | 32,767
| `i32` | `std::int32_t` | 32 bits | -2,147,483,648 | 2,147,483,647
| `i64` | `std::int64_t` | 64 bits | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807
| `i128` | `int128_t` | 128 bits | -170,141,183,460,469,231,731,687,303,715,884,105,728 | 170,141,183,460,469,231,731,687,303,715,884,105,727
|===

Each type exposes a `basis_type` member type alias that refers to the underlying integer type, allowing conversion back to built-in types when needed.

[source,c++]
----
#include <boost/safe_numbers/signed_integers.hpp>

namespace boost::safe_numbers {

using i8 = detail::signed_integer_basis<std::int8_t>;
using i16 = detail::signed_integer_basis<std::int16_t>;
using i32 = detail::signed_integer_basis<std::int32_t>;
using i64 = detail::signed_integer_basis<std::int64_t>;
using i128 = detail::signed_integer_basis<int128::int128_t>;

template <signed_integral BasisType>
class signed_integer_basis {

public:
using basis_type = BasisType;

// Construction
constexpr signed_integer_basis() noexcept = default;
explicit constexpr signed_integer_basis(BasisType val) noexcept;

template <typename T>
requires std::is_same_v<T, bool>
explicit constexpr signed_integer_basis(T) noexcept = delete; // bool prohibited

// Conversion to underlying types
template <signed_integral OtherBasis>
explicit constexpr operator OtherBasis() const;

// Comparison operators
friend constexpr auto operator<=>(signed_integer_basis lhs, signed_integer_basis rhs) noexcept
-> std::strong_ordering = default;

// Unary operators
constexpr auto operator+() const noexcept -> signed_integer_basis;
constexpr auto operator-() const -> signed_integer_basis;

// Compound assignment operators (arithmetic)
template <signed_integral OtherBasis>
constexpr auto operator+=(signed_integer_basis<OtherBasis> rhs) -> signed_integer_basis&;

template <signed_integral OtherBasis>
constexpr auto operator-=(signed_integer_basis<OtherBasis> rhs) -> signed_integer_basis&;

template <signed_integral OtherBasis>
constexpr auto operator*=(signed_integer_basis<OtherBasis> rhs) -> signed_integer_basis&;

template <signed_integral OtherBasis>
constexpr auto operator/=(signed_integer_basis<OtherBasis> rhs) -> signed_integer_basis&;

template <signed_integral OtherBasis>
constexpr auto operator%=(signed_integer_basis<OtherBasis> rhs) -> signed_integer_basis&;

// Increment and decrement operators
constexpr auto operator++() -> signed_integer_basis&;
constexpr auto operator++(int) -> signed_integer_basis;
constexpr auto operator--() -> signed_integer_basis&;
constexpr auto operator--(int) -> signed_integer_basis;

}; // class signed_integer_basis

// Arithmetic operators (throw on overflow/underflow)
template <signed_integral BasisType>
constexpr auto operator+(signed_integer_basis<BasisType> lhs,
signed_integer_basis<BasisType> rhs) -> signed_integer_basis<BasisType>;

template <signed_integral BasisType>
constexpr auto operator-(signed_integer_basis<BasisType> lhs,
signed_integer_basis<BasisType> rhs) -> signed_integer_basis<BasisType>;

template <signed_integral BasisType>
constexpr auto operator*(signed_integer_basis<BasisType> lhs,
signed_integer_basis<BasisType> rhs) -> signed_integer_basis<BasisType>;

template <signed_integral BasisType>
constexpr auto operator/(signed_integer_basis<BasisType> lhs,
signed_integer_basis<BasisType> rhs) -> signed_integer_basis<BasisType>;

template <signed_integral BasisType>
constexpr auto operator%(signed_integer_basis<BasisType> lhs,
signed_integer_basis<BasisType> rhs) -> signed_integer_basis<BasisType>;

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

== Increment and Decrement Operators

[source,c++]
----
constexpr auto operator++() -> signed_integer_basis&;
constexpr auto operator++(int) -> signed_integer_basis;
constexpr auto operator--() -> signed_integer_basis&;
constexpr auto operator--(int) -> signed_integer_basis;
----

- `++` (pre/post): Throws `std::overflow_error` if the value is already at `std::numeric_limits<BasisType>::max()`
- `--` (pre/post): Throws `std::underflow_error` if the value is already at `std::numeric_limits<BasisType>::min()`

== Unary Operators

[source,c++]
----
constexpr auto operator+() const noexcept -> signed_integer_basis;
constexpr auto operator-() const -> signed_integer_basis;
----

- `+`: Returns a copy of the value (identity).
- `-`: Throws `std::domain_error` if the value is `std::numeric_limits<BasisType>::min()`, since negating the minimum value of a two's complement signed integer overflows.

== Mixed-Width Operations

Operations between different width safe signed integer types are compile-time errors.
The operands must be promoted to a common type explicitly before performing the operation.

[source,c++]
----
auto a = i16{100};
auto b = i32{200};

// auto result = a + b; // Compile error: mismatched types
auto result = static_cast<i32>(a) + b; // OK: explicit promotion
----
124 changes: 124 additions & 0 deletions include/boost/safe_numbers/detail/signed_integer_basis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ class signed_integer_basis

template <fundamental_signed_integral OtherBasis>
constexpr auto operator%=(signed_integer_basis<OtherBasis> rhs) -> signed_integer_basis&;

constexpr auto operator++() -> signed_integer_basis&;

constexpr auto operator++(int) -> signed_integer_basis;

constexpr auto operator--() -> signed_integer_basis&;

constexpr auto operator--(int) -> signed_integer_basis;
};

// Helper for diagnostic messages
Expand Down Expand Up @@ -1947,6 +1955,122 @@ constexpr auto signed_integer_basis<BasisType>::operator%=(const signed_integer_
return *this;
}

// ------------------------------
// Increment / Decrement error messages
// ------------------------------

template <fundamental_signed_integral BasisType>
constexpr auto signed_overflow_inc_msg() noexcept -> const char*
{
if constexpr (std::is_same_v<BasisType, std::int8_t>)
{
return "Overflow detected in i8 increment";
}
else if constexpr (std::is_same_v<BasisType, std::int16_t>)
{
return "Overflow detected in i16 increment";
}
else if constexpr (std::is_same_v<BasisType, std::int32_t>)
{
return "Overflow detected in i32 increment";
}
else if constexpr (std::is_same_v<BasisType, std::int64_t>)
{
return "Overflow detected in i64 increment";
}
else
{
return "Overflow detected in i128 increment";
}
}

template <fundamental_signed_integral BasisType>
constexpr auto signed_underflow_dec_msg() noexcept -> const char*
{
if constexpr (std::is_same_v<BasisType, std::int8_t>)
{
return "Underflow detected in i8 decrement";
}
else if constexpr (std::is_same_v<BasisType, std::int16_t>)
{
return "Underflow detected in i16 decrement";
}
else if constexpr (std::is_same_v<BasisType, std::int32_t>)
{
return "Underflow detected in i32 decrement";
}
else if constexpr (std::is_same_v<BasisType, std::int64_t>)
{
return "Underflow detected in i64 decrement";
}
else
{
return "Underflow detected in i128 decrement";
}
}

// ------------------------------
// Pre and post increment
// ------------------------------

template <fundamental_signed_integral BasisType>
constexpr auto signed_integer_basis<BasisType>::operator++()
-> signed_integer_basis&
{
if (this->basis_ == std::numeric_limits<BasisType>::max()) [[unlikely]]
{
BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_inc_msg<BasisType>());
}

++this->basis_;
return *this;
}

template <fundamental_signed_integral BasisType>
constexpr auto signed_integer_basis<BasisType>::operator++(int)
-> signed_integer_basis
{
if (this->basis_ == std::numeric_limits<BasisType>::max()) [[unlikely]]
{
BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_inc_msg<BasisType>());
}

const auto temp {*this};
++this->basis_;
return temp;
}

// ------------------------------
// Pre and post decrement
// ------------------------------

template <fundamental_signed_integral BasisType>
constexpr auto signed_integer_basis<BasisType>::operator--()
-> signed_integer_basis&
{
if (this->basis_ == std::numeric_limits<BasisType>::min()) [[unlikely]]
{
BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_dec_msg<BasisType>());
}

--this->basis_;
return *this;
}

template <fundamental_signed_integral BasisType>
constexpr auto signed_integer_basis<BasisType>::operator--(int)
-> signed_integer_basis
{
if (this->basis_ == std::numeric_limits<BasisType>::min()) [[unlikely]]
{
BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_dec_msg<BasisType>());
}

const auto temp {*this};
--this->basis_;
return temp;
}

} // namespace boost::safe_numbers::detail

#undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ run test_signed_subtraction.cpp ;
run test_signed_multiplication.cpp ;
run test_signed_division.cpp ;
run test_signed_modulo.cpp ;
run test_signed_inc_dec.cpp ;

run test_unsigned_addition.cpp ;
compile-fail compile_fail_unsigned_addition.cpp ;
Expand Down
Loading
Loading