Skip to content

Commit afc7f3f

Browse files
committed
Initial commit log10 but more tests needed
1 parent 23f5439 commit afc7f3f

File tree

3 files changed

+297
-14
lines changed

3 files changed

+297
-14
lines changed

include/boost/decimal/detail/cmath/log10.hpp

Lines changed: 96 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
#define BOOST_DECIMAL_DETAIL_CMATH_LOG10_HPP
88

99
#include <boost/decimal/fwd.hpp> // NOLINT(llvm-include-order)
10-
#include <boost/decimal/detail/type_traits.hpp>
10+
#include <boost/decimal/detail/cmath/impl/log_impl.hpp>
1111
#include <boost/decimal/detail/concepts.hpp>
1212
#include <boost/decimal/detail/config.hpp>
13+
#include <boost/decimal/detail/type_traits.hpp>
1314
#include <boost/decimal/numbers.hpp>
1415

1516
#ifndef BOOST_DECIMAL_BUILD_MODULE
@@ -26,21 +27,102 @@ template <typename T>
2627
constexpr auto log10_impl(T x) noexcept
2728
BOOST_DECIMAL_REQUIRES(detail::is_decimal_floating_point_v, T)
2829
{
29-
// TODO(ckormanyos) Actually this is eintirely preliminary. The implementation
30-
// of log10 will/should-be entirely re-worked with specific base-10-relevant details.
31-
32-
// TODO(ckormanyos) Handle non-normal varguments.
33-
34-
// TODO(ckormanyos) Put in a basic check for pure powers of 10, resulting
35-
// in an exact result.
36-
37-
// Starting point: See implementation of log() and adapt to the following series.
38-
// Series[Log[10] Log[10, (1 + (z/2))/(1 - (z/2))], {z, 0, 17}]
39-
40-
return log(x) / numbers::ln10_v<T>;
30+
constexpr T zero { 0, 0 };
31+
constexpr T one { 1, 0 };
32+
33+
T result { };
34+
35+
if (isnan(x))
36+
{
37+
result = x;
38+
}
39+
else if (isinf(x))
40+
{
41+
result = (!signbit(x)) ? x: std::numeric_limits<T>::quiet_NaN();
42+
}
43+
else if (x < one)
44+
{
45+
// Handle reflection, the [+/-] zero-pole, and non-pole, negative x.
46+
if (x > zero)
47+
{
48+
result = -log10(one / x);
49+
}
50+
else if ((x == zero) || (-x == zero))
51+
{
52+
// Actually, this should be equivalent to -HUGE_VAL.
53+
54+
result = -std::numeric_limits<T>::infinity();
55+
}
56+
else
57+
{
58+
result = std::numeric_limits<T>::quiet_NaN();
59+
}
60+
}
61+
else if(x > one)
62+
{
63+
// The algorithm for base-10 logarithm is based on Chapter 5, pages 35-36
64+
// of Cody and Waite, Software Manual for the Elementary Functions,
65+
// Prentice Hall, 1980.
66+
67+
int exp10val { };
68+
69+
T g { frexp10(x, &exp10val) };
70+
71+
while(g > one)
72+
{
73+
// TODO(ckormanyos) Do we really have to multiply individual powers
74+
// of 10 here? Or is there a reliable way to simply use pow10()?
75+
// For instance count intagral orders of exp10val and divide by pow10().
76+
77+
g /= 10;
78+
79+
++exp10val;
80+
}
81+
82+
if(g == one)
83+
{
84+
result = T { exp10val };
85+
}
86+
else
87+
{
88+
constexpr T inv_sqrt10 { UINT64_C(3162277660168379332), -19 };
89+
90+
const bool reduce_sqrt10 { g < inv_sqrt10 };
91+
92+
if (reduce_sqrt10)
93+
{
94+
constexpr T sqrt10 { UINT64_C(3162277660168379332), -18 };
95+
96+
g *= sqrt10;
97+
}
98+
99+
const T s { (g - one) / (g + one) };
100+
const T z { s + s };
101+
const T zsq { z * z };
102+
103+
result = z * fma(detail::log_series_expansion(zsq), zsq, one);
104+
105+
result /= numbers::ln10_v<T>;
106+
107+
if(reduce_sqrt10)
108+
{
109+
constexpr T half { 5, -1 };
110+
111+
result -= half;
112+
}
113+
114+
result += static_cast<T>(exp10val);
115+
}
116+
}
117+
else
118+
{
119+
result = zero;
120+
}
121+
122+
return result;
41123
}
42124

43-
} // namespace detail
125+
} //namespace detail
44126

45127
BOOST_DECIMAL_EXPORT template <typename T>
46128
constexpr auto log10(T x) noexcept

test/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ run test_legendre.cpp ;
106106
run test_literals.cpp ;
107107
run test_lgamma.cpp ;
108108
run test_log.cpp ;
109+
run test_log10.cpp ;
109110
run test_log1p.cpp ;
110111
run test_pow.cpp ;
111112
run test_promotion.cpp ;

test/test_log10.cpp

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Copyright 2024 Matt Borland
2+
// Copyright 2024 Christopher Kormanyos
3+
// Distributed under the Boost Software License, Version 1.0.
4+
// https://www.boost.org/LICENSE_1_0.txt
5+
6+
#include <chrono>
7+
#include <iomanip>
8+
#include <iostream>
9+
#include <limits>
10+
#include <random>
11+
#include <string>
12+
13+
#include <boost/decimal.hpp>
14+
15+
#if defined(__clang__)
16+
# pragma clang diagnostic push
17+
# pragma clang diagnostic ignored "-Wfloat-equal"
18+
#elif defined(__GNUC__)
19+
# pragma GCC diagnostic push
20+
# pragma GCC diagnostic ignored "-Wfloat-equal"
21+
#endif
22+
23+
#include <boost/core/lightweight_test.hpp>
24+
25+
namespace local
26+
{
27+
template<typename IntegralTimePointType,
28+
typename ClockType = std::chrono::high_resolution_clock>
29+
auto time_point() noexcept -> IntegralTimePointType
30+
{
31+
using local_integral_time_point_type = IntegralTimePointType;
32+
using local_clock_type = ClockType;
33+
34+
const auto current_now =
35+
static_cast<std::uintmax_t>
36+
(
37+
std::chrono::duration_cast<std::chrono::nanoseconds>
38+
(
39+
local_clock_type::now().time_since_epoch()
40+
).count()
41+
);
42+
43+
return static_cast<local_integral_time_point_type>(current_now);
44+
}
45+
46+
template<typename NumericType>
47+
auto is_close_fraction(const NumericType& a,
48+
const NumericType& b,
49+
const NumericType& tol) noexcept -> bool
50+
{
51+
using std::fabs;
52+
53+
auto result_is_ok = bool { };
54+
55+
NumericType delta { };
56+
57+
if(b == static_cast<NumericType>(0))
58+
{
59+
delta = fabs(a - b); // LCOV_EXCL_LINE
60+
61+
result_is_ok = (delta < tol); // LCOV_EXCL_LINE
62+
}
63+
else
64+
{
65+
delta = fabs(1 - (a / b));
66+
67+
result_is_ok = (delta < tol);
68+
}
69+
70+
// LCOV_EXCL_START
71+
if (!result_is_ok)
72+
{
73+
std::cerr << std::setprecision(std::numeric_limits<NumericType>::digits10) << "a: " << a
74+
<< "\nb: " << b
75+
<< "\ndelta: " << delta
76+
<< "\ntol: " << tol << std::endl;
77+
}
78+
// LCOV_EXCL_STOP
79+
80+
return result_is_ok;
81+
}
82+
83+
template<typename DecimalType, typename FloatType>
84+
auto test_log10(const int tol_factor) -> bool
85+
{
86+
using decimal_type = DecimalType;
87+
using float_type = FloatType;
88+
89+
std::random_device rd;
90+
std::mt19937_64 gen(rd());
91+
92+
gen.seed(time_point<typename std::mt19937_64::result_type>());
93+
94+
auto dis_r =
95+
std::uniform_real_distribution<float_type>
96+
{
97+
static_cast<float_type>(1.2L),
98+
static_cast<float_type>(8.9L)
99+
};
100+
101+
bool result_is_ok { true };
102+
103+
auto trials = static_cast<std::uint32_t>(UINT8_C(0));
104+
105+
#if !defined(BOOST_DECIMAL_REDUCE_TEST_DEPTH)
106+
constexpr auto count = (sizeof(decimal_type) == static_cast<std::size_t>(UINT8_C(4))) ? static_cast<std::uint32_t>(UINT32_C(0x200)) : static_cast<std::uint32_t>(UINT32_C(0x40));
107+
#else
108+
constexpr auto count = (sizeof(decimal_type) == static_cast<std::size_t>(UINT8_C(4))) ? static_cast<std::uint32_t>(UINT32_C(0x40)) : static_cast<std::uint32_t>(UINT32_C(0x4));
109+
#endif
110+
111+
for( ; trials < count; ++trials)
112+
{
113+
auto x_flt = dis_r(gen);
114+
115+
auto dis_n =
116+
std::uniform_int_distribution<int>
117+
{
118+
static_cast<int>(INT8_C(-17)),
119+
static_cast<int>(INT8_C(17))
120+
};
121+
122+
std::string str_e { "1.0E" + std::to_string(dis_n(gen)) };
123+
124+
char* p_end;
125+
126+
x_flt *= static_cast<float>(strtold(str_e.c_str(), &p_end));
127+
128+
static_cast<void>(p_end);
129+
130+
const auto x_dec = static_cast<decimal_type>(x_flt);
131+
132+
using std::log10;
133+
134+
const auto val_flt = log10(x_flt);
135+
const auto val_dec = log10(x_dec);
136+
137+
const auto result_log_is_ok = is_close_fraction(val_flt, static_cast<float_type>(val_dec), std::numeric_limits<float_type>::epsilon() * static_cast<float_type>(tol_factor));
138+
139+
result_is_ok = (result_log_is_ok && result_is_ok);
140+
141+
if(!result_log_is_ok)
142+
{
143+
// LCOV_EXCL_START
144+
std::cerr << "x_flt : " << std::scientific << std::setprecision(std::numeric_limits<float_type>::digits10) << x_flt << std::endl;
145+
std::cerr << "val_flt: " << std::scientific << std::setprecision(std::numeric_limits<float_type>::digits10) << val_flt << std::endl;
146+
std::cerr << "val_dec: " << std::scientific << std::setprecision(std::numeric_limits<float_type>::digits10) << val_dec << std::endl;
147+
148+
break;
149+
// LCOV_EXCL_STOP
150+
}
151+
}
152+
153+
BOOST_TEST(result_is_ok);
154+
155+
return result_is_ok;
156+
}
157+
158+
template<typename DecimalType, typename FloatType>
159+
auto test_log10_pow10(const int tol_factor) -> bool
160+
{
161+
using decimal_type = DecimalType;
162+
using float_type = FloatType;
163+
164+
bool result_is_ok { true };
165+
166+
for(int i = static_cast<int>(INT8_C(-23)); i <= static_cast<int>(INT8_C(23)); ++i)
167+
{
168+
const decimal_type x_arg { 1, i };
169+
170+
const decimal_type log_val_p10 = log10(x_arg);
171+
172+
const auto result_log10_pow10_is_ok = (static_cast<float_type>(log_val_p10) == static_cast<float_type>(i));
173+
174+
result_is_ok = (result_log10_pow10_is_ok && result_is_ok);
175+
}
176+
177+
BOOST_TEST(result_is_ok);
178+
179+
return result_is_ok;
180+
}
181+
182+
} // namespace local
183+
184+
auto main() -> int
185+
{
186+
auto result_is_ok = true;
187+
188+
{
189+
using decimal_type = boost::decimal::decimal32;
190+
using float_type = float;
191+
192+
const auto test_log10_is_ok = local::test_log10<decimal_type, float_type>(128);
193+
194+
result_is_ok = (test_log10_is_ok && result_is_ok);
195+
}
196+
197+
result_is_ok = ((boost::report_errors() == 0) && result_is_ok);
198+
199+
return (result_is_ok ? 0 : -1);
200+
}

0 commit comments

Comments
 (0)