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
5 changes: 4 additions & 1 deletion doc/modules/ROOT/pages/api_reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ https://www.boost.org/LICENSE_1_0.txt

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

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

=== Arithmetic
Expand Down Expand Up @@ -231,7 +234,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`)
| Integer utility functions (`isqrt`, `remove_trailing_zeros`, `is_power_10`, `is_power_2`, `ipow`, `log2`, `log10`)

| `<boost/safe_numbers/iostream.hpp>`
| Stream I/O operators (`operator<<`, `operator>>`) for library types
Expand Down
59 changes: 59 additions & 0 deletions doc/modules/ROOT/pages/integer_utilities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,65 @@ static_assert(log2(verified_u32{u32{1048576}}) == 20); // 2^20
static_assert(log2(verified_u64{u64{9223372036854775808ULL}}) == 63);
----

== log10

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

Uses an O(1) algorithm based on the most significant bit position to approximate `log~10~`, refined with a single power-of-10 table lookup.

=== Runtime Overload

[source,c++]
----
template <non_bounded_unsigned_library_type T>
requires (!is_verified_type_v<T>)
constexpr auto log10(const T n) noexcept -> int;
----

Computes `floor(log~10~(n))` using `num_digits(n) - 1`, where `num_digits` approximates the digit count via `log~10~(x) {tilde}{equals} log~2~(x) / log~2~(10)` and refines with at most two comparisons against a power-of-10 lookup table.

==== Parameters

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

==== Return Value

The floor of the base-10 logarithm of `n`.

==== Example

[source,c++]
----
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
----

=== Verified Overload

[source,c++]
----
template <non_bounded_unsigned_library_type T>
consteval auto log10(const verified_type_basis<T> n) noexcept -> int;
----

Compile-time only overload for verified types.

Since `log10` is `consteval` for verified types, the result is guaranteed to be a compile-time constant.

==== Example

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

static_assert(log10(verified_u32{u32{1000000000}}) == 9);
static_assert(log10(verified_u64{u64{10000000000000000000ULL}}) == 19);
----

== ipow

Integer exponentiation using the exponentiation-by-squaring algorithm.
Expand Down
141 changes: 141 additions & 0 deletions include/boost/safe_numbers/detail/num_digits.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_SAFE_NUMBERS_DETAIL_NUM_DIGITS_HPP
#define BOOST_SAFE_NUMBERS_DETAIL_NUM_DIGITS_HPP

// https://stackoverflow.com/questions/1489830/efficient-way-to-determine-number-of-digits-in-an-integer
// https://graphics.stanford.edu/~seander/bithacks.html

#include <boost/safe_numbers/detail/int128/bit.hpp>
#include <boost/safe_numbers/detail/int128/detail/config.hpp>

#ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE

#include <boost/core/bit.hpp>
#include <array>
#include <cstdint>
#include <limits>

#endif

namespace boost::safe_numbers::detail {

// ============================================================================
// Power-of-10 lookup tables (generated at compile time)
// ============================================================================

template <typename T>
consteval auto make_powers_of_10() noexcept
{
constexpr auto N {static_cast<std::size_t>(std::numeric_limits<T>::digits10 + 1)};

std::array<T, N> table {};
table[0] = T{1};
for (std::size_t i {1}; i < N; ++i)
{
table[i] = static_cast<T>(table[i - 1] * T{10});
}

return table;
}

inline constexpr auto powers_of_10_u32 {make_powers_of_10<std::uint32_t>()};
inline constexpr auto powers_of_10_u64 {make_powers_of_10<std::uint64_t>()};
inline constexpr auto powers_of_10_u128 {make_powers_of_10<int128::uint128_t>()};

// ============================================================================
// num_digits: counts the number of decimal digits using MSB approximation
//
// Algorithm: log10(x) ~= log2(x) / log2(10) ~= log2(x) / 3.322
// Use (msb * 1000) / 3322 + 1 as an estimate, then refine with a
// table lookup (at most one comparison in each direction).
// ============================================================================

// Overload for types fitting in uint32_t (uint8_t, uint16_t, uint32_t)
template <typename T>
requires (std::numeric_limits<T>::digits <= 32 && std::is_unsigned_v<T>)
constexpr auto num_digits(const T init_x) noexcept -> int
{
const auto x {static_cast<std::uint32_t>(init_x)};

if (x == 0)
{
return 1;
}

const auto msb {32 - boost::core::countl_zero(x)};

// Approximate log10
const auto estimated_digits {(msb * 1000) / 3322 + 1};

if (estimated_digits < 10 && x >= powers_of_10_u32[static_cast<std::size_t>(estimated_digits)])
{
return estimated_digits + 1;
}

if (estimated_digits > 1 && x < powers_of_10_u32[static_cast<std::size_t>(estimated_digits - 1)])
{
return estimated_digits - 1;
}

return estimated_digits;
}

// Overload for uint64_t
constexpr auto num_digits(const std::uint64_t x) noexcept -> int
{
if (x <= UINT32_MAX)
{
return num_digits(static_cast<std::uint32_t>(x));
}

const auto msb {64 - boost::core::countl_zero(x)};

// Approximate log10
const auto estimated_digits {(msb * 1000) / 3322 + 1};

if (estimated_digits < 20 && x >= powers_of_10_u64[static_cast<std::size_t>(estimated_digits)])
{
return estimated_digits + 1;
}

if (estimated_digits > 1 && x < powers_of_10_u64[static_cast<std::size_t>(estimated_digits - 1)])
{
return estimated_digits - 1;
}

return estimated_digits;
}

// Overload for uint128_t
constexpr auto num_digits(const boost::int128::uint128_t& x) noexcept -> int
{
if (x.high == UINT64_C(0))
{
return num_digits(x.low);
}

const auto msb {64 + (64 - boost::int128::detail::countl_zero(x.high))};

// Approximate log10
const auto estimated_digits {(msb * 1000) / 3322 + 1};

if (estimated_digits < 39 && x >= powers_of_10_u128[static_cast<std::size_t>(estimated_digits)])
{
return estimated_digits + 1;
}

// Estimated digits can't be less than 20 (65+ bits)
if (x < powers_of_10_u128[static_cast<std::size_t>(estimated_digits - 1)])
{
return estimated_digits - 1;
}

return estimated_digits;
}

} // namespace boost::safe_numbers::detail

#endif // BOOST_SAFE_NUMBERS_DETAIL_NUM_DIGITS_HPP
18 changes: 18 additions & 0 deletions include/boost/safe_numbers/integer_utilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <boost/safe_numbers/detail/config.hpp>
#include <boost/safe_numbers/detail/type_traits.hpp>
#include <boost/safe_numbers/detail/rtz.hpp>
#include <boost/safe_numbers/detail/num_digits.hpp>
#include <boost/safe_numbers/bit.hpp>

namespace boost::safe_numbers {
Expand Down Expand Up @@ -120,6 +121,23 @@ consteval auto log2(const detail::verified_type_basis<T> n) noexcept -> int
return bit_width(n) - 1;
}

// Integer log base 10: floor(log10(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>
requires (!detail::is_verified_type_v<T>)
constexpr auto log10(const T n) noexcept -> int
{
using underlying = detail::underlying_type_t<T>;
return detail::num_digits(static_cast<underlying>(n)) - 1;
}

template <detail::non_bounded_unsigned_library_type T>
consteval auto log10(const detail::verified_type_basis<T> n) noexcept -> int
{
using underlying = detail::underlying_type_t<T>;
return detail::num_digits(static_cast<underlying>(n)) - 1;
}

namespace detail {

// Iterative exponentiation by squaring: O(log b) multiplications
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ run test_is_power_10.cpp ;
run test_is_power_2.cpp ;
run test_ipow.cpp ;
run test_log2.cpp ;
run test_log10.cpp ;

# Compile Tests
compile compile_tests/compile_test_unsigned_integers.cpp ;
Expand Down
Loading
Loading