File: sin_cos_degrees.h

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (114 lines) | stat: -rw-r--r-- 3,821 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// 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 UI_GFX_GEOMETRY_SIN_COS_DEGREES_H_
#define UI_GFX_GEOMETRY_SIN_COS_DEGREES_H_

#include <algorithm>
#include <array>
#include <cmath>
#include <numbers>
#include <utility>

#include "base/numerics/angle_conversions.h"

namespace gfx {

struct SinCos {
  double sin;
  double cos;
  bool IsZeroAngle() const { return sin == 0 && cos == 1; }
};

inline SinCos SinCosDegrees(double degrees) {
  // Some math libraries have poor accuracy with large arguments,
  // so range-reduce explicitly before we call sin() or cos(). However, unless
  // we're _really_ large (out of range of an int), we can do that faster than
  // fmod(), since we have an integer divisor (and as an extra bonus, we've
  // already got it precomputed). We pick a pretty arbitrary limit that should
  // be safe.
  //
  // We range-reduce to [0..45]. This should hit the fast path of sincos()
  // on most platforms (since no further reduction is needed; reducing
  // accurately modulo a trancendental can we slow), using only branches that
  // should be possible to do using conditional operations; using a switch
  // instead would be possible, but benchmarked much slower on M1.
  // For platforms that don't use sincos() (e.g., it seems Clang doesn't
  // manage the rewrite on Linux), we also save on having the range reduction
  // done only once.
  if (degrees > -90000000.0 && degrees < 90000000.0) {
    // Make sure 0, 90, 180 and 270 degrees get exact results. (We also have
    // precomputed values for 45, 135, etc., but only as a side effect of using
    // 45 instead of 90, for the benefit of the range reduction algorithm below.
    // The error for e.g. sin(45 degrees) is typically only 1 ulp.)
    double n45degrees = degrees / 45.0;
    int octant = static_cast<int>(n45degrees);
    if (octant == n45degrees) {
      constexpr auto kSinCosN45 = std::to_array<SinCos>({
          {0, 1},
          {std::numbers::sqrt2 / 2, std::numbers::sqrt2 / 2},
          {1, 0},
          {std::numbers::sqrt2 / 2, -std::numbers::sqrt2 / 2},
          {0, -1},
          {-std::numbers::sqrt2 / 2, -std::numbers::sqrt2 / 2},
          {-1, 0},
          {-std::numbers::sqrt2 / 2, std::numbers::sqrt2 / 2},
      });

      return kSinCosN45[octant & 7];
    }

    if (degrees < 0) {
      // This will cause the range-reduction below to move us
      // into [0..45], as desired, instead of [-45..0].
      --octant;
    }
    degrees -= octant * 45.0;  // Range-reduce to [0..45].

    // Deal with 45..90 the same as 45..0. This also covers
    // 135..180, 225..270 and 315..360, i.e. the odd octants.
    // The relevant trigonometric identities is that
    // sin(90 - a) = cos(a) and vice versa; we do the sin/cos
    // flip below.
    if (octant & 1) {
      degrees = 45.0 - degrees;
    }

    double rad = base::DegToRad(degrees);
    double s = std::sin(rad);
    double c = std::cos(rad);

    // 45..135 and -135..-45 can be moved into the opposite areas
    // simply by flipping the x and y axis (in conjunction with
    // the conversion from CW to CCW done above).
    using std::swap;
    if ((octant + 1) & 2) {
      swap(s, c);
    }

    // For sine, 180..360 (lower half) is the same as 0..180,
    // except negative.
    if (octant & 4) {
      s = -s;
    }

    // For cosine, 90..270 (right half) is the same as -90..90,
    // except negative.
    if ((octant + 2) & 4) {
      c = -c;
    }

    return SinCos{s, c};
  }

  // Slow path for extreme cases.
  degrees = std::fmod(degrees, 360.0);
  double rad = base::DegToRad(degrees);
  return SinCos{std::sin(rad), std::cos(rad)};
}

}  // namespace gfx

#endif  // UI_GFX_GEOMETRY_SIN_COS_DEGREES_H_