835 changes: 835 additions & 0 deletions libc/src/math/generic/log.cpp

Large diffs are not rendered by default.

1,434 changes: 657 additions & 777 deletions libc/src/math/generic/log10.cpp

Large diffs are not rendered by default.

91 changes: 91 additions & 0 deletions libc/src/math/generic/log_range_reduction.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//===-- Extra range reduction steps for accurate pass of logarithms -------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_MATH_GENERIC_LOG_RANGE_REDUCTION_H
#define LLVM_LIBC_SRC_MATH_GENERIC_LOG_RANGE_REDUCTION_H

#include "common_constants.h"
#include "src/__support/FPUtil/dyadic_float.h"

namespace __llvm_libc {

// Struct to store -log*(r) for 4 range reduction steps.
struct LogRR {
fputil::DyadicFloat<128> step_1[128];
fputil::DyadicFloat<128> step_2[193];
fputil::DyadicFloat<128> step_3[161];
fputil::DyadicFloat<128> step_4[130];
};

// Perform logarithm range reduction steps 2-4.
// Inputs from the first step of range reduction:
// m_x : the reduced argument after the first step of range reduction
// satisfying -2^-8 <= m_x < 2^-7 and ulp(m_x) >= 2^-60.
// idx1: index of the -log(r1) table from the first step.
// Outputs of the extra range reduction steps:
// sum: adding -log(r1) - log(r2) - log(r3) - log(r4) to the resulted sum.
// return value: the reduced argument v satisfying:
// -0x1.0002143p-29 <= v < 0x1p-29, and ulp(v) >= 2^(-125).
LIBC_INLINE fputil::DyadicFloat<128>
log_range_reduction(double m_x, const LogRR &log_table,
fputil::DyadicFloat<128> &sum) {
using Float128 = typename fputil::DyadicFloat<128>;
using MType = typename Float128::MantissaType;

int64_t v = static_cast<int64_t>(m_x * 0x1.0p60); // ulp = 2^-60

// Range reduction - Step 2
// Output range: vv2 in [-0x1.3ffcp-15, 0x1.3e3dp-15].
// idx2 = trunc(2^14 * (v + 2^-8 + 2^-15))
size_t idx2 = static_cast<size_t>((v + 0x10'2000'0000'0000) >> 46);
sum = fputil::quick_add(sum, log_table.step_2[idx2]);

int64_t s2 = static_cast<int64_t>(S2[idx2]); // |s| <= 2^-7, ulp = 2^-16
int64_t sv2 = s2 * v; // |s*v| < 2^-14, ulp = 2^(-60-16) = 2^-76
int64_t spv2 = (s2 << 44) + v; // |s + v| < 2^-14, ulp = 2^-60
int64_t vv2 = (spv2 << 16) + sv2; // |vv2| < 2^-14, ulp = 2^-76

// Range reduction - Step 3
// Output range: vv3 in [-0x1.01928p-22 , 0x1p-22]
// idx3 = trunc(2^21 * (v + 80*2^-21 + 2^-22))
size_t idx3 = static_cast<size_t>((vv2 + 0x2840'0000'0000'0000) >> 55);
sum = fputil::quick_add(sum, log_table.step_3[idx3]);

int64_t s3 = static_cast<int64_t>(S3[idx3]); // |s| < 2^-13, ulp = 2^-21
int64_t spv3 = (s3 << 55) + vv2; // |s + v| < 2^-21, ulp = 2^-76
// |s*v| < 2^-27, ulp = 2^(-76-21) = 2^-97
__int128_t sv3 = static_cast<__int128_t>(s3) * static_cast<__int128_t>(vv2);
// |vv3| < 2^-21, ulp = 2^-97
__int128_t vv3 = (static_cast<__int128_t>(spv3) << 21) + sv3;

// Range reduction - Step 4
// Output range: vv4 in [-0x1.0002143p-29 , 0x1p-29]
// idx4 = trunc(2^21 * (v + 65*2^-28 + 2^-29))
size_t idx4 = static_cast<size_t>((static_cast<int>(vv3 >> 68) + 131) >> 1);

sum = fputil::quick_add(sum, log_table.step_4[idx4]);

__int128_t s4 = static_cast<__int128_t>(S4[idx4]); // |s| < 2^-21, ulp = 2^-28
// |s + v| < 2^-28, ulp = 2^-97
__int128_t spv4 = (s4 << 69) + vv3;
// |s*v| < 2^-42, ulp = 2^(-97-28) = 2^-125
__int128_t sv4 = s4 * vv3;
// |vv4| < 2^-28, ulp = 2^-125
__int128_t vv4 = (spv4 << 28) + sv4;

return (vv4 < 0) ? Float128(true, -125,
MType({static_cast<uint64_t>(-vv4),
static_cast<uint64_t>((-vv4) >> 64)}))
: Float128(false, -125,
MType({static_cast<uint64_t>(vv4),
static_cast<uint64_t>(vv4 >> 64)}));
}

} // namespace __llvm_libc

#endif // LLVM_LIBC_SRC_MATH_GENERIC_LOG_RANGE_REDUCTION_H
18 changes: 18 additions & 0 deletions libc/src/math/log.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//===-- Implementation header for log ---------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_MATH_LOG_H
#define LLVM_LIBC_SRC_MATH_LOG_H

namespace __llvm_libc {

double log(double x);

} // namespace __llvm_libc

#endif // LLVM_LIBC_SRC_MATH_LOG_H
14 changes: 14 additions & 0 deletions libc/test/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,20 @@ add_fp_unittest(
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
log_test
NEED_MPFR
SUITE
libc_math_unittests
SRCS
log_test.cpp
DEPENDS
libc.src.errno.errno
libc.include.math
libc.src.math.log
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
logf_test
NEED_MPFR
Expand Down
138 changes: 138 additions & 0 deletions libc/test/src/math/log_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//===-- Unittests for log -------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/FPUtil/FPBits.h"
#include "src/errno/libc_errno.h"
#include "src/math/log.h"
#include "test/UnitTest/FPMatcher.h"
#include "test/UnitTest/Test.h"
#include "utils/MPFRWrapper/MPFRUtils.h"
#include <math.h>

#include <errno.h>
#include <stdint.h>

namespace mpfr = __llvm_libc::testing::mpfr;
using __llvm_libc::testing::tlog;

DECLARE_SPECIAL_CONSTANTS(double)

TEST(LlvmLibcLogTest, SpecialNumbers) {
EXPECT_FP_EQ(aNaN, __llvm_libc::log(aNaN));
EXPECT_FP_EQ(inf, __llvm_libc::log(inf));
EXPECT_FP_IS_NAN_WITH_EXCEPTION(__llvm_libc::log(neg_inf), FE_INVALID);
EXPECT_FP_EQ_WITH_EXCEPTION(neg_inf, __llvm_libc::log(0.0), FE_DIVBYZERO);
EXPECT_FP_EQ_WITH_EXCEPTION(neg_inf, __llvm_libc::log(-0.0), FE_DIVBYZERO);
EXPECT_FP_IS_NAN_WITH_EXCEPTION(__llvm_libc::log(-1.0), FE_INVALID);
EXPECT_FP_EQ_ALL_ROUNDING(zero, __llvm_libc::log(1.0));
}

TEST(LlvmLibcLogTest, TrickyInputs) {
constexpr int N = 30;
constexpr uint64_t INPUTS[N] = {
0x3ff0000000000000, // x = 1.0
0x4024000000000000, // x = 10.0
0x4059000000000000, // x = 10^2
0x408f400000000000, // x = 10^3
0x40c3880000000000, // x = 10^4
0x40f86a0000000000, // x = 10^5
0x412e848000000000, // x = 10^6
0x416312d000000000, // x = 10^7
0x4197d78400000000, // x = 10^8
0x41cdcd6500000000, // x = 10^9
0x4202a05f20000000, // x = 10^10
0x42374876e8000000, // x = 10^11
0x426d1a94a2000000, // x = 10^12
0x42a2309ce5400000, // x = 10^13
0x42d6bcc41e900000, // x = 10^14
0x430c6bf526340000, // x = 10^15
0x4341c37937e08000, // x = 10^16
0x4376345785d8a000, // x = 10^17
0x43abc16d674ec800, // x = 10^18
0x43e158e460913d00, // x = 10^19
0x4415af1d78b58c40, // x = 10^20
0x444b1ae4d6e2ef50, // x = 10^21
0x4480f0cf064dd592, // x = 10^22
0x3fefffffffef06ad, 0x3fefde0f22c7d0eb, 0x225e7812faadb32f,
0x3fee1076964c2903, 0x3fdfe93fff7fceb0, 0x3ff012631ad8df10,
0x3fefbfdaa448ed98,
};
for (int i = 0; i < N; ++i) {
double x = double(FPBits(INPUTS[i]));
EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Log, x, __llvm_libc::log(x),
0.5);
}
}

TEST(LlvmLibcLogTest, AllExponents) {
double x = 0x1.0p-1074;
for (int i = -1074; i < 1024; ++i, x *= 2.0) {
ASSERT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Log, x, __llvm_libc::log(x),
0.5);
}
}

TEST(LlvmLibcLogTest, InDoubleRange) {
constexpr uint64_t COUNT = 1234561;
constexpr uint64_t START = 0x3FD0'0000'0000'0000ULL; // 0.25
constexpr uint64_t STOP = 0x4010'0000'0000'0000ULL; // 4.0
// constexpr uint64_t START = 0x3FF0'0000'0000'0000ULL; // 1.0
// constexpr uint64_t STOP = 0x4000'0000'0000'0000ULL; // 2.0
constexpr uint64_t STEP = (STOP - START) / COUNT;

auto test = [&](mpfr::RoundingMode rounding_mode) {
mpfr::ForceRoundingMode __r(rounding_mode);
uint64_t fails = 0;
uint64_t count = 0;
uint64_t cc = 0;
double mx, mr = 0.0;
double tol = 0.5;

for (uint64_t i = 0, v = START; i <= COUNT; ++i, v += STEP) {
double x = FPBits(v).get_val();
if (isnan(x) || isinf(x) || x < 0.0)
continue;
libc_errno = 0;
double result = __llvm_libc::log(x);
++cc;
if (isnan(result) || isinf(result))
continue;

++count;
// ASSERT_MPFR_MATCH(mpfr::Operation::Log, x, result, 0.5);
if (!EXPECT_MPFR_MATCH_ROUNDING_SILENTLY(mpfr::Operation::Log, x, result,
0.5, rounding_mode)) {
++fails;
while (!EXPECT_MPFR_MATCH_ROUNDING_SILENTLY(
mpfr::Operation::Log, x, result, tol, rounding_mode)) {
mx = x;
mr = result;
tol *= 2.0;
}
}
}
tlog << " Log failed: " << fails << "/" << count << "/" << cc
<< " tests.\n";
tlog << " Max ULPs is at most: " << static_cast<uint64_t>(tol) << ".\n";
if (fails) {
EXPECT_MPFR_MATCH(mpfr::Operation::Log, mx, mr, 0.5, rounding_mode);
}
};

tlog << " Test Rounding To Nearest...\n";
test(mpfr::RoundingMode::Nearest);

tlog << " Test Rounding Downward...\n";
test(mpfr::RoundingMode::Downward);

tlog << " Test Rounding Upward...\n";
test(mpfr::RoundingMode::Upward);

tlog << " Test Rounding Toward Zero...\n";
test(mpfr::RoundingMode::TowardZero);
}