Skip to content

Commit 3e6e560

Browse files
authored
Merge pull request #60 from cppalliance/54
Add is power of 10 helper function
2 parents ab11f3c + 54d147c commit 3e6e560

File tree

8 files changed

+328
-16
lines changed

8 files changed

+328
-16
lines changed

doc/modules/ROOT/pages/api_reference.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ https://www.boost.org/LICENSE_1_0.txt
168168

169169
| xref:integer_utilities.adoc[`remove_trailing_zeros`]
170170
| Branchlessly removes trailing decimal zeros from a safe unsigned integer
171+
172+
| xref:integer_utilities.adoc[`is_power_10`]
173+
| Tests whether a safe unsigned integer is an exact power of 10
171174
|===
172175

173176
=== Arithmetic

doc/modules/ROOT/pages/design.adoc

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Bugs like these are subtle, difficult to catch in code review, and can have seve
2626

2727
Existing tools help but leave gaps.
2828
Compiler flags like `-Wsign-conversion` and `-Wconversion` catch many issues at compile time, and UBSan catches undefined behavior at runtime.
29-
But UBSan explicitly allows unsigned integer rollover because the standard defines it as well-defined behavior -- even though silent rollover is the source of countless bugs.
29+
But UBSan explicitly allows unsigned integer rollover because the standard defines it as well-defined behavior, even though silent rollover is the source of countless bugs.
3030
Boost.SafeNumbers aims to go further: by default, unsigned rollover is an error.
3131

3232
== Core Design Principles
@@ -42,15 +42,15 @@ The cost is slightly more verbose code at type boundaries; the benefit is that e
4242

4343
=== Fail Early, Fail Loudly
4444

45-
When an operation would produce an incorrect result -- overflow, underflow, division by zero -- the library makes this visible rather than silently continuing with a wrong value.
45+
When an operation produces an incorrect result, (e.g., overflow, underflow, division by zero) the library makes this visible rather than silently continuing with a wrong value.
4646

4747
At compile time, errors become compiler errors.
4848
At runtime, errors throw exceptions by default.
4949
There is no mode where an error is silently ignored unless the programmer explicitly opts into wrapping behavior.
5050

5151
=== Type Safety as a First-Class Concern
5252

53-
The safe types (`u8`, `u16`, `u32`, `u64`, `u128`) are concrete, named types -- not template wrappers around a policy.
53+
The safe types (`u8`, `u16`, `u32`, `u64`, `u128`) are concrete, named types, not template wrappers around a policy.
5454
This makes them easy to read, easy to teach, and easy to use as drop-in replacements for builtin types.
5555

5656
Overflow policies are expressed through named free functions (`saturating_add`, `wrapping_add`, etc.) rather than through type-level policy parameters.
@@ -101,7 +101,7 @@ Different domains require different responses to overflow:
101101
- General application code benefits from *exceptions*: the error is reported and can be handled.
102102
- Some code needs to *detect and branch*: check whether overflow occurred without changing control flow.
103103

104-
The library's default -- `throw_exception` -- is the safest general-purpose choice because it makes errors impossible to ignore.
104+
The library's default, `throw_exception`, is the safest general-purpose choice because it makes errors impossible to ignore.
105105
For other needs, named free functions make the policy explicit at each call site:
106106

107107
[source,c++]
@@ -135,24 +135,24 @@ See xref:policies.adoc[] for the full API reference.
135135

136136
The library intentionally prohibits several operations that are legal in pass:[C++] but are common sources of bugs:
137137

138-
**Construction from `bool`** -- Prevents conflation of boolean logic and integer arithmetic.
138+
**Construction from `bool`**: Prevents conflation of boolean logic and integer arithmetic.
139139
In pass:[C++], `true + true == 2`, which is rarely the intended behavior.
140140
Constructing a safe integer from `bool` is a compile-time error.
141141

142-
**Mixed-width arithmetic** -- Adding a `u8` to a `u32` is a compile-time error.
142+
**Mixed-width arithmetic**: Adding a `u8` to a `u32` is a compile-time error.
143143
In pass:[C++], the narrower operand would be implicitly promoted, which hides the fact that two different-sized values are being combined.
144144
The library requires an explicit conversion so the programmer acknowledges the width difference.
145145

146-
**Unary minus on unsigned types** -- In pass:[C++], `-x` on an unsigned value performs modular negation, producing a large positive number.
146+
**Unary minus on unsigned types**: In pass:[C++], `-x` on an unsigned value performs modular negation, producing a large positive number.
147147
This is almost never intentional.
148-
The library does not define unary minus for unsigned types, so attempting it is a compile-time error.
148+
The library will provide a `static_assert` message should this operation be attempted.
149149

150-
**Implicit conversions to and from builtin types** -- All conversions between safe types and builtin types require an explicit cast.
150+
**Implicit conversions to and from builtin types**: All conversions between safe types and builtin types require an explicit cast.
151151
This prevents accidental loss of safety guarantees when passing values across API boundaries.
152152

153153
== Performance Considerations
154154

155-
The library uses compiler intrinsics for overflow detection: `__builtin_add_overflow`, `__builtin_sub_overflow`, and `__builtin_mul_overflow` on GCC and Clang, and platform-specific intrinsics (`_addcarry_u64`, `_subborrow_u64`, `_umul128`) on MSVC.
155+
The library uses compiler intrinsics for overflow detection: `pass:[__builtin_add_overflow]`, `pass:[__builtin_sub_overflow]`, and `pass:[__builtin_mul_overflow]` on GCC and Clang, and platform-specific intrinsics (`_addcarry_u64`, `_subborrow_u64`, `_umul128`) on MSVC.
156156
These compile to efficient hardware instructions (typically a single arithmetic instruction followed by a conditional branch on the carry or overflow flag).
157157

158158
The target is a runtime penalty of less than 2x compared to builtin types.

doc/modules/ROOT/pages/integer_utilities.adoc

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,60 @@ using namespace boost::safe_numbers;
188188
constexpr auto r = remove_trailing_zeros(verified_u32{u32{5000}});
189189
// r.trimmed_number == 5, r.number_of_removed_zeros == 3
190190
----
191+
192+
== is_power_10
193+
194+
Tests whether an unsigned integer value is an exact power of 10 (i.e., one of 1, 10, 100, 1000, ...).
195+
196+
Implemented using `remove_trailing_zeros` and checking that the trimmed result equals 1.
197+
198+
=== Runtime Overload
199+
200+
[source,c++]
201+
----
202+
template <non_bounded_unsigned_library_type T>
203+
requires (!is_verified_type_v<T>)
204+
constexpr auto is_power_10(const T n) -> bool;
205+
----
206+
207+
==== Parameters
208+
209+
* `n` -- The value to test. Must be non-zero.
210+
211+
==== Return Value
212+
213+
`true` if `n` is a power of 10, `false` otherwise.
214+
215+
==== Example
216+
217+
[source,c++]
218+
----
219+
using namespace boost::safe_numbers;
220+
221+
is_power_10(u32{1000}); // true
222+
is_power_10(u32{1}); // true
223+
is_power_10(u32{1234}); // false
224+
is_power_10(u64{10000000000000000000ULL}); // true
225+
----
226+
227+
=== Verified Overload
228+
229+
[source,c++]
230+
----
231+
template <non_bounded_unsigned_library_type T>
232+
consteval auto is_power_10(const verified_type_basis<T> n) -> bool;
233+
----
234+
235+
Compile-time only overload for verified types.
236+
237+
Since `is_power_10` is `consteval` for verified types, the result is guaranteed to be a compile-time constant and can be used directly in `static_assert`.
238+
239+
==== Example
240+
241+
[source,c++]
242+
----
243+
using namespace boost::safe_numbers;
244+
245+
static_assert(is_power_10(verified_u32{u32{100}}));
246+
static_assert(!is_power_10(verified_u32{u32{200}}));
247+
----

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,8 @@ namespace boost {
2525
namespace int128 {
2626

2727
struct
28-
#if defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_INT128) || defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_MSVC_INT128)
28+
#if (defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_INT128) || defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_MSVC_INT128)) && !defined(_M_IX86)
2929
alignas(alignof(detail::builtin_i128))
30-
#else
31-
alignas(16)
3230
#endif
3331
int128_t
3432
{

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,8 @@ namespace boost {
2626
namespace int128 {
2727

2828
struct
29-
#if defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_INT128) || defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_MSVC_INT128)
29+
#if (defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_INT128) || defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_MSVC_INT128)) && !defined(_M_IX86)
3030
alignas(alignof(detail::builtin_u128))
31-
#else
32-
alignas(16)
3331
#endif
3432
uint128_t
3533
{

include/boost/safe_numbers/integer_utilities.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,25 @@ consteval auto remove_trailing_zeros(const detail::verified_type_basis<T> val)
6161
return detail::remove_trailing_zeros(static_cast<underlying>(val));
6262
}
6363

64+
template <detail::non_bounded_unsigned_library_type T>
65+
requires (!detail::is_verified_type_v<T>)
66+
constexpr auto is_power_10(const T n) -> bool
67+
{
68+
using underlying = typename detail::underlying_type_t<T>;
69+
70+
const auto [trimmed_number, _] = detail::remove_trailing_zeros(static_cast<underlying>(n));
71+
return trimmed_number == static_cast<underlying>(1);
72+
}
73+
74+
template <detail::non_bounded_unsigned_library_type T>
75+
consteval auto is_power_10(const detail::verified_type_basis<T> n) -> bool
76+
{
77+
using underlying = typename detail::underlying_type_t<T>;
78+
79+
const auto [trimmed_number, _] = detail::remove_trailing_zeros(static_cast<underlying>(n));
80+
return trimmed_number == static_cast<underlying>(1);
81+
}
82+
6483
} // namespace boost::safe_numbers
6584

6685
#endif // BOOST_SAFE_NUMBERS_INTEGER_UTILITIES_HPP

test/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ run test_verified_limits.cpp ;
132132
# Utility function tests
133133
run test_isqrt.cpp ;
134134
run test_remove_trailing_zeros.cpp ;
135+
run test_is_power_10.cpp ;
135136

136137
# Compile Tests
137138
compile compile_tests/compile_test_unsigned_integers.cpp ;

0 commit comments

Comments
 (0)