32-bit compilation fixes:

* Use uint64_t instead of size_t as hash return value

 * Expect alignof(uint64_t) <= 8 instead of == 8
This commit is contained in:
Nikita Lisitsa 2025-01-25 20:35:37 +03:00
parent 7c15c1bb0d
commit 2c3565df61
19 changed files with 545 additions and 59 deletions

View file

@ -9,7 +9,7 @@ namespace psemek::ecs::detail
template <bool With>
struct ordered_component_hasher
{
std::size_t result = 0xb6113227befa663bull;
std::uint64_t result = 0xb6113227befa663bull;
void operator()(util::uuid const & uuid)
{
@ -22,7 +22,7 @@ namespace psemek::ecs::detail
};
template <bool With, typename UUIDs>
std::size_t ordered_component_hash(UUIDs const & uuids)
std::uint64_t ordered_component_hash(UUIDs const & uuids)
{
ordered_component_hasher<With> hasher;
for (auto const & uuid : uuids)
@ -31,7 +31,7 @@ namespace psemek::ecs::detail
}
template <typename WithUUIDs, typename WithoutUUIDs>
std::size_t ordered_component_hash(WithUUIDs const & with_uuids, WithoutUUIDs const & without_uuids)
std::uint64_t ordered_component_hash(WithUUIDs const & with_uuids, WithoutUUIDs const & without_uuids)
{
return ordered_component_hash<true>(with_uuids) ^ ordered_component_hash<false>(without_uuids);
}

View file

@ -9,12 +9,12 @@ namespace psemek::ecs::detail
struct table_hashset_hash
{
std::size_t operator()(util::span<util::uuid const> const & uuids) const
std::uint64_t operator()(util::span<util::uuid const> const & uuids) const
{
return unordered_component_hash<true>(uuids);
}
std::size_t operator()(std::unique_ptr<table> const & table) const
std::uint64_t operator()(std::unique_ptr<table> const & table) const
{
return table->hash();
}

View file

@ -9,7 +9,7 @@ namespace psemek::ecs::detail
template <bool With>
struct unordered_component_hasher
{
std::size_t result = 0xcd5694d2b3f3443eull;
std::uint64_t result = 0xcd5694d2b3f3443eull;
void operator()(util::uuid const & uuid)
{
@ -21,7 +21,7 @@ namespace psemek::ecs::detail
};
template <bool With, typename UUIDs>
std::size_t unordered_component_hash(UUIDs const & uuids)
std::uint64_t unordered_component_hash(UUIDs const & uuids)
{
unordered_component_hasher<With> hasher;
for (auto const & uuid : uuids)
@ -30,7 +30,7 @@ namespace psemek::ecs::detail
}
template <typename WithUUIDs, typename WithoutUUIDs>
std::size_t unordered_component_hash(WithUUIDs const & with_uuids, WithoutUUIDs const & without_uuids)
std::uint64_t unordered_component_hash(WithUUIDs const & with_uuids, WithoutUUIDs const & without_uuids)
{
return unordered_component_hash<true>(with_uuids) ^ unordered_component_hash<false>(without_uuids);
}

View file

@ -40,9 +40,9 @@ namespace std
template <>
struct hash<::psemek::ecs::handle>
{
size_t operator()(::psemek::ecs::handle const & h) const noexcept
uint64_t operator()(::psemek::ecs::handle const & h) const noexcept
{
return (static_cast<size_t>(h.id) << 32) | h.epoch;
return (static_cast<uint64_t>(h.id) << 32) | h.epoch;
}
};

View file

@ -7,8 +7,7 @@
#include <psemek/math/matrix.hpp>
#include <psemek/math/interval.hpp>
#include <psemek/math/quaternion.hpp>
#include <boost/container/flat_map.hpp>
#include <psemek/util/hash_table.hpp>
#include <string_view>
@ -121,8 +120,8 @@ namespace psemek::gfx
private:
GLuint program_ = 0;
mutable boost::container::flat_map<std::string, GLint, std::less<>> uniforms_;
mutable boost::container::flat_map<std::string, GLuint, std::less<>> uniform_blocks_;
mutable util::hash_map<std::string, GLint, util::any_hash, std::equal_to<>> uniforms_;
mutable util::hash_map<std::string, GLuint, util::any_hash, std::equal_to<>> uniform_blocks_;
program(std::nullptr_t)
{}

View file

@ -222,10 +222,10 @@ namespace std
template <typename T, std::size_t N>
struct hash<::psemek::math::point<T, N>>
{
std::size_t operator()(::psemek::math::point<T, N> const & v) const noexcept
std::uint64_t operator()(::psemek::math::point<T, N> const & v) const noexcept
{
std::hash<T> h;
std::size_t r = 0;
hash<T> h;
std::uint64_t r = 0;
for (std::size_t i = 0; i < N; ++i)
::psemek::util::hash_combine(r, h(v[i]));
return r;

View file

@ -139,9 +139,9 @@ namespace std
template <typename Point, std::size_t K>
struct hash<::psemek::math::simplex<Point, K>>
{
std::size_t operator()(::psemek::math::simplex<Point, K> const & simplex) const
std::uint64_t operator()(::psemek::math::simplex<Point, K> const & simplex) const
{
std::size_t result = 0;
std::uint64_t result = 0;
std::hash<Point> h;
for (auto const & p : simplex.points)
::psemek::util::hash_combine(result, h(p));

View file

@ -472,10 +472,10 @@ namespace std
template <typename T, std::size_t N>
struct hash<::psemek::math::vector<T, N>>
{
std::size_t operator()(::psemek::math::vector<T, N> const & v) const noexcept
std::uint64_t operator()(::psemek::math::vector<T, N> const & v) const noexcept
{
std::hash<T> h;
std::size_t r = 0;
std::uint64_t r = 0;
for (std::size_t i = 0; i < N; ++i)
::psemek::util::hash_combine(r, h(v[i]));
return r;

View file

@ -19,14 +19,14 @@ namespace psemek::sir
static_assert(alignof(std::uint8_t) == 1);
static_assert(alignof(std::uint16_t) == 2);
static_assert(alignof(std::uint32_t) == 4);
static_assert(alignof(std::uint64_t) == 8);
static_assert(alignof(std::uint64_t) <= 8);
static_assert(alignof(std::int8_t) == 1);
static_assert(alignof(std::int16_t) == 2);
static_assert(alignof(std::int32_t) == 4);
static_assert(alignof(std::int64_t) == 8);
static_assert(alignof(std::int64_t) <= 8);
static_assert(sizeof(float) == 4);
static_assert(alignof(float) == 4);
static_assert(sizeof(double) == 8);
static_assert(alignof(double) == 8);
static_assert(alignof(double) <= 8);
}

View file

@ -0,0 +1,197 @@
#pragma once
#include <psemek/util/span.hpp>
#include <psemek/util/range.hpp>
#include <cstdint>
#include <cstddef>
#include <memory>
#include <concepts>
namespace psemek::util
{
struct big_int
{
using digit = std::uint32_t;
big_int() = default;
template <std::integral T>
big_int(T value);
big_int(big_int const &) = delete;
big_int(big_int && other);
static big_int from_digits(std::unique_ptr<digit[]> digits, std::uint32_t size, bool negative = false);
template <typename Iterator>
static big_int from_digits(Iterator begin, Iterator end, bool negative = false);
template <typename Container>
static big_int from_digits(Container && container, bool negative = false);
big_int & operator = (big_int const &) = delete;
big_int & operator = (big_int && other);
util::span<digit> digits();
util::span<digit const> digits() const;
bool negative() const;
private:
static constexpr std::uint32_t sign_bit = std::uint32_t(1) << 31;
static constexpr std::uint32_t digit_size = sizeof(digit);
static constexpr std::uint32_t digit_bits = digit_size * 8;
static constexpr std::uint32_t digit_mask = digit(-1);
std::unique_ptr<digit[]> digits_ = nullptr;
// Highest bit of size stores the overall big int sign
std::uint32_t size_ = 0;
std::uint32_t capacity_ = 0;
void set_negative(bool negative)
{
size_ = size() | (negative ? sign_bit : 0);
}
std::uint32_t size() const
{
return size_ & (~sign_bit);
}
void set_size(std::uint32_t new_size)
{
size_ = new_size | (size_ & sign_bit);
}
void canonicalize();
void ensure_capacity(std::uint32_t new_capacity);
template <std::integral T>
static T shift_by_digit(T value);
template <std::integral T>
static std::uint32_t size_for(T value);
void add_positive(span<digit const> digits);
};
template <std::integral T>
big_int::big_int(T value)
{
auto size = size_for(value);
ensure_capacity(size);
set_size(size);
set_negative(value < 0);
for (digit & d : digits())
{
d = value & digit_mask;
value = shift_by_digit(value);
}
if (negative())
{
for (digit & d : digits())
d = ~d;
digit const one = 1;
add_positive(make_singleton_span(one));
}
}
inline big_int::big_int(big_int && other)
: digits_(std::move(other.digits_))
, size_(other.size_)
, capacity_(other.capacity_)
{
other.size_ = 0;
other.capacity_ = 0;
}
inline big_int big_int::from_digits(std::unique_ptr<digit[]> digits, std::uint32_t size, bool negative)
{
big_int result;
result.digits_ = std::move(digits);
result.size_ = size;
result.capacity_ = size;
result.set_negative(negative);
result.canonicalize();
return result;
}
template <typename Iterator>
big_int big_int::from_digits(Iterator begin, Iterator end, bool negative)
{
auto size = std::distance(begin, end);
auto digits = std::make_unique<digit[]>(size);
std::copy(begin, end, digits.get());
return from_digits(std::move(digits), size, negative);
}
template <typename Container>
big_int big_int::from_digits(Container && container, bool negative)
{
return from_digits(util::xbegin(container), util::xend(container), negative);
}
inline util::span<big_int::digit> big_int::digits()
{
return {digits_.get(), size()};
}
inline util::span<big_int::digit const> big_int::digits() const
{
return {digits_.get(), size()};
}
inline bool big_int::negative() const
{
return (size_ & sign_bit) == sign_bit;
}
template <std::integral T>
T big_int::shift_by_digit(T value)
{
if constexpr (sizeof(value) <= digit_size)
{
if (value < 0)
return -1;
else
return 0;
}
else
{
return value >> digit_bits;
}
}
template <std::integral T>
std::uint32_t big_int::size_for(T value)
{
std::uint32_t result_bits = 0;
if (value >= 0)
{
while (value > 0)
{
value = shift_by_digit(value);
result_bits += digit_bits;
}
}
else
{
result_bits = 1;
while ((value >> (digit_bits - 1)) != T(-1))
{
value = shift_by_digit(value);
result_bits += digit_bits;
}
}
return (result_bits + (digit_bits - 1)) / digit_bits;
}
}

View file

@ -97,9 +97,9 @@ namespace std
template <>
struct hash<::psemek::util::dynamic_bitset>
{
std::size_t operator()(::psemek::util::dynamic_bitset const & b) const
std::uint64_t operator()(::psemek::util::dynamic_bitset const & b) const
{
std::size_t result = 0x23d2ef655094f9aeull;
std::uint64_t result = static_cast<std::size_t>(0x23d2ef655094f9aeull);
for (auto value : b.storage())
::psemek::util::hash_combine(result, value);
return result;

View file

@ -962,7 +962,7 @@ namespace std
template <>
struct hash<::psemek::util::ecs::species_handle>
{
std::size_t operator()(::psemek::util::ecs::species_handle h) const
std::uint64_t operator()(::psemek::util::ecs::species_handle h) const
{
return std::hash<::psemek::util::ecs_detail::species_handle>()(h.value);
}
@ -971,7 +971,7 @@ namespace std
template <>
struct hash<::psemek::util::ecs::entity_handle>
{
std::size_t operator()(::psemek::util::ecs::entity_handle h) const
std::uint64_t operator()(::psemek::util::ecs::entity_handle h) const
{
return std::hash<::psemek::util::ecs_detail::entity_handle>()(h.value);
}

View file

@ -2,22 +2,23 @@
#include <functional>
#include <tuple>
#include <cstdint>
namespace psemek::util
{
constexpr void hash_combine(std::size_t & seed, std::size_t value)
constexpr void hash_combine(std::uint64_t & seed, std::uint64_t value)
{
constexpr std::size_t k = 0x9ddfea08eb382d69ULL;
std::size_t a = (value ^ seed) * k;
constexpr std::uint64_t k = 0x9ddfea08eb382d69ULL;
std::uint64_t a = (value ^ seed) * k;
a ^= (a >> 47);
std::size_t b = (seed ^ a) * k;
std::uint64_t b = (seed ^ a) * k;
b ^= (b >> 47);
seed = b * k;
}
template <typename Iterator, typename Hash = std::hash<std::decay_t<decltype(*std::declval<Iterator>())>>>
constexpr void hash_sequence(std::size_t & seed, Iterator begin, Iterator end, Hash hash = Hash{})
constexpr void hash_sequence(std::uint64_t & seed, Iterator begin, Iterator end, Hash hash = Hash{})
{
for (; begin != end; ++begin)
{
@ -30,17 +31,17 @@ namespace psemek::util
struct is_transparent{};
template <typename T>
std::size_t operator() (T const & x) const
std::uint64_t operator() (T const & x) const
{
return std::hash<T>{}(x);
}
};
template <typename ... T>
constexpr std::size_t hash_all(T const & ... x)
constexpr std::uint64_t hash_all(T const & ... x)
{
any_hash hash;
std::size_t seed = 0;
std::uint64_t seed = 0;
(hash_combine(seed, hash(x)), ...);
return seed;
}
@ -54,9 +55,9 @@ namespace std
struct hash<std::pair<T, H>>
: std::pair<std::hash<T>, std::hash<H>>
{
std::size_t operator()(std::pair<T, H> const & x) const
std::uint64_t operator()(std::pair<T, H> const & x) const
{
std::size_t seed = 0;
std::uint64_t seed = 0;
::psemek::util::hash_combine(seed, this->first(x.first));
::psemek::util::hash_combine(seed, this->second(x.second));
return seed;
@ -67,16 +68,16 @@ namespace std
struct hash<std::tuple<Ts...>>
: std::tuple<std::hash<Ts>...>
{
std::size_t operator()(std::tuple<Ts...> const & t) const
std::uint64_t operator()(std::tuple<Ts...> const & t) const
{
return hash_impl(t, std::make_index_sequence<sizeof...(Ts)>{});
}
private:
template <std::size_t ... Is>
std::size_t hash_impl(std::tuple<Ts...> const & t, std::index_sequence<Is...>) const
std::uint64_t hash_impl(std::tuple<Ts...> const & t, std::index_sequence<Is...>) const
{
std::size_t result = 0;
std::uint64_t result = 0;
(::psemek::util::hash_combine(result, std::get<Is>(*this)(std::get<Is>(t))), ...);
return result;
}

View file

@ -13,14 +13,14 @@ namespace psemek::util
namespace detail
{
constexpr std::size_t stored_value_mask = 1ull << 63;
constexpr std::size_t tombstone_mask = 1ull << 62;
constexpr std::size_t hash_value_mask = ~(stored_value_mask | tombstone_mask);
constexpr std::uint64_t stored_value_mask = 1ull << 63;
constexpr std::uint64_t tombstone_mask = 1ull << 62;
constexpr std::uint64_t hash_value_mask = ~(stored_value_mask | tombstone_mask);
template <typename T>
struct hash_table_entry
{
std::size_t hash = 0;
std::uint64_t hash = 0;
alignas(T) char storage[sizeof(T)] = {0};
bool has_value() const
@ -44,13 +44,13 @@ namespace psemek::util
}
template <typename H>
void set_value(H && value, std::size_t hash)
void set_value(H && value, std::uint64_t hash)
{
new (storage_ptr()) T{std::forward<H>(value)};
this->hash = (hash & hash_value_mask) | stored_value_mask;
}
bool hash_equal(std::size_t hash) const
bool hash_equal(std::uint64_t hash) const
{
return (hash & hash_value_mask) == (this->hash & hash_value_mask);
}
@ -211,7 +211,7 @@ namespace psemek::util
std::pair<hash_table_iterator<T>, bool> insert(H && value)
{
ensure_capacity_for(size_ + 1);
std::size_t hash = this->hash()(this->key_projector()(value));
std::uint64_t hash = this->hash()(this->key_projector()(value));
return insert_impl(std::forward<H>(value), hash);
}
@ -220,7 +220,7 @@ namespace psemek::util
{
if (size_ == 0)
return end();
std::size_t hash = this->hash()(key);
std::uint64_t hash = this->hash()(key);
return find_impl(key, hash);
}
@ -320,13 +320,13 @@ namespace psemek::util
reallocate(capacity());
}
std::size_t probe_index(std::size_t hash, std::size_t i) const
std::size_t probe_index(std::uint64_t hash, std::size_t i) const
{
return (hash + (i * (i + 1)) / 2) % storage_.capacity;
return (static_cast<std::size_t>(hash) + (i * (i + 1)) / 2) % storage_.capacity;
}
template <typename H>
std::pair<hash_table_iterator<T>, bool> insert_impl(H && value, std::size_t hash)
std::pair<hash_table_iterator<T>, bool> insert_impl(H && value, std::uint64_t hash)
{
std::size_t i = 0;
while (true)
@ -349,7 +349,7 @@ namespace psemek::util
}
template <typename Key>
hash_table_iterator<T> find_impl(Key const & key, std::size_t hash) const
hash_table_iterator<T> find_impl(Key const & key, std::uint64_t hash) const
{
std::size_t i = 0;
while (true)

View file

@ -3,6 +3,7 @@
#include <string>
#include <string_view>
#include <functional>
#include <cstdint>
namespace psemek::util
{
@ -87,7 +88,7 @@ namespace std
template <>
struct hash<::psemek::util::hstring>
{
std::size_t operator()(::psemek::util::hstring const & str) const
std::uint64_t operator()(::psemek::util::hstring const & str) const
{
return std::hash<std::string_view>()(str.view());
}

View file

@ -2,7 +2,6 @@
#include <psemek/util/hash.hpp>
#include <array>
#include <cstdint>
#include <string_view>
#include <iostream>
@ -29,7 +28,7 @@ namespace psemek::util
};
static_assert(sizeof(uuid) == 16);
static_assert(alignof(uuid) == 8);
static_assert(alignof(uuid) <= 8);
constexpr bool is_rfc_4122(uuid const & uuid)
{
@ -72,9 +71,9 @@ namespace std
template <>
struct hash<::psemek::util::uuid>
{
std::size_t operator()(::psemek::util::uuid const & uuid) const noexcept
std::uint64_t operator()(::psemek::util::uuid const & uuid) const noexcept
{
std::size_t result = uuid[0];
std::uint64_t result = uuid[0];
::psemek::util::hash_combine(result, uuid[1]);
return result;
}

View file

@ -0,0 +1,110 @@
#include <psemek/util/big_int.hpp>
#include <algorithm>
namespace psemek::util
{
namespace
{
struct adder
{
std::uint32_t sum = 0;
std::uint32_t carry = 0;
void add(std::uint32_t x)
{
auto result = static_cast<std::uint64_t>(sum) + x;
sum = result & std::uint32_t(-1);
carry += result >> 32;
}
};
}
void big_int::canonicalize()
{
auto size = this->size();
auto negative = this->negative();
while (size > 0 && digits_[size - 1] == 0)
--size;
set_size(size);
set_negative(size > 0 && negative);
}
void big_int::ensure_capacity(std::uint32_t new_capacity)
{
if (new_capacity == 0)
{
digits_ = nullptr;
size_ = 0;
capacity_ = 0;
}
else if (new_capacity > capacity_)
{
new_capacity = std::max({static_cast<std::uint32_t>(1.6 * size()), static_cast<std::uint32_t>(16), new_capacity});
auto new_digits = std::make_unique<digit[]>(new_capacity);
std::size_t i = 0;
for (; i < size(); ++i)
new_digits[i] = digits_[i];
for (; i < new_capacity; ++i)
new_digits[i] = 0;
digits_ = std::move(new_digits);
capacity_ = new_capacity;
}
}
void big_int::add_positive(span<digit const> digits)
{
digit carry = 0;
std::size_t i = 0;
std::size_t min_size = std::min<std::size_t>(size(), digits.size());
for (; i < min_size; ++i)
{
adder adder{digits_[i]};
adder.add(digits[i]);
adder.add(carry);
std::tie(digits_[i], carry) = {adder.sum, adder.carry};
}
if (i < digits.size())
{
ensure_capacity(digits.size());
for (; i < digits.size(); ++i)
{
adder adder{digits[i]};
adder.add(carry);
std::tie(digits_[i], carry) = {adder.sum, adder.carry};
}
set_size(digits.size());
}
if (i < size())
{
for (; i < size() && carry != 0; ++i)
{
adder adder{digits_[i]};
adder.add(carry);
std::tie(digits_[i], carry) = {adder.sum, adder.carry};
}
}
if (carry != 0)
{
ensure_capacity(size() + 1);
digits_[size()] = carry;
set_size(size() + 1);
}
canonicalize();
}
}

179
libs/util/tests/big_int.cpp Normal file
View file

@ -0,0 +1,179 @@
#include <psemek/test/test.hpp>
#include <psemek/util/big_int.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform.hpp>
using namespace psemek;
using namespace psemek::util;
namespace
{
void check_digits(big_int const & bi, std::vector<big_int::digit> const & digits_expected)
{
span<big_int::digit const> digits_actual = bi.digits();
expect_equal(digits_actual.size(), digits_expected.size());
for (std::size_t i = 0; i < digits_actual.size(); ++i)
expect_equal(digits_actual[i], digits_expected[i]);
}
}
test_case(util_big__int_init_small)
{
{
big_int x;
expect(!x.negative());
check_digits(x, {});
}
{
big_int x(1);
expect(!x.negative());
check_digits(x, {1});
}
{
big_int x(173);
expect(!x.negative());
check_digits(x, {173});
}
{
big_int x(1u << 31);
expect(!x.negative());
check_digits(x, {1u << 31});
}
{
big_int x(1ull << 32);
expect(!x.negative());
check_digits(x, {0, 1});
}
{
big_int x(1ull << 63);
expect(!x.negative());
check_digits(x, {0, 1u << 31});
}
{
big_int x(static_cast<std::uint64_t>(-1));
expect(!x.negative());
check_digits(x, {-1, -1});
}
{
big_int x(-1);
expect(x.negative());
check_digits(x, {1});
}
{
big_int x(-279);
expect(x.negative());
check_digits(x, {279});
}
{
big_int x((-1) << 31);
expect(x.negative());
check_digits(x, {1u << 31});
}
{
big_int x((-1ll) << 32);
expect(x.negative());
check_digits(x, {0, 1u});
}
{
big_int x((-1ll) << 63);
expect(x.negative());
check_digits(x, {0, 1u << 31});
}
{
big_int x(((-1ll) << 62) + ((-1ll) << 15));
expect(x.negative());
check_digits(x, {1u << 15, 1u << 30});
}
}
test_case(util_big__int_init_from__digits)
{
random::generator rng;
{
big_int x = big_int::from_digits(std::vector<int>{});
expect(!x.negative());
check_digits(x, {});
}
{
big_int x = big_int::from_digits(std::vector{5724});
expect(!x.negative());
check_digits(x, {5724});
}
{
big_int x = big_int::from_digits(std::vector{1234, 5678});
expect(!x.negative());
check_digits(x, {1234, 5678});
}
{
big_int x = big_int::from_digits(std::vector{1234, 5678, 0, 0});
expect(!x.negative());
check_digits(x, {1234, 5678});
}
{
std::vector<big_int::digit> digits(64);
for (auto & digit : digits)
digit = random::uniform<big_int::digit>(rng);
if (digits.back() == 0)
digits.back() = 1;
big_int x = big_int::from_digits(digits);
expect(!x.negative());
check_digits(x, digits);
}
{
big_int x = big_int::from_digits(std::vector<int>{}, true);
expect(!x.negative());
check_digits(x, {});
}
{
big_int x = big_int::from_digits(std::vector{5724}, true);
expect(x.negative());
check_digits(x, {5724});
}
{
big_int x = big_int::from_digits(std::vector{1234, 5678}, true);
expect(x.negative());
check_digits(x, {1234, 5678});
}
{
big_int x = big_int::from_digits(std::vector{1234, 5678, 0, 0}, true);
expect(x.negative());
check_digits(x, {1234, 5678});
}
{
std::vector<big_int::digit> digits(64);
for (auto & digit : digits)
digit = random::uniform<big_int::digit>(rng);
if (digits.back() == 0)
digits.back() = 1;
big_int x = big_int::from_digits(digits, true);
expect(x.negative());
check_digits(x, digits);
}
}

View file

@ -166,7 +166,7 @@ std::optional<std::uint64_t> parse_seed(std::string const & str, int base)
{
try
{
std::uint64_t unused;
std::size_t unused;
return std::stoull(str, &unused, base);
}
catch (...)