Skip to content

Commit

Permalink
Add rounding functions to base/numerics/safe_conversions.h.
Browse files Browse the repository at this point in the history
These will replace the versions in
ui/gfx/geometry/safe_integer_conversions.h.

Bug: 1088346
Change-Id: Ied8b5cbeff02fa870923976bb3ceb6fc8f98c340
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2274161
Commit-Queue: Peter Kasting <pkasting@chromium.org>
Auto-Submit: Peter Kasting <pkasting@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Cr-Commit-Position: refs/heads/master@{#783797}
  • Loading branch information
pkasting authored and Commit Bot committed Jun 30, 2020
1 parent cb35835 commit fc5d8f3
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 3 deletions.
2 changes: 2 additions & 0 deletions base/OWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ per-file feature_list*=isherman@chromium.org
per-file rand_util*=set noparent
per-file rand_util*=file://ipc/SECURITY_OWNERS

per-file safe_numerics_unittest.cc=file://base/numerics/OWNERS

# For TCMalloc tests:
per-file security_unittest.cc=jln@chromium.org

Expand Down
31 changes: 28 additions & 3 deletions base/numerics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,26 @@ is used in cases where an out-of-bounds source value should be saturated to the
corresponding maximum or minimum of the destination type:

```cpp
// Cast to a smaller type, saturating as needed.
int8_t eight_bit_value = saturated_cast<int8_t>(int_value);

// Convert from float with saturation to INT_MAX, INT_MIN, or 0 for NaN.
int int_value = saturated_cast<int>(floating_point_value);
```

`Ceil`, `Floor`, and `Round` provide similar functionality to the versions in
`std::`, but saturate and return an integral type. An optional template
parameter specifies the desired destination type (`int` if unspecified). These
should be used for most floating-to-integral conversions.

```cpp
// Basically saturated_cast<int>(std::round(floating_point_value)).
int int_value = Round(floating_point_value);

// A destination type can be explicitly specified.
uint8_t byte_value = Floor<uint8_t>(floating_point_value);
```

### Enforcing arithmetic type conversions at compile-time

The `strict_cast` emits code that is identical to `static_cast`. However,
Expand Down Expand Up @@ -179,16 +195,25 @@ performing a range of conversions, assignments, and tests.

### Other helper and conversion functions

* `IsValueInRangeForNumericType<>()` - A convenience function that returns
true if the type supplied as the template parameter can represent the value
passed as an argument to the function.
* `Ceil<>()` - A convenience function that computes the ceil of its floating-
point arg, then saturates to the destination type (template parameter,
defaults to `int`).
* `Floor<>()` - A convenience function that computes the floor of its
floating-point arg, then saturates to the destination type (template
parameter, defaults to `int`).
* `IsTypeInRangeForNumericType<>()` - A convenience function that evaluates
entirely at compile-time and returns true if the destination type (first
template parameter) can represent the full range of the source type
(second template parameter).
* `IsValueInRangeForNumericType<>()` - A convenience function that returns
true if the type supplied as the template parameter can represent the value
passed as an argument to the function.
* `IsValueNegative()` - A convenience function that will accept any
arithmetic type as an argument and will return whether the value is less
than zero. Unsigned types always return false.
* `Round<>()` - A convenience function that rounds its floating-point arg,
then saturates to the destination type (template parameter, defaults to
`int`).
* `SafeUnsignedAbs()` - Returns the absolute value of the supplied integer
parameter as an unsigned result (thus avoiding an overflow if the value
is the signed, two's complement minimum).
Expand Down
28 changes: 28 additions & 0 deletions base/numerics/safe_conversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <stddef.h>

#include <cmath>
#include <limits>
#include <type_traits>

Expand Down Expand Up @@ -353,6 +354,33 @@ using internal::IsValueNegative;
// Explicitly make a shorter size_t alias for convenience.
using SizeT = StrictNumeric<size_t>;

// floating -> integral conversions that saturate and thus can actually return
// an integral type. In most cases, these should be preferred over the std::
// versions.
template <typename Dst = int,
typename Src,
typename = std::enable_if_t<std::is_integral<Dst>::value &&
std::is_floating_point<Src>::value>>
Dst Floor(Src value) {
return saturated_cast<Dst>(std::floor(value));
}
template <typename Dst = int,
typename Src,
typename = std::enable_if_t<std::is_integral<Dst>::value &&
std::is_floating_point<Src>::value>>
Dst Ceil(Src value) {
return saturated_cast<Dst>(std::ceil(value));
}
template <typename Dst = int,
typename Src,
typename = std::enable_if_t<std::is_integral<Dst>::value &&
std::is_floating_point<Src>::value>>
Dst Round(Src value) {
const Src rounded =
(value >= 0.0f) ? std::floor(value + 0.5f) : std::ceil(value - 0.5f);
return saturated_cast<Dst>(rounded);
}

} // namespace base

#endif // BASE_NUMERICS_SAFE_CONVERSIONS_H_
112 changes: 112 additions & 0 deletions base/safe_numerics_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1660,6 +1660,118 @@ TEST(SafeNumerics, VariadicNumericOperations) {
}
}

TEST(SafeNumerics, CeilInt) {
constexpr float kMax = std::numeric_limits<int>::max();
constexpr float kMin = std::numeric_limits<int>::min();
constexpr float kInfinity = std::numeric_limits<float>::infinity();
constexpr float kNaN = std::numeric_limits<float>::quiet_NaN();

constexpr int kIntMax = std::numeric_limits<int>::max();
constexpr int kIntMin = std::numeric_limits<int>::min();

EXPECT_EQ(kIntMax, Ceil(kInfinity));
EXPECT_EQ(kIntMax, Ceil(kMax));
EXPECT_EQ(kIntMax, Ceil(kMax + 100.0f));
EXPECT_EQ(0, Ceil(kNaN));

EXPECT_EQ(-100, Ceil(-100.5f));
EXPECT_EQ(0, Ceil(0.0f));
EXPECT_EQ(101, Ceil(100.5f));

EXPECT_EQ(kIntMin, Ceil(-kInfinity));
EXPECT_EQ(kIntMin, Ceil(kMin));
EXPECT_EQ(kIntMin, Ceil(kMin - 100.0f));
EXPECT_EQ(0, Ceil(-kNaN));
}

TEST(SafeNumerics, FloorInt) {
constexpr float kMax = std::numeric_limits<int>::max();
constexpr float kMin = std::numeric_limits<int>::min();
constexpr float kInfinity = std::numeric_limits<float>::infinity();
constexpr float kNaN = std::numeric_limits<float>::quiet_NaN();

constexpr int kIntMax = std::numeric_limits<int>::max();
constexpr int kIntMin = std::numeric_limits<int>::min();

EXPECT_EQ(kIntMax, Floor(kInfinity));
EXPECT_EQ(kIntMax, Floor(kMax));
EXPECT_EQ(kIntMax, Floor(kMax + 100.0f));
EXPECT_EQ(0, Floor(kNaN));

EXPECT_EQ(-101, Floor(-100.5f));
EXPECT_EQ(0, Floor(0.0f));
EXPECT_EQ(100, Floor(100.5f));

EXPECT_EQ(kIntMin, Floor(-kInfinity));
EXPECT_EQ(kIntMin, Floor(kMin));
EXPECT_EQ(kIntMin, Floor(kMin - 100.0f));
EXPECT_EQ(0, Floor(-kNaN));
}

TEST(SafeNumerics, RoundInt) {
constexpr float kMax = std::numeric_limits<int>::max();
constexpr float kMin = std::numeric_limits<int>::min();
constexpr float kInfinity = std::numeric_limits<float>::infinity();
constexpr float kNaN = std::numeric_limits<float>::quiet_NaN();

constexpr int kIntMax = std::numeric_limits<int>::max();
constexpr int kIntMin = std::numeric_limits<int>::min();

EXPECT_EQ(kIntMax, Round(kInfinity));
EXPECT_EQ(kIntMax, Round(kMax));
EXPECT_EQ(kIntMax, Round(kMax + 100.0f));
EXPECT_EQ(0, Round(kNaN));

EXPECT_EQ(-100, Round(-100.1f));
EXPECT_EQ(-101, Round(-100.5f));
EXPECT_EQ(-101, Round(-100.9f));
EXPECT_EQ(0, Round(0.0f));
EXPECT_EQ(100, Round(100.1f));
EXPECT_EQ(101, Round(100.5f));
EXPECT_EQ(101, Round(100.9f));

EXPECT_EQ(kIntMin, Round(-kInfinity));
EXPECT_EQ(kIntMin, Round(kMin));
EXPECT_EQ(kIntMin, Round(kMin - 100.0f));
EXPECT_EQ(0, Round(-kNaN));
}

TEST(SafeNumerics, Int64) {
constexpr double kMax = std::numeric_limits<int64_t>::max();
constexpr double kMin = std::numeric_limits<int64_t>::min();
constexpr double kInfinity = std::numeric_limits<double>::infinity();
constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();

constexpr int64_t kInt64Max = std::numeric_limits<int64_t>::max();
constexpr int64_t kInt64Min = std::numeric_limits<int64_t>::min();

EXPECT_EQ(kInt64Max, Floor<int64_t>(kInfinity));
EXPECT_EQ(kInt64Max, Ceil<int64_t>(kInfinity));
EXPECT_EQ(kInt64Max, Round<int64_t>(kInfinity));
EXPECT_EQ(kInt64Max, Floor<int64_t>(kMax));
EXPECT_EQ(kInt64Max, Ceil<int64_t>(kMax));
EXPECT_EQ(kInt64Max, Round<int64_t>(kMax));
EXPECT_EQ(kInt64Max, Floor<int64_t>(kMax + 100.0));
EXPECT_EQ(kInt64Max, Ceil<int64_t>(kMax + 100.0));
EXPECT_EQ(kInt64Max, Round<int64_t>(kMax + 100.0));
EXPECT_EQ(0, Floor<int64_t>(kNaN));
EXPECT_EQ(0, Ceil<int64_t>(kNaN));
EXPECT_EQ(0, Round<int64_t>(kNaN));

EXPECT_EQ(kInt64Min, Floor<int64_t>(-kInfinity));
EXPECT_EQ(kInt64Min, Ceil<int64_t>(-kInfinity));
EXPECT_EQ(kInt64Min, Round<int64_t>(-kInfinity));
EXPECT_EQ(kInt64Min, Floor<int64_t>(kMin));
EXPECT_EQ(kInt64Min, Ceil<int64_t>(kMin));
EXPECT_EQ(kInt64Min, Round<int64_t>(kMin));
EXPECT_EQ(kInt64Min, Floor<int64_t>(kMin - 100.0));
EXPECT_EQ(kInt64Min, Ceil<int64_t>(kMin - 100.0));
EXPECT_EQ(kInt64Min, Round<int64_t>(kMin - 100.0));
EXPECT_EQ(0, Floor<int64_t>(-kNaN));
EXPECT_EQ(0, Ceil<int64_t>(-kNaN));
EXPECT_EQ(0, Round<int64_t>(-kNaN));
}

#if defined(__clang__)
#pragma clang diagnostic pop // -Winteger-overflow
#endif
Expand Down

0 comments on commit fc5d8f3

Please sign in to comment.