Skip to content

Commit

Permalink
Fix broken buffer estimator in to_chars_static
Browse files Browse the repository at this point in the history
- assumed decimal
- sometimes got it wrong
- potentially got it badly wrong
- now uses lookup table to ensure the right number of characters to
  represent in integer of a given number of binary digits
- table generator: https://godbolt.org/z/sYc7v185T
- correctness: ~95%
- probably of causing buffer overrun: < 1%
  • Loading branch information
John McFarlane committed Jan 29, 2022
1 parent fba5a8e commit 5f75b2a
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 23 deletions.
55 changes: 53 additions & 2 deletions include/cnl/_impl/charconv/to_chars_capacity.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,59 @@
#include "../cnl_assert.h"
#include "../numbers/signedness.h"

#include <array>
#include <limits>
#include <numbers>

namespace cnl::_impl {
[[nodiscard]] constexpr auto num_digits_from_binary(int num_digits, int base)
{
CNL_ASSERT(base >= 2);
CNL_ASSERT(base <= 36);

// How many characters are needed to print numbers of this base (minus 2), relative to binary?
constexpr auto integer_chars_factor{std::array<double, 37>{
std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
1.0000000000000000000000000000000000000000000000000000000,
0.63092975357145753001475441124057397246360778808593750000,
0.50000000000000000000000000000000000000000000000000000000,
0.43067655807339311202497356134699657559394836425781250000,
0.38685280723454162910002196440473198890686035156250000000,
0.35620718710802223849754000184475444257259368896484375000,
0.33333333333333342585191871876304503530263900756835937500,
0.31546487678572876500737720562028698623180389404296875000,
0.30102999566398119801746702250966336578130722045898437500,
0.28906482631788787962534570397110655903816223144531250000,
0.27894294565112986994392940687248483300209045410156250000,
0.27023815442731979885593318613246083259582519531250000000,
0.26264953503719362659651892499823588877916336059570312500,
0.25595802480981555415695538613363169133663177490234375000,
0.25000000000000000000000000000000000000000000000000000000,
0.24465054211822603869030956502683693543076515197753906250,
0.23981246656813148820930337024037726223468780517578125000,
0.23540891336663827271280524655594490468502044677734375000,
0.23137821315975920510865648793696891516447067260742187500,
0.22767024869695301481087312822637613862752914428710937500,
0.22424382421757543815132862619066145271062850952148437500,
0.22106472945750377245843765194877050817012786865234375000,
0.21810429198553157870144048047222895547747611999511718750,
0.21533827903669655601248678067349828779697418212890625000,
0.21274605355336317913383936684112995862960815429687500000,
0.21030991785715250075305959853722015395760536193847656250,
0.20801459767650948284867240545281674712896347045898437500,
0.20584683246043447568673911973746726289391517639160156250,
0.20379504709050619282173499868804356083273887634277343750,
0.20184908658209987919462946592830121517181396484375000000,
0.20000000000000001110223024625156540423631668090820312500,
0.19823986317056055406204961855110013857483863830566406250,
0.19656163223282260843483015833044191822409629821777343750,
0.19495902189378633284633224320714361965656280517578125000,
0.19342640361727081455001098220236599445343017578125000000}};

return static_cast<int>(num_digits * integer_chars_factor[base]) + 1;
}

// maximum number of characters necessary to represent given Scalar
// in give base in human-readable text excluding nul terminator,
// e.g. to_chars_capacity<int8_t>{}() == 4 // ["-128".."127"]
Expand All @@ -24,10 +73,12 @@ namespace cnl::_impl {
template<typename Scalar>
requires integer<Scalar>
struct to_chars_capacity<Scalar> {
[[nodiscard]] constexpr auto operator()(int /*base*/ = 10) const
[[nodiscard]] constexpr auto operator()(int base = 10) const
{
static_assert(std::numeric_limits<Scalar>::radix == 2);

auto const sign_chars = static_cast<int>(cnl::numbers::signedness_v<Scalar>);
auto const integer_chars = static_cast<int>(std::numeric_limits<Scalar>::digits * std::numbers::ln2 / std::numbers::ln10) + 1;
auto const integer_chars = num_digits_from_binary(std::numeric_limits<Scalar>::digits, base);
return sign_chars + integer_chars;
}
};
Expand Down
20 changes: 1 addition & 19 deletions include/cnl/_impl/scaled_integer/to_chars_capacity.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,6 @@

/// compositional numeric library
namespace cnl::_impl {
constexpr auto num_digits_from_binary(int num_digits, int radix)
{
switch (radix) {
case 2:
return num_digits;
case 8:
return (num_digits + 2) / 3;
case 10:
return (num_digits * 1000 + 3322) / 3321;
case 16:
return (num_digits + 3) / 4;
default: {
auto const binary_digits_per_digit{used_digits(radix - 1)};
return (num_digits + binary_digits_per_digit - 1) / binary_digits_per_digit;
}
}
}

constexpr auto num_digits_to_binary(int num_digits, int radix)
{
switch (radix) {
Expand Down Expand Up @@ -67,7 +49,7 @@ namespace cnl::_impl {
num_digits_to_binary(std::max(0, exponent), radix)};
auto const num_integer_bits{
num_significant_integer_bits + num_trailing_integer_bits};
auto const integer_chars = num_digits_from_binary(num_integer_bits, 10);
auto const integer_chars = num_digits_from_binary(num_integer_bits, base);
auto const radix_chars = static_cast<int>(fractional_digits > 0);
auto const fractional_chars = std::max(0, fractional_digits);

Expand Down
1 change: 1 addition & 0 deletions test/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ set(test_sources
scaled_int/elastic/make_elastic_scaled_int.cpp
scaled_int/elastic/elastic_scaled_int.cpp
scaled_int/elastic/to_chars_capacity.cpp
scaled_int/elastic/to_chars_static.cpp
scaled_int/rounding/elastic/rounding_elastic_scaled_int.cpp
scaled_int/overflow/elastic/int.cpp
static_int/shift_left.cpp
Expand Down
2 changes: 1 addition & 1 deletion test/unit/elastic_int/elastic_int.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,7 @@ namespace {

static_assert(
identical(
cnl::to_chars_static_result<5>{{'3', 'e', '8', 0}, 3},
cnl::to_chars_static_result<4>{{'3', 'e', '8', 0}, 3},
cnl::to_chars_static<16>(cnl::make_elastic_integer(1000_c))));
#endif

Expand Down
2 changes: 1 addition & 1 deletion test/unit/scaled_int/elastic/elastic_scaled_int.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ TEST(elastic_scaled_integer, to_string_thousand) // NOLINT
TEST(elastic_scaled_integer, to_string_thousandth) // NOLINT
{
auto const n{.001_cnl};
static_assert(5 == cnl::_impl::to_chars_capacity<std::remove_cvref_t<decltype(n)>>{}());
static_assert(6 == cnl::_impl::to_chars_capacity<std::remove_cvref_t<decltype(n)>>{}());

auto const expected{".001"};
auto const actual{cnl::to_string(n)};
Expand Down
61 changes: 61 additions & 0 deletions test/unit/scaled_int/elastic/to_chars_static.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

// Copyright John McFarlane 2022.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file ../LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <cnl/_impl/charconv/to_chars.h>

#include <cnl/elastic_scaled_integer.h>

#include <cnl/_impl/type_traits/identical.h>

#include <gtest/gtest.h>

namespace {
using cnl::_impl::identical;

TEST(to_chars_static, elastic_scaled_123_456) // NOLINT
{
using namespace cnl::literals;
constexpr auto expected{cnl::to_chars_static_result<10>{{'1', '2', '3', '.', '4', '5', '6'}, 7}};
constexpr auto actual{cnl::to_chars_static(123.456_cnl)};
#if !defined(_MSC_VER)
static_assert(identical(expected, actual));
#endif
ASSERT_EQ(expected, actual);
}

TEST(to_chars_static, elastic_scaled_12345_6) // NOLINT
{
using namespace cnl::literals;
constexpr auto expected{cnl::to_chars_static_result<8>{{'1', '2', '3', '4', '5', '.', '6'}, 7}};
constexpr auto actual{cnl::to_chars_static(12345.6_cnl)};
#if !defined(_MSC_VER)
static_assert(identical(expected, actual));
#endif
ASSERT_EQ(expected, actual);
}

TEST(to_chars_static, elastic_scaled_123456_) // NOLINT
{
using namespace cnl::literals;
constexpr auto expected{cnl::to_chars_static_result<7>{{'1', '2', '3', '4', '5', '6'}, 6}};
constexpr auto actual{cnl::to_chars_static(123456._cnl)};
#if !defined(_MSC_VER)
static_assert(identical(expected, actual));
#endif
ASSERT_EQ(expected, actual);
}

TEST(to_chars_static, elastic_scaled_n123456_)
{
using namespace cnl::literals;
constexpr auto expected{cnl::to_chars_static_result<7>{{'-', '1', '2', '3', '4', '5', '6'}, 7}};
constexpr auto actual{cnl::to_chars_static(-123456._cnl)};
#if !defined(_MSC_VER)
static_assert(identical(expected, actual));
#endif
ASSERT_EQ(expected, actual);
}
}

0 comments on commit 5f75b2a

Please sign in to comment.