Skip to content

Commit dd22722

Browse files
Ink Open Sourcecopybara-github
authored andcommitted
Factor FloatModulo out of Angle::Normalize
So that it can also be used in other places. Note that it has to go in a new module rather than in `geometry/internal/algorithms` in order to avoid a dependency cycle with `Angle`. PiperOrigin-RevId: 721475555
1 parent fd5ece4 commit dd22722

File tree

7 files changed

+167
-19
lines changed

7 files changed

+167
-19
lines changed

ink/geometry/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ cc_library(
5858
hdrs = ["angle.h"],
5959
visibility = ["//visibility:public"],
6060
deps = [
61+
"//ink/geometry/internal:modulo",
6162
"//ink/types:numbers",
6263
"@com_google_absl//absl/strings:str_format",
6364
],

ink/geometry/angle.cc

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,6 @@
2222

2323
namespace ink {
2424

25-
Angle Angle::Normalized() const {
26-
float radians =
27-
std::fmod(ValueInRadians(), static_cast<float>(2.0 * numbers::kPi));
28-
// fmod always matches the sign of the first argument, so fmod(angle, 2π)
29-
// returns a value in the range (-2π, 2π). We want [0, 2π), so wrap negative
30-
// values back to positive.
31-
if (radians < 0.f) {
32-
radians += static_cast<float>(2.0 * numbers::kPi);
33-
// If fmod returned a sufficiently small negative number, then adding 2π
34-
// will give us a result exactly equal to 2π, due to float rounding.
35-
// However, Angle::Normalized() promises to return a value strictly less
36-
// than 2π, so we should normalize to zero in this case.
37-
if (radians == static_cast<float>(2.0 * numbers::kPi)) {
38-
radians = 0.f;
39-
}
40-
}
41-
return Angle::Radians(radians);
42-
}
43-
4425
Angle Angle::NormalizedAboutZero() const {
4526
float radians =
4627
std::fmod(ValueInRadians(), static_cast<float>(2.0 * numbers::kPi));

ink/geometry/angle.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <cstdlib>
2020
#include <string>
2121

22+
#include "ink/geometry/internal/modulo.h"
2223
#include "ink/types/numbers.h"
2324

2425
namespace ink {
@@ -197,6 +198,11 @@ inline Angle& operator/=(Angle& lhs, float rhs) {
197198
return lhs;
198199
}
199200

201+
inline Angle Angle::Normalized() const {
202+
return Angle::Radians(geometry_internal::FloatModulo(
203+
ValueInRadians(), static_cast<float>(2.0 * numbers::kPi)));
204+
}
205+
200206
} // namespace ink
201207

202208
#endif // INK_GEOMETRY_ANGLE_H_

ink/geometry/internal/BUILD.bazel

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,25 @@ cc_test(
133133
],
134134
)
135135

136+
cc_library(
137+
name = "modulo",
138+
srcs = ["modulo.cc"],
139+
hdrs = ["modulo.h"],
140+
deps = [
141+
"@com_google_absl//absl/log:absl_check",
142+
],
143+
)
144+
145+
cc_test(
146+
name = "modulo_test",
147+
srcs = ["modulo_test.cc"],
148+
deps = [
149+
":modulo",
150+
"@com_google_fuzztest//fuzztest",
151+
"@com_google_fuzztest//fuzztest:fuzztest_gtest_main",
152+
],
153+
)
154+
136155
cc_library(
137156
name = "circle",
138157
srcs = ["circle.cc"],

ink/geometry/internal/modulo.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "ink/geometry/internal/modulo.h"
16+
17+
#include <cmath>
18+
19+
#include "absl/log/absl_check.h"
20+
21+
namespace ink::geometry_internal {
22+
23+
float FloatModulo(float a, float b) {
24+
ABSL_DCHECK(std::isfinite(b) && b > 0.f);
25+
float result = std::fmodf(a, b);
26+
// `fmodf` always matches the sign of the first argument, so `fmodf(a, b)`
27+
// returns a value in the range (-b, b). We want [0, b), so wrap negative
28+
// values back to positive.
29+
if (result < 0.f) {
30+
result += b;
31+
// If `fmodf` returned a sufficiently small negative number, then adding `b`
32+
// will give us a result exactly equal to `b`, due to float rounding.
33+
// However, `FloatModulo` promises to return a value strictly less than `b`,
34+
// so we should return zero in this case.
35+
if (result == b) {
36+
result = 0.f;
37+
}
38+
}
39+
return result;
40+
}
41+
42+
} // namespace ink::geometry_internal

ink/geometry/internal/modulo.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef INK_GEOMETRY_INTERNAL_MODULO_H_
16+
#define INK_GEOMETRY_INTERNAL_MODULO_H_
17+
18+
namespace ink::geometry_internal {
19+
20+
// Returns `a` mod `b`, with the result being in the range [0, `b`). This is
21+
// different from `std::fmodf`, which returns a negative result if `a` is
22+
// negative. `b` must be finite and strictly greater than zero. Returns NaN if
23+
// `a` is infinite or NaN.
24+
float FloatModulo(float a, float b);
25+
26+
} // namespace ink::geometry_internal
27+
28+
#endif // INK_GEOMETRY_INTERNAL_MODULO_H_
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "ink/geometry/internal/modulo.h"
16+
17+
#include <cmath>
18+
#include <limits>
19+
20+
#include "gmock/gmock.h"
21+
#include "gtest/gtest.h"
22+
#include "fuzztest/fuzztest.h"
23+
24+
namespace ink::geometry_internal {
25+
namespace {
26+
27+
using ::testing::AllOf;
28+
using ::testing::Ge;
29+
using ::testing::IsNan;
30+
using ::testing::Lt;
31+
32+
constexpr float kInf = std::numeric_limits<float>::infinity();
33+
const float kNaN = std::nanf("");
34+
35+
fuzztest::Domain<float> FinitePositiveFloat() {
36+
return fuzztest::Filter([](float b) { return std::isfinite(b); },
37+
fuzztest::Positive<float>());
38+
}
39+
40+
TEST(ModuloTest, FloatModulo) {
41+
EXPECT_FLOAT_EQ(FloatModulo(0, 1), 0);
42+
EXPECT_FLOAT_EQ(FloatModulo(0.75, 1), 0.75);
43+
EXPECT_FLOAT_EQ(FloatModulo(1, 1), 0);
44+
EXPECT_FLOAT_EQ(FloatModulo(1.25, 1), 0.25);
45+
EXPECT_FLOAT_EQ(FloatModulo(-0.25, 1), 0.75);
46+
EXPECT_FLOAT_EQ(FloatModulo(-1.5, 1), 0.5);
47+
48+
EXPECT_FLOAT_EQ(FloatModulo(0, 10), 0);
49+
EXPECT_FLOAT_EQ(FloatModulo(7.5, 10), 7.5);
50+
EXPECT_FLOAT_EQ(FloatModulo(10, 10), 0);
51+
EXPECT_FLOAT_EQ(FloatModulo(12.5, 10), 2.5);
52+
EXPECT_FLOAT_EQ(FloatModulo(-2.5, 10), 7.5);
53+
EXPECT_FLOAT_EQ(FloatModulo(-15, 10), 5);
54+
}
55+
56+
void FloatModuloOfNonFiniteIsNan(float b) {
57+
EXPECT_THAT(FloatModulo(kInf, b), IsNan());
58+
EXPECT_THAT(FloatModulo(-kInf, b), IsNan());
59+
EXPECT_THAT(FloatModulo(kNaN, b), IsNan());
60+
}
61+
FUZZ_TEST(ModuloTest, FloatModuloOfNonFiniteIsNan)
62+
.WithDomains(FinitePositiveFloat());
63+
64+
void FloatModuloIsStrictlyInRange(float a, float b) {
65+
EXPECT_THAT(FloatModulo(a, b), AllOf(Ge(0.f), Lt(b)));
66+
}
67+
FUZZ_TEST(ModuloTest, FloatModuloIsStrictlyInRange)
68+
.WithDomains(fuzztest::Finite<float>(), FinitePositiveFloat());
69+
70+
} // namespace
71+
} // namespace ink::geometry_internal

0 commit comments

Comments
 (0)