Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ cc_library(
copts = COPTS,
textual_hdrs = [
"hwy/contrib/math/math-inl.h",
"hwy/contrib/math/f16_math-inl.h",
"hwy/contrib/math/fast_math-inl.h",
"hwy/contrib/math/fp_arith-inl.h",
],
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ list(APPEND HWY_CONTRIB_SOURCES
hwy/contrib/hash/cuckoo2x2-inl.h
hwy/contrib/image/image.cc
hwy/contrib/image/image.h
hwy/contrib/math/f16_math-inl.h
hwy/contrib/math/fast_math-inl.h
hwy/contrib/intdiv/intdiv-inl.h
hwy/contrib/math/fp_arith-inl.h
Expand Down Expand Up @@ -1029,6 +1030,7 @@ list(APPEND HWY_TEST_FILES
hwy/contrib/hash/shardmul_test.cc
hwy/contrib/image/image_test.cc
hwy/contrib/intdiv/intdiv_test.cc
hwy/contrib/math/f16_math_test.cc
hwy/contrib/math/fast_math_test.cc
hwy/contrib/math/math_hyper_test.cc
hwy/contrib/math/math_tan_test.cc
Expand Down
1 change: 1 addition & 0 deletions hwy.gni
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ hwy_contrib_public = [
"$_hwy/contrib/algo/transform-inl.h",
"$_hwy/contrib/dot/dot-inl.h",
"$_hwy/contrib/image/image.h",
"$_hwy/contrib/math/f16_math-inl.h",
"$_hwy/contrib/math/fast_math-inl.h",
"$_hwy/contrib/math/fp_arith-inl.h",
"$_hwy/contrib/intdiv/intdiv-inl.h",
Expand Down
236 changes: 236 additions & 0 deletions hwy/contrib/math/f16_math-inl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Include guard (still compiled once per target)
#if defined(HIGHWAY_HWY_CONTRIB_MATH_F16_MATH_INL_H_) == \
defined(HWY_TARGET_TOGGLE) // NOLINT
#ifdef HIGHWAY_HWY_CONTRIB_MATH_F16_MATH_INL_H_
#undef HIGHWAY_HWY_CONTRIB_MATH_F16_MATH_INL_H_
#else
#define HIGHWAY_HWY_CONTRIB_MATH_F16_MATH_INL_H_
#endif

#include "hwy/contrib/math/math-inl.h"
#include "hwy/highway.h"

HWY_BEFORE_NAMESPACE();
namespace hwy {
namespace HWY_NAMESPACE {

// float16_t overloads of the math functions in math-inl.h. There are no
// float16 kernels yet, so these promote to float32, evaluate the float32
// kernel, and demote the result. The ops used here support float16_t lanes
// on all targets, even when HWY_HAVE_FLOAT16 is 0.

// Not named `impl`: unqualified calls such as Log() from inside that
// namespace would also find math-inl.h's impl::Log and be ambiguous.
namespace f16_impl {

// Promotes lower/upper halves of the float16 vector `v` to float32,
// evaluates `kernel` (a functor wrapping one float32 math function) twice,
// then demotes and recombines. Never instantiated on HWY_SCALAR, so
// PromoteUpperTo and Combine are safe to name here. There is no
// OrderedDemote2To for f32->f16, hence DemoteTo + Combine.
template <class D, class Kernel, class V = VFromD<D>>
HWY_INLINE V F16ViaF32PerHalf(D d, V v, Kernel kernel) {
const Half<D> dh;
const RepartitionToWide<D> df32;
const VFromD<decltype(df32)> lo = kernel(df32, PromoteLowerTo(df32, v));
const VFromD<decltype(df32)> hi = kernel(df32, PromoteUpperTo(df32, v));
return Combine(d, DemoteTo(dh, hi), DemoteTo(dh, lo));
}

// Evaluates `kernel` on all lanes of `v` with a single promotion and one
// kernel evaluation. This overload handles vectors that fit in half of the
// widest vector - including all partial vectors, N=1, and the entire
// HWY_SCALAR target. The kPow2 bound ensures Rebind<float, D> (one more
// kPow2 because float lanes are twice as large) is a valid tag.
template <class D, class Kernel, class V = VFromD<D>, HWY_IF_F16_D(D),
HWY_IF_V_SIZE_LE_D(D, HWY_MAX_BYTES / 2),
HWY_IF_POW2_LE_D(D, HWY_MAX_POW2 - 1)>
HWY_INLINE V F16ViaF32(D d, V v, Kernel kernel) {
const Rebind<float, D> df32;
return DemoteTo(d, kernel(df32, PromoteTo(df32, v)));
}

// Wider vectors take the per-half path.
template <class D, class Kernel, class V = VFromD<D>, HWY_IF_F16_D(D),
HWY_IF_V_SIZE_GT_D(D, HWY_MAX_BYTES / 2)>
HWY_INLINE V F16ViaF32(D d, V v, Kernel kernel) {
return F16ViaF32PerHalf(d, v, kernel);
}

// Small vectors already at the maximum kPow2 (possible on RVV, e.g.
// CappedTag<float16_t, 8, 3>): Rebind<float, D> would exceed HWY_MAX_POW2,
// so use the per-half path, whose RepartitionToWide keeps kPow2 unchanged.
template <class D, class Kernel, class V = VFromD<D>, HWY_IF_F16_D(D),
HWY_IF_V_SIZE_LE_D(D, HWY_MAX_BYTES / 2),
HWY_IF_POW2_GT_D(D, HWY_MAX_POW2 - 1)>
HWY_INLINE V F16ViaF32(D d, V v, Kernel kernel) {
return F16ViaF32PerHalf(d, v, kernel);
}

struct ExpKernel {
template <class DF, class VF>
HWY_INLINE VF operator()(DF df, VF x) const {
return Exp(df, x);
}
};

struct Exp2Kernel {
template <class DF, class VF>
HWY_INLINE VF operator()(DF df, VF x) const {
return Exp2(df, x);
}
};

struct Expm1Kernel {
template <class DF, class VF>
HWY_INLINE VF operator()(DF df, VF x) const {
return Expm1(df, x);
}
};

struct LogKernel {
template <class DF, class VF>
HWY_INLINE VF operator()(DF df, VF x) const {
return Log(df, x);
}
};

struct Log10Kernel {
template <class DF, class VF>
HWY_INLINE VF operator()(DF df, VF x) const {
return Log10(df, x);
}
};

struct Log1pKernel {
template <class DF, class VF>
HWY_INLINE VF operator()(DF df, VF x) const {
return Log1p(df, x);
}
};

struct Log2Kernel {
template <class DF, class VF>
HWY_INLINE VF operator()(DF df, VF x) const {
return Log2(df, x);
}
};

} // namespace f16_impl

// The generic templates in math-inl.h are constrained with
// HWY_IF_NOT_SPECIAL_FLOAT_D, so these HWY_IF_F16_D overloads partition the
// overload set rather than being ambiguous.

/**
* Highway SIMD version of std::exp(x) for float16 lanes.
*
* Valid Lane Types: float16
* Max Error: ULP = 2
* Valid Range: float16[-65504, +104]
* @return e^x
*/
template <class D, class V, HWY_IF_F16_D(D)>
HWY_INLINE V Exp(D d, V x) {
return f16_impl::F16ViaF32(d, x, f16_impl::ExpKernel());
}

/**
* Highway SIMD version of std::exp2(x) for float16 lanes.
*
* Valid Lane Types: float16
* Max Error: ULP = 2
* Valid Range: float16[-65504, +128]
* @return 2^x
*/
template <class D, class V, HWY_IF_F16_D(D)>
HWY_INLINE V Exp2(D d, V x) {
return f16_impl::F16ViaF32(d, x, f16_impl::Exp2Kernel());
}

/**
* Highway SIMD version of std::expm1(x) for float16 lanes.
*
* Valid Lane Types: float16
* Max Error: ULP = 2
* Valid Range: float16[-65504, +104]
* @return e^x - 1
*/
template <class D, class V, HWY_IF_F16_D(D)>
HWY_INLINE V Expm1(D d, V x) {
return f16_impl::F16ViaF32(d, x, f16_impl::Expm1Kernel());
}

/**
* Highway SIMD version of std::log(x) for float16 lanes.
*
* Valid Lane Types: float16
* Max Error: ULP = 1
* Valid Range: float16(0, +65504]
* @return natural logarithm of 'x'
*/
template <class D, class V, HWY_IF_F16_D(D)>
HWY_INLINE V Log(D d, V x) {
return f16_impl::F16ViaF32(d, x, f16_impl::LogKernel());
}

/**
* Highway SIMD version of std::log10(x) for float16 lanes.
*
* Valid Lane Types: float16
* Max Error: ULP = 1
* Valid Range: float16(0, +65504]
* @return base 10 logarithm of 'x'
*/
template <class D, class V, HWY_IF_F16_D(D)>
HWY_INLINE V Log10(D d, V x) {
return f16_impl::F16ViaF32(d, x, f16_impl::Log10Kernel());
}

/**
* Highway SIMD version of std::log1p(x) for float16 lanes.
*
* Valid Lane Types: float16
* Max Error: ULP = 2
* Valid Range: float16[0, +65504]
* @return log(1 + x)
*/
template <class D, class V, HWY_IF_F16_D(D)>
HWY_INLINE V Log1p(D d, V x) {
return f16_impl::F16ViaF32(d, x, f16_impl::Log1pKernel());
}

/**
* Highway SIMD version of std::log2(x) for float16 lanes.
*
* Valid Lane Types: float16
* Max Error: ULP = 1
* Valid Range: float16(0, +65504]
* @return base 2 logarithm of 'x'
*/
template <class D, class V, HWY_IF_F16_D(D)>
HWY_INLINE V Log2(D d, V x) {
return f16_impl::F16ViaF32(d, x, f16_impl::Log2Kernel());
}

// NOLINTNEXTLINE(google-readability-namespace-comments)
} // namespace HWY_NAMESPACE
} // namespace hwy
HWY_AFTER_NAMESPACE();

#endif // HIGHWAY_HWY_CONTRIB_MATH_F16_MATH_INL_H_
91 changes: 91 additions & 0 deletions hwy/contrib/math/f16_math_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdint.h>
#include <stdio.h>

#include <cmath> // std::exp

// For faster tests. Not using AES, hence NEON_WITHOUT_AES is sufficient.
// SVE is mostly superseded by SVE2.
#ifndef HWY_DISABLED_TARGETS
#define HWY_DISABLED_TARGETS (HWY_NEON | HWY_SVE)
#endif // HWY_DISABLED_TARGETS

#include "hwy/base.h"

// clang-format off
#undef HWY_TARGET_INCLUDE
#define HWY_TARGET_INCLUDE "hwy/contrib/math/f16_math_test.cc"
#include "hwy/foreach_target.h" // IWYU pragma: keep
#include "hwy/highway.h"
#include "hwy/contrib/math/f16_math-inl.h"
#include "hwy/contrib/math/math-inl.h" // CallExp
#include "hwy/contrib/math/math_test-inl.h"
// clang-format on

HWY_BEFORE_NAMESPACE();
namespace hwy {
namespace HWY_NAMESPACE {
namespace {

// The float16 min/max bounds mirror the float32 bounds in math_test.cc where
// the kernel is validated (e.g. +104 for Exp), clamped to the float16 finite
// range [-65504, +65504]. The smallest positive float16 subnormal is 2^-24.
//
// Exp, Exp2, Expm1 and Log1p allow 2 ULP because they are the functions whose
// results reach the float16 subnormals, where the spacing is a fixed 2^-24 and
// the smallest results hold only about four significant bits, so a float32
// result that differs slightly between targets can land two float16 values
// away. The logarithms never return a subnormal and stay at 1 ULP.
// clang-format off
DEFINE_F16_MATH_TEST(Exp,
std::exp, CallExp, -65504.0f, +104.0f, 2)
DEFINE_F16_MATH_TEST(Exp2,
std::exp2, CallExp2, -65504.0f, +128.0f, 2)
DEFINE_F16_MATH_TEST(Expm1,
std::expm1, CallExpm1, -65504.0f, +104.0f, 2)
DEFINE_F16_MATH_TEST(Log,
std::log, CallLog, +5.960464478E-8f, +65504.0f, 1)
DEFINE_F16_MATH_TEST(Log10,
std::log10, CallLog10, +5.960464478E-8f, +65504.0f, 1)
DEFINE_F16_MATH_TEST(Log1p,
std::log1p, CallLog1p, +0.0f, +65504.0f, 2)
DEFINE_F16_MATH_TEST(Log2,
std::log2, CallLog2, +5.960464478E-8f, +65504.0f, 1)
// clang-format on

} // namespace
// NOLINTNEXTLINE(google-readability-namespace-comments)
} // namespace HWY_NAMESPACE
} // namespace hwy
HWY_AFTER_NAMESPACE();

#if HWY_ONCE
namespace hwy {
namespace {
HWY_BEFORE_TEST(HwyF16MathTest);
HWY_EXPORT_AND_TEST_P(HwyF16MathTest, TestAllF16Exp);
HWY_EXPORT_AND_TEST_P(HwyF16MathTest, TestAllF16Exp2);
HWY_EXPORT_AND_TEST_P(HwyF16MathTest, TestAllF16Expm1);
HWY_EXPORT_AND_TEST_P(HwyF16MathTest, TestAllF16Log);
HWY_EXPORT_AND_TEST_P(HwyF16MathTest, TestAllF16Log10);
HWY_EXPORT_AND_TEST_P(HwyF16MathTest, TestAllF16Log1p);
HWY_EXPORT_AND_TEST_P(HwyF16MathTest, TestAllF16Log2);
HWY_AFTER_TEST();
} // namespace
} // namespace hwy
HWY_TEST_MAIN();
#endif // HWY_ONCE
Loading
Loading