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: 6 additions & 3 deletions doc/modules/ROOT/pages/api_reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,15 @@ https://www.boost.org/LICENSE_1_0.txt
| xref:integer_utilities.adoc[`ipow`]
| Integer exponentiation by squaring

| xref:integer_utilities.adoc[`log2`]
| xref:integer_utilities.adoc[`ilog2`]
| Returns the floor of the base-2 logarithm

| xref:integer_utilities.adoc[`log10`]
| xref:integer_utilities.adoc[`ilog10`]
| Returns the floor of the base-10 logarithm

| xref:integer_utilities.adoc[`ilog`]
| Returns the floor of an arbitrary-base logarithm

| xref:integer_utilities.adoc[`abs_diff`]
| Computes the absolute difference between two safe integers

Expand Down Expand Up @@ -299,7 +302,7 @@ This header is not included in the convenience header since it requires external
| Contains specializations of `<format>` for library types

| `<boost/safe_numbers/integer_utilities.hpp>`
| Integer utility functions (`isqrt`, `remove_trailing_zeros`, `is_power_10`, `is_power_2`, `ipow`, `log2`, `log10`, `abs_diff`, `div_ceil`, `next_multiple_of`)
| Integer utility functions (`isqrt`, `remove_trailing_zeros`, `is_power_10`, `is_power_2`, `ipow`, `ilog2`, `ilog10`, `ilog`, `abs_diff`, `div_ceil`, `next_multiple_of`)

| `<boost/safe_numbers/numeric.hpp>`
| Standard numeric algorithms (`gcd`, `lcm`, `midpoint`)
Expand Down
78 changes: 65 additions & 13 deletions doc/modules/ROOT/pages/integer_utilities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,14 @@ is_power_10(u32{1234}); // false
is_power_10(u64{10000000000000000000ULL}); // true
----

== log2
== ilog2

Returns the integer base-2 logarithm (floor of log~2~) of a value.

[source,c++]
----
template <non_bounded_unsigned_library_type T>
[[nodiscard]] constexpr auto log2(const T n) noexcept -> int;
[[nodiscard]] constexpr auto ilog2(const T n) -> int;
----

Computes `floor(log~2~(n))` using `bit_width(n) - 1`.
Expand All @@ -180,10 +180,13 @@ Computes `floor(log~2~(n))` using `bit_width(n) - 1`.

* `n` -- The value to compute the logarithm of. Must be non-zero.

==== Throws

`std::domain_error` if `n == 0`.

==== Return Value

The floor of the base-2 logarithm of `n`.
For `n == 0`, returns `-1` (since `bit_width(0) == 0`).

==== Complexity

Expand All @@ -195,13 +198,13 @@ O(1).
----
using namespace boost::safe_numbers;

auto r1 = log2(u32{1024}); // r1 == 10 (2^10 = 1024)
auto r2 = log2(u32{1000}); // r2 == 9 (floor of log2(1000))
auto r3 = log2(u32{1}); // r3 == 0
auto r4 = log2(u64{9223372036854775808ULL}); // r4 == 63 (2^63)
auto r1 = ilog2(u32{1024}); // r1 == 10 (2^10 = 1024)
auto r2 = ilog2(u32{1000}); // r2 == 9 (floor of log2(1000))
auto r3 = ilog2(u32{1}); // r3 == 0
auto r4 = ilog2(u64{9223372036854775808ULL}); // r4 == 63 (2^63)
----

== log10
== ilog10

Returns the integer base-10 logarithm (floor of log~10~) of a value.

Expand All @@ -210,7 +213,7 @@ Uses an O(1) algorithm based on the most significant bit position to approximate
[source,c++]
----
template <non_bounded_unsigned_library_type T>
[[nodiscard]] constexpr auto log10(const T n) noexcept -> int;
[[nodiscard]] constexpr auto ilog10(const T n) -> int;
----

Computes `floor(log~10~(n))` using `num_digits(n) - 1`, where `num_digits` approximates the digit count via `log~10~(x) ~= log~2~(x) / log~2~(10)` and refines with at most two comparisons against a power-of-10 lookup table.
Expand All @@ -219,6 +222,10 @@ Computes `floor(log~10~(n))` using `num_digits(n) - 1`, where `num_digits` appro

* `n` -- The value to compute the logarithm of. Must be non-zero.

==== Throws

`std::domain_error` if `n == 0`.

==== Return Value

The floor of the base-10 logarithm of `n`.
Expand All @@ -229,10 +236,55 @@ The floor of the base-10 logarithm of `n`.
----
using namespace boost::safe_numbers;

auto r1 = log10(u32{1000}); // r1 == 3
auto r2 = log10(u32{999}); // r2 == 2 (floor of log10(999))
auto r3 = log10(u32{1}); // r3 == 0
auto r4 = log10(u64{10000000000000000000ULL}); // r4 == 19
auto r1 = ilog10(u32{1000}); // r1 == 3
auto r2 = ilog10(u32{999}); // r2 == 2 (floor of log10(999))
auto r3 = ilog10(u32{1}); // r3 == 0
auto r4 = ilog10(u64{10000000000000000000ULL}); // r4 == 19
----

== ilog

Returns the integer logarithm in an arbitrary base (floor of log~base~) of a value.

[source,c++]
----
template <non_bounded_unsigned_library_type T>
[[nodiscard]] constexpr auto ilog(const T n, const T base) -> int;
----

Computes `floor(log~base~(n))` by repeated division.
For the common cases of base 2 and base 10, prefer the specialized `ilog2` and `ilog10` functions which run in O(1).

==== Parameters

* `n` -- The value to compute the logarithm of. Must be non-zero.
* `base` -- The base of the logarithm. Must be >= 2.

==== Throws

* `std::domain_error` if `n == 0`.
* `std::domain_error` if `base < 2` (base 0 would cause division by zero; base 1 would cause an infinite loop).

==== Return Value

The floor of the base-`base` logarithm of `n`.
For `n < base`, returns `0`.

==== Complexity

O(log~base~(n)) divisions.

==== Example

[source,c++]
----
using namespace boost::safe_numbers;

auto r1 = ilog(u32{1000}, u32{10}); // r1 == 3 (floor of log10(1000))
auto r2 = ilog(u32{1024}, u32{2}); // r2 == 10 (floor of log2(1024))
auto r3 = ilog(u32{80}, u32{3}); // r3 == 3 (floor of log3(80))
auto r4 = ilog(u32{3125}, u32{5}); // r4 == 5 (5^5 = 3125)
auto r5 = ilog(u32{1}, u32{7}); // r5 == 0 (any base, log(1) = 0)
----

== ipow
Expand Down
56 changes: 53 additions & 3 deletions include/boost/safe_numbers/integer_utilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
#include <boost/safe_numbers/detail/num_digits.hpp>
#include <boost/safe_numbers/bit.hpp>

#ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE

#include <boost/throw_exception.hpp>
#include <stdexcept>

#endif

namespace boost::safe_numbers {

// Newton's method as it can't possibly overflow, and converges rapidly
Expand Down Expand Up @@ -71,20 +78,63 @@ template <detail::non_bounded_unsigned_library_type T>

// Integer log base 2: floor(log2(n)) == bit_width(n) - 1
template <detail::non_bounded_unsigned_library_type T>
[[nodiscard]] constexpr auto log2(const T n) noexcept -> int
[[nodiscard]] constexpr auto ilog2(const T n) -> int
{
using underlying_type = detail::underlying_type_t<T>;

if (static_cast<underlying_type>(n) == underlying_type{0})
{
BOOST_THROW_EXCEPTION(std::domain_error("ilog2(0) is undefined"));
}

return bit_width(n) - 1;
}

// Integer log base 10: floor(log10(n)) == num_digits(n) - 1
// Integer log base 10: floor(ilog10(n)) == num_digits(n) - 1
// Uses MSB-based approximation with power-of-10 table lookup (O(1))
template <detail::non_bounded_unsigned_library_type T>
[[nodiscard]] constexpr auto log10(const T n) noexcept -> int
[[nodiscard]] constexpr auto ilog10(const T n) -> int
{
using underlying_type = detail::underlying_type_t<T>;

if (static_cast<underlying_type>(n) == underlying_type{0})
{
BOOST_THROW_EXCEPTION(std::domain_error("ilog10(0) is undefined"));
}

return detail::num_digits(static_cast<underlying_type>(n)) - 1;
}

// Integer log arbitrary base: floor(log_base(n))
// Repeated division: O(log_base(n)) divisions
template <detail::non_bounded_unsigned_library_type T>
[[nodiscard]] constexpr auto ilog(const T n, const T base) -> int
{
using underlying_type = detail::underlying_type_t<T>;

if (static_cast<underlying_type>(n) == underlying_type{0})
{
BOOST_THROW_EXCEPTION(std::domain_error("ilog(0, base) is undefined"));
}

if (static_cast<underlying_type>(base) < underlying_type{2})
{
BOOST_THROW_EXCEPTION(std::domain_error("ilog(n, base) requires base >= 2"));
}

auto result {0};
auto val {static_cast<underlying_type>(n)};
const auto b {static_cast<underlying_type>(base)};

while (val >= b)
{
val /= b;
++result;
}

return result;
}

namespace detail {

// Iterative exponentiation by squaring: O(log b) multiplications
Expand Down
5 changes: 3 additions & 2 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ run test_remove_trailing_zeros.cpp ;
run test_is_power_10.cpp ;
run test_is_power_2.cpp ;
run test_ipow.cpp ;
run test_log2.cpp ;
run test_log10.cpp ;
run test_ilog2.cpp ;
run test_ilog10.cpp ;
run test_ilog.cpp ;
run test_gcd.cpp ;
run test_lcm.cpp ;
run test_midpoint.cpp ;
Expand Down
Loading