diff --git a/libc/config/darwin/aarch64/entrypoints.txt b/libc/config/darwin/aarch64/entrypoints.txt index 437eca79a76f6..4674a9309115b 100644 --- a/libc/config/darwin/aarch64/entrypoints.txt +++ b/libc/config/darwin/aarch64/entrypoints.txt @@ -363,6 +363,7 @@ set(TARGET_LIBM_ENTRYPOINTS libc.src.math.tan libc.src.math.tanf libc.src.math.tanhf + libc.src.math.tanpif libc.src.math.totalorder libc.src.math.totalorderf libc.src.math.totalorderl diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt index 9e042cd4a8acb..cff5b7f8312d6 100644 --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -625,6 +625,7 @@ set(TARGET_LIBM_ENTRYPOINTS libc.src.math.tan libc.src.math.tanf libc.src.math.tanhf + libc.src.math.tanpif libc.src.math.totalorder libc.src.math.totalorderf libc.src.math.totalorderl diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt index fa62f4d290c3a..a1203cc4991af 100644 --- a/libc/config/linux/arm/entrypoints.txt +++ b/libc/config/linux/arm/entrypoints.txt @@ -437,6 +437,7 @@ set(TARGET_LIBM_ENTRYPOINTS libc.src.math.tan libc.src.math.tanf libc.src.math.tanhf + libc.src.math.tanpif libc.src.math.totalorder libc.src.math.totalorderf libc.src.math.totalorderl diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt index db8f8a7cf0b74..14361f5b6beff 100644 --- a/libc/config/linux/riscv/entrypoints.txt +++ b/libc/config/linux/riscv/entrypoints.txt @@ -635,6 +635,7 @@ set(TARGET_LIBM_ENTRYPOINTS libc.src.math.tan libc.src.math.tanf libc.src.math.tanhf + libc.src.math.tanpif libc.src.math.totalorder libc.src.math.totalorderf libc.src.math.totalorderl diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index 59c248871f83a..ea31d858dbc44 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -657,6 +657,7 @@ set(TARGET_LIBM_ENTRYPOINTS libc.src.math.tan libc.src.math.tanf libc.src.math.tanhf + libc.src.math.tanpif libc.src.math.totalorder libc.src.math.totalorderf libc.src.math.totalorderl diff --git a/libc/config/windows/entrypoints.txt b/libc/config/windows/entrypoints.txt index 09021a08cf731..8898fd74c302f 100644 --- a/libc/config/windows/entrypoints.txt +++ b/libc/config/windows/entrypoints.txt @@ -295,6 +295,7 @@ set(TARGET_LIBM_ENTRYPOINTS libc.src.math.tan libc.src.math.tanf libc.src.math.tanhf + libc.src.math.tanpif libc.src.math.trunc libc.src.math.truncf libc.src.math.truncl diff --git a/libc/docs/headers/math/index.rst b/libc/docs/headers/math/index.rst index 3cb41a6871b94..9679c4a6c807f 100644 --- a/libc/docs/headers/math/index.rst +++ b/libc/docs/headers/math/index.rst @@ -349,7 +349,7 @@ Higher Math Functions +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+ | tanh | |check| | | | |check| | | 7.12.5.6 | F.10.2.6 | +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+ -| tanpi | | | | |check| | | 7.12.4.14 | F.10.1.14 | +| tanpi | |check| | | | |check| | | 7.12.4.14 | F.10.1.14 | +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+ | tgamma | | | | | | 7.12.8.4 | F.10.5.4 | +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+ diff --git a/libc/include/math.yaml b/libc/include/math.yaml index 11bead0745954..3044ec3437ff8 100644 --- a/libc/include/math.yaml +++ b/libc/include/math.yaml @@ -2524,6 +2524,12 @@ functions: arguments: - type: _Float16 guard: LIBC_TYPES_HAS_FLOAT16 + - name: tanpif + standards: + - stdc + return_type: float + arguments: + - type: float - name: tanpif16 standards: - stdc diff --git a/libc/src/math/CMakeLists.txt b/libc/src/math/CMakeLists.txt index b27b0d2b523f8..455ad3456573a 100644 --- a/libc/src/math/CMakeLists.txt +++ b/libc/src/math/CMakeLists.txt @@ -519,6 +519,8 @@ add_math_entrypoint_object(tanf16) add_math_entrypoint_object(tanh) add_math_entrypoint_object(tanhf) add_math_entrypoint_object(tanhf16) + +add_math_entrypoint_object(tanpif) add_math_entrypoint_object(tanpif16) add_math_entrypoint_object(tgamma) diff --git a/libc/src/math/generic/CMakeLists.txt b/libc/src/math/generic/CMakeLists.txt index fd1e6c0d648aa..a379110853012 100644 --- a/libc/src/math/generic/CMakeLists.txt +++ b/libc/src/math/generic/CMakeLists.txt @@ -606,6 +606,21 @@ add_entrypoint_object( libc.src.__support.macros.properties.types ) +add_entrypoint_object( + tanpif + SRCS + tanpif.cpp + HDRS + ../tanpif.h + DEPENDS + .sincosf_utils + libc.src.__support.FPUtil.except_value_utils + libc.src.__support.FPUtil.fenv_impl + libc.src.__support.FPUtil.fp_bits + libc.src.__support.FPUtil.multiply_add + libc.src.__support.macros.optimization +) + add_entrypoint_object( tanpif16 SRCS diff --git a/libc/src/math/generic/tanpif.cpp b/libc/src/math/generic/tanpif.cpp new file mode 100644 index 0000000000000..58d46c9481aa5 --- /dev/null +++ b/libc/src/math/generic/tanpif.cpp @@ -0,0 +1,106 @@ +//===-- Single-precision tanpi function -----------------------------------===// +// +// 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/math/tanpif.h" +#include "sincosf_utils.h" +#include "src/__support/FPUtil/FEnvImpl.h" +#include "src/__support/FPUtil/FPBits.h" +#include "src/__support/FPUtil/cast.h" +#include "src/__support/FPUtil/except_value_utils.h" +#include "src/__support/FPUtil/multiply_add.h" +#include "src/__support/common.h" +#include "src/__support/macros/config.h" +#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY + +namespace LIBC_NAMESPACE_DECL { + +#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS +constexpr size_t N_EXCEPTS = 3; + +constexpr fputil::ExceptValues TANPIF_EXCEPTS{{ + // (input, RZ output, RU offset, RD offset, RN offset) + {0x38F26685, 0x39BE6182, 1, 0, 0}, + {0x3E933802, 0x3FA267DD, 1, 0, 0}, + {0x3F3663FF, 0xBFA267DD, 0, 1, 0}, +}}; +#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS + +LLVM_LIBC_FUNCTION(float, tanpif, (float x)) { + using FPBits = typename fputil::FPBits; + FPBits xbits(x); + + uint32_t x_u = xbits.uintval(); + uint32_t x_abs = x_u & 0x7fff'ffffU; + double xd = static_cast(xbits.get_val()); + + // Handle exceptional values + if (LIBC_UNLIKELY(x_abs <= 0x3F3663FF)) { + if (LIBC_UNLIKELY(x_abs == 0U)) + return x; + +#ifndef LIBC_MATH_HAS_SKIP_ACCURATE_PASS + bool x_sign = x_u >> 31; + + if (auto r = TANPIF_EXCEPTS.lookup_odd(x_abs, x_sign); + LIBC_UNLIKELY(r.has_value())) + return r.value(); +#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS + } + + // Numbers greater or equal to 2^23 are always integers, or infinity, or NaN + if (LIBC_UNLIKELY(x_abs >= 0x4B00'0000)) { + // x is inf or NaN. + if (LIBC_UNLIKELY(x_abs >= 0x7f80'0000U)) { + if (xbits.is_signaling_nan()) { + fputil::raise_except_if_required(FE_INVALID); + return FPBits::quiet_nan().get_val(); + } + + if (x_abs == 0x7f80'0000U) { + fputil::set_errno_if_required(EDOM); + fputil::raise_except_if_required(FE_INVALID); + } + + return x + FPBits::quiet_nan().get_val(); + } + + return FPBits::zero(xbits.sign()).get_val(); + } + + // Range reduction: + // For |x| > 1/32, we perform range reduction as follows: + // Find k and y such that: + // x = (k + y) * 1/32 + // k is an integer + // |y| < 0.5 + // + // This is done by performing: + // k = round(x * 32) + // y = x * 32 - k + // + // Once k and y are computed, we then deduce the answer by the formula: + // tan(x) = sin(x) / cos(x) + // = (sin_y * cos_k + cos_y * sin_k) / (cos_y * cos_k - sin_y * sin_k) + double sin_k, cos_k, sin_y, cosm1_y; + sincospif_eval(xd, sin_k, cos_k, sin_y, cosm1_y); + + if (LIBC_UNLIKELY(sin_y == 0 && cos_k == 0)) { + fputil::set_errno_if_required(EDOM); + fputil::raise_except_if_required(FE_DIVBYZERO); + + int32_t x_mp5_i = static_cast(xd - 0.5); + return FPBits::inf((x_mp5_i & 0x1) ? Sign::NEG : Sign::POS).get_val(); + } + + using fputil::multiply_add; + return fputil::cast( + multiply_add(sin_y, cos_k, multiply_add(cosm1_y, sin_k, sin_k)) / + multiply_add(sin_y, -sin_k, multiply_add(cosm1_y, cos_k, cos_k))); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/math/tanpif.h b/libc/src/math/tanpif.h new file mode 100644 index 0000000000000..59e6dcfa9ff73 --- /dev/null +++ b/libc/src/math/tanpif.h @@ -0,0 +1,20 @@ +//===-- Implementation header for tanpif ------------------------*- 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_TANPIF_H +#define LLVM_LIBC_SRC_MATH_TANPIF_H + +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +float tanpif(float x); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_MATH_TANPIF_H diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt index 0d0232335eb5f..43cde0d68873e 100644 --- a/libc/test/src/math/CMakeLists.txt +++ b/libc/test/src/math/CMakeLists.txt @@ -201,6 +201,21 @@ add_fp_unittest( libc.src.math.tanf16 ) +add_fp_unittest( + tanpif_test + NEED_MPFR + SUITE + libc-math-unittests + SRCS + tanpif_test.cpp + HDRS + sdcomp26094.h + DEPENDS + libc.src.math.tanpif + libc.src.__support.CPP.array + libc.src.__support.FPUtil.fp_bits +) + add_fp_unittest( tanpif16_test NEED_MPFR diff --git a/libc/test/src/math/exhaustive/CMakeLists.txt b/libc/test/src/math/exhaustive/CMakeLists.txt index 551f449c9c8db..1a23a335b2fff 100644 --- a/libc/test/src/math/exhaustive/CMakeLists.txt +++ b/libc/test/src/math/exhaustive/CMakeLists.txt @@ -122,6 +122,22 @@ add_fp_unittest( -lpthread ) +add_fp_unittest( + tanpif_test + NO_RUN_POSTBUILD + NEED_MPFR + SUITE + libc_math_exhaustive_tests + SRCS + tanpif_test.cpp + DEPENDS + .exhaustive_test + libc.src.math.tanpif + libc.src.__support.FPUtil.fp_bits + LINK_LIBRARIES + -lpthread +) + add_fp_unittest( erff_test NO_RUN_POSTBUILD diff --git a/libc/test/src/math/exhaustive/tanpif_test.cpp b/libc/test/src/math/exhaustive/tanpif_test.cpp new file mode 100644 index 0000000000000..44a8a06f52374 --- /dev/null +++ b/libc/test/src/math/exhaustive/tanpif_test.cpp @@ -0,0 +1,33 @@ +//===-- Exhaustive test for tanpif ----------------------------------------===// +// +// 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 "exhaustive_test.h" +#include "src/math/tanpif.h" +#include "utils/MPFRWrapper/MPFRUtils.h" + +namespace mpfr = LIBC_NAMESPACE::testing::mpfr; + +using LlvmLibcTanpifExhaustiveTest = + LlvmLibcUnaryOpExhaustiveMathTest; + +// Range: [0, Inf]; +static constexpr uint32_t POS_START = 0x0000'0000U; +static constexpr uint32_t POS_STOP = 0x7f80'0000U; + +TEST_F(LlvmLibcTanpifExhaustiveTest, PostiveRange) { + test_full_range_all_roundings(POS_START, POS_STOP); +} + +// Range: [-Inf, 0]; +static constexpr uint32_t NEG_START = 0xb000'0000U; +static constexpr uint32_t NEG_STOP = 0xff80'0000U; + +TEST_F(LlvmLibcTanpifExhaustiveTest, NegativeRange) { + test_full_range_all_roundings(NEG_START, NEG_STOP); +} diff --git a/libc/test/src/math/smoke/CMakeLists.txt b/libc/test/src/math/smoke/CMakeLists.txt index 599939cbec4b5..4aafe03d1d08b 100644 --- a/libc/test/src/math/smoke/CMakeLists.txt +++ b/libc/test/src/math/smoke/CMakeLists.txt @@ -134,6 +134,17 @@ add_fp_unittest( libc.src.math.tanf16 ) +add_fp_unittest( + tanpif_test + SUITE + libc-math-smoke-tests + SRCS + tanpif_test.cpp + DEPENDS + libc.src.errno.errno + libc.src.math.tanpif +) + add_fp_unittest( tanpif16_test SUITE diff --git a/libc/test/src/math/smoke/tanpif_test.cpp b/libc/test/src/math/smoke/tanpif_test.cpp new file mode 100644 index 0000000000000..e122f57a2fe0f --- /dev/null +++ b/libc/test/src/math/smoke/tanpif_test.cpp @@ -0,0 +1,36 @@ +//===-- Unittests for tanpif ----------------------------------------------===// +// +// 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/libc_errno.h" +#include "src/math/tanpif.h" +#include "test/UnitTest/FPMatcher.h" +#include "test/UnitTest/Test.h" + +using LlvmLibcTanpifTest = LIBC_NAMESPACE::testing::FPTest; + +TEST_F(LlvmLibcTanpifTest, SpecialNumbers) { + libc_errno = 0; + + EXPECT_FP_EQ_WITH_EXCEPTION(aNaN, LIBC_NAMESPACE::tanpif(sNaN), FE_INVALID); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(aNaN, LIBC_NAMESPACE::tanpif(aNaN)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(zero, LIBC_NAMESPACE::tanpif(zero)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(neg_zero, LIBC_NAMESPACE::tanpif(neg_zero)); + EXPECT_MATH_ERRNO(0); + + EXPECT_FP_EQ(aNaN, LIBC_NAMESPACE::tanpif(inf)); + EXPECT_MATH_ERRNO(EDOM); + + EXPECT_FP_EQ(aNaN, LIBC_NAMESPACE::tanpif(neg_inf)); + EXPECT_MATH_ERRNO(EDOM); +} diff --git a/libc/test/src/math/tanpif_test.cpp b/libc/test/src/math/tanpif_test.cpp new file mode 100644 index 0000000000000..47f794c4e1b37 --- /dev/null +++ b/libc/test/src/math/tanpif_test.cpp @@ -0,0 +1,91 @@ +//===-- Unittests for tanpif ----------------------------------------------===// +// +// 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/math/tanpif.h" +#include "test/UnitTest/FPMatcher.h" +#include "test/src/math/sdcomp26094.h" +#include "utils/MPFRWrapper/MPFRUtils.h" + +using LlvmLibcTanpifTest = LIBC_NAMESPACE::testing::FPTest; + +using LIBC_NAMESPACE::testing::SDCOMP26094_VALUES; + +namespace mpfr = LIBC_NAMESPACE::testing::mpfr; + +TEST_F(LlvmLibcTanpifTest, SpecificBitPatterns) { + constexpr int N = 38; + constexpr uint32_t INPUTS[N] = { + 0x3f00'0000U, // x = 0.5 + 0x461d'd600U, // x = 10101.5 + 0x3f06'0a92U, // x = pi/6 + 0x3f3a'dc51U, // x = 0x1.75b8a2p-1f + 0x3f49'0fdbU, // x = pi/4 + 0x3f86'0a92U, // x = pi/3 + 0x3fa7'832aU, // x = 0x1.4f0654p+0f + 0x3fc9'0fdbU, // x = pi/2 + 0x4017'1973U, // x = 0x1.2e32e6p+1f + 0x4049'0fdbU, // x = pi + 0x4096'cbe4U, // x = 0x1.2d97c8p+2f + 0x40c9'0fdbU, // x = 2*pi + 0x433b'7490U, // x = 0x1.76e92p+7f + 0x437c'e5f1U, // x = 0x1.f9cbe2p+7f + 0x4619'9998U, // x = 0x1.33333p+13f + 0x474d'246fU, // x = 0x1.9a48dep+15f + 0x4afd'ece4U, // x = 0x1.fbd9c8p+22f + 0x4c23'32e9U, // x = 0x1.4665d2p+25f + 0x50a3'e87fU, // x = 0x1.47d0fep+34f + 0x5239'47f6U, // x = 0x1.728fecp+37f + 0x53b1'46a6U, // x = 0x1.628d4cp+40f + 0x55ca'fb2aU, // x = 0x1.95f654p+44f + 0x588e'f060U, // x = 0x1.1de0cp+50f + 0x5c07'bcd0U, // x = 0x1.0f79ap+57f + 0x5ebc'fddeU, // x = 0x1.79fbbcp+62f + 0x5fa6'eba7U, // x = 0x1.4dd74ep+64f + 0x61a4'0b40U, // x = 0x1.48168p+68f + 0x6386'134eU, // x = 0x1.0c269cp+72f + 0x6589'8498U, // x = 0x1.13093p+76f + 0x6600'0001U, // x = 0x1.000002p+77f + 0x664e'46e4U, // x = 0x1.9c8dc8p+77f + 0x66b0'14aaU, // x = 0x1.602954p+78f + 0x67a9'242bU, // x = 0x1.524856p+80f + 0x6a19'76f1U, // x = 0x1.32ede2p+85f + 0x6c55'da58U, // x = 0x1.abb4bp+89f + 0x6f79'be45U, // x = 0x1.f37c8ap+95f + 0x7276'69d4U, // x = 0x1.ecd3a8p+101f + 0x7758'4625U, // x = 0x1.b08c4ap+111f + }; + + for (int i = 0; i < N; ++i) { + float x = FPBits(INPUTS[i]).get_val(); + EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Tanpi, x, + LIBC_NAMESPACE::tanpif(x), 0.5); + EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Tanpi, -x, + LIBC_NAMESPACE::tanpif(-x), 0.5); + } +} + +// For small values, tanpi(x) is pi * x. +TEST_F(LlvmLibcTanpifTest, SmallValues) { + float x = FPBits(0x1780'0000U).get_val(); + EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Tanpi, x, + LIBC_NAMESPACE::tanpif(x), 0.5); + + x = FPBits(0x0040'0000U).get_val(); + EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Tanpi, x, + LIBC_NAMESPACE::tanpif(x), 0.5); +} + +// SDCOMP-26094: check tanpif in the cases for which the range reducer +// returns values furthest beyond its nominal upper bound of pi/4. +TEST_F(LlvmLibcTanpifTest, SDCOMP_26094) { + for (uint32_t v : SDCOMP26094_VALUES) { + float x = FPBits((v)).get_val(); + EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Tanpi, x, + LIBC_NAMESPACE::tanpif(x), 0.5); + } +}