blob: f28778eda7e433ac5835a1fa9bbb5d354b84dc43 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GEOMETRY_MATH_FUNCTIONS_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GEOMETRY_MATH_FUNCTIONS_H_
#include <array>
#include <cfloat>
#include <cmath>
#include <optional>
#include <type_traits>
#include <utility>
#include "base/notreached.h"
#include "ui/gfx/geometry/sin_cos_degrees.h"
namespace blink {
namespace {
template <class ValueType>
std::pair<ValueType, ValueType> GetNearestMultiples(ValueType a, ValueType b) {
using std::swap;
bool is_negative = a < 0.0;
a = std::abs(a);
b = std::abs(b);
// To get rid of rounding and range problems we use std::fmod
// to get the lower to A multiple of B.
ValueType c = -std::fmod(a, b);
ValueType lower = a + c;
// Sort a, b, c by increasing magnitude so that a + b + c later can
// avoid rounding problems (e.g., if one number is very small compared to
// other).
if (std::abs(a) > std::abs(c)) {
swap(a, c);
}
if (std::abs(a) > std::abs(b)) {
swap(a, b);
}
if (std::abs(b) > std::abs(c)) {
swap(b, c);
}
ValueType upper = a + b + c;
if (is_negative) {
swap(lower, upper);
lower = -lower;
upper = -upper;
}
return {lower, upper};
}
template <class OperatorType, typename ValueType>
std::optional<ValueType> PreCheckSteppedValueFunctionArguments(OperatorType op,
ValueType a,
ValueType b) {
// In round(A, B), if B is 0, the result is NaN.
// In mod(A, B) or rem(A, B), if B is 0, the result is NaN.
// If A and B are both infinite, the result is NaN.
if (b == 0.0 || (std::isinf(a) && std::isinf(b))) {
return std::numeric_limits<ValueType>::quiet_NaN();
}
// If A is exactly equal to an integer multiple of B,
// round() resolves to A exactly.
// If A is infinite but B is finite, the result is the same infinity.
if (OperatorType::kRoundNearest <= op && op <= OperatorType::kRoundToZero &&
(std::fmod(a, b) == 0.0 || (std::isinf(a) && !std::isinf(b)))) {
return a;
}
// In mod(A, B) or rem(A, B), if A is infinite, the result is NaN.
if (OperatorType::kMod <= op && op <= OperatorType::kRem && std::isinf(a)) {
return std::numeric_limits<ValueType>::quiet_NaN();
}
return {};
}
template <typename T>
requires std::floating_point<T>
T TanDegrees(T degrees) {
// Use table values for tan() if possible.
// We pick a pretty arbitrary limit that should be safe.
if (degrees > -90000000.0 && degrees < 90000000.0) {
// Make sure 0, 45, 90, 135, 180, 225 and 270 degrees get exact results.
T n45degrees = degrees / 45.0;
int octant = static_cast<int>(n45degrees);
if (octant == n45degrees) {
constexpr std::array<T, 8> kTanN45 = {
/* 0deg */ 0.0,
/* 45deg */ 1.0,
/* 90deg */ std::numeric_limits<T>::infinity(),
/* 135deg */ -1.0,
/* 180deg */ 0.0,
/* 225deg */ 1.0,
/* 270deg */ -std::numeric_limits<T>::infinity(),
/* 315deg */ -1.0,
};
return kTanN45[octant & 7];
}
}
// Slow path for non-table cases.
T x = Deg2rad(degrees);
return std::tan(x);
}
} // namespace
template <class OperatorType, typename ValueType>
requires std::is_enum_v<OperatorType> && std::floating_point<ValueType>
ValueType EvaluateTrigonometricFunction(
OperatorType op,
ValueType a,
std::optional<ValueType>(b) = std::nullopt) {
switch (op) {
case OperatorType::kSin: {
return gfx::SinCosDegrees(a).sin;
}
case OperatorType::kCos: {
return gfx::SinCosDegrees(a).cos;
}
case OperatorType::kTan: {
return TanDegrees(a);
}
case OperatorType::kAsin: {
ValueType value = Rad2deg(std::asin(a));
DCHECK(value >= -90 && value <= 90 || std::isnan(value));
return value;
}
case OperatorType::kAcos: {
ValueType value = Rad2deg(std::acos(a));
DCHECK(value >= 0 && value <= 180 || std::isnan(value));
return value;
}
case OperatorType::kAtan: {
ValueType value = Rad2deg(std::atan(a));
DCHECK(value >= -90 && value <= 90 || std::isnan(value));
return value;
}
case OperatorType::kAtan2: {
DCHECK(b.has_value());
ValueType value = Rad2deg(std::atan2(a, b.value()));
DCHECK(value >= -180 && value <= 180 || std::isnan(value));
return value;
}
default:
NOTREACHED();
}
}
template <class OperatorType, typename ValueType>
requires std::is_enum_v<OperatorType> && std::floating_point<ValueType>
ValueType EvaluateSteppedValueFunction(OperatorType op,
ValueType a,
ValueType b) {
// https://drafts.csswg.org/css-values/#round-infinities
std::optional<ValueType> pre_check =
PreCheckSteppedValueFunctionArguments(op, a, b);
if (pre_check.has_value()) {
return pre_check.value();
}
// If A is finite but B is infinite, the result depends
// on the <rounding-strategy> and the sign of A:
// -- nearest, to-zero
// If A is positive or +0.0, return +0.0. Otherwise, return -0.0.
// -- up
// If A is positive (not zero), return +∞. If A is +0.0, return +0.0.
// Otherwise, return -0.0. down If A is negative (not zero), return −∞. If A
// is -0.0, return -0.0. Otherwise, return +0.0. If A is infinite but B is
// finite, the result is the same infinity.
auto [lower, upper] = GetNearestMultiples(a, b);
switch (op) {
case OperatorType::kRoundNearest: {
if (!std::isinf(a) && std::isinf(b)) {
return std::signbit(a) ? -0.0 : +0.0;
} else {
// In the negative case we need to swap lower and upper for the nearest
// rounding. This also means tie-breaking should pick the lower rather
// than upper,
const bool a_is_negative = a < 0.0;
if (a_is_negative) {
using std::swap;
swap(lower, upper);
}
const ValueType distance = std::abs(std::fmod(a, b));
const ValueType half_b = std::abs(b) / 2;
if (distance < half_b || (a_is_negative && distance == half_b)) {
return lower;
} else {
return upper;
}
}
}
case OperatorType::kRoundUp: {
if (!std::isinf(a) && std::isinf(b)) {
if (!a) {
return a;
} else {
return std::signbit(a) ? -0.0
: std::numeric_limits<ValueType>::infinity();
}
} else {
return upper;
}
}
case OperatorType::kRoundDown: {
if (!std::isinf(a) && std::isinf(b)) {
if (!a) {
return a;
} else {
return std::signbit(a) ? -std::numeric_limits<ValueType>::infinity()
: +0.0;
}
} else {
return lower;
}
}
case OperatorType::kRoundToZero: {
if (!std::isinf(a) && std::isinf(b)) {
return std::signbit(a) ? -0.0 : +0.0;
} else {
return std::abs(upper) < std::abs(lower) ? upper : lower;
}
}
case OperatorType::kMod: {
// In mod(A, B) only, if B is infinite and A has opposite sign to B
// (including an oppositely-signed zero), the result is NaN.
if (std::isinf(b) && std::signbit(a) != std::signbit(b)) {
return std::numeric_limits<ValueType>::quiet_NaN();
}
// If both arguments are positive or both are negative:
// the value of the function is equal to the value of A
// shifted by the integer multiple of B that brings
// the value between zero and B.
// If the A value and the B step are on opposite sides of zero:
// mod() (short for “modulus”) continues to choose the integer
// multiple of B that puts the value between zero and B.
// std::fmod - the returned value has the same sign as A
// and is less than B in magnitude.
ValueType result = std::fmod(a, b);
if (std::signbit(result) != std::signbit(b)) {
// When the absolute values of arguments are the same, but they
// appear on different sides from zero, the result of std::fmod will be
// either -0 (e.g. mod(-1, 1)), or +0 (e.g. mod(1, -1)), we need to swap
// the sign of the resulting zero to match the sign of the B.
if (std::abs(a) == std::abs(b)) {
return -result;
}
// If the result is on opposite side of zero from B,
// put it between 0 and B. As the result of std::fmod is less
// than B in magnitude, adding B would perform a correct shift.
return result + b;
}
return result;
}
case OperatorType::kRem: {
// If both arguments are positive or both are negative:
// the value of the function is equal to the value of A
// shifted by the integer multiple of B that brings
// the value between zero and B.
// If the A value and the B step are on opposite sides of zero:
// rem() (short for "remainder") chooses the integer multiple of B
// that puts the value between zero and -B,
// avoiding changing the sign of the value.
// std::fmod - the returned value has the same sign as A
// and is less than B in magnitude.
return std::fmod(a, b);
}
default:
NOTREACHED();
}
}
template <typename ValueType>
requires std::floating_point<ValueType>
ValueType EvaluateSignFunction(ValueType v) {
return (v == 0 || std::isnan(v)) ? v : ((v > 0) ? 1 : -1);
}
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GEOMETRY_MATH_FUNCTIONS_H_