Flutter Engine
The Flutter Engine
checked_math_impl.h
Go to the documentation of this file.
1// Copyright 2017 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef BASE_NUMERICS_CHECKED_MATH_IMPL_H_
6#define BASE_NUMERICS_CHECKED_MATH_IMPL_H_
7
8#include <climits>
9#include <cmath>
10#include <cstddef>
11#include <cstdint>
12#include <cstdlib>
13#include <limits>
14#include <type_traits>
15
18
19namespace base {
20namespace internal {
21
22template <typename T>
23constexpr bool CheckedAddImpl(T x, T y, T* result) {
24 static_assert(std::is_integral<T>::value, "Type must be integral");
25 // Since the value of x+y is undefined if we have a signed type, we compute
26 // it using the unsigned type of the same size.
27 using UnsignedDst = typename std::make_unsigned<T>::type;
28 using SignedDst = typename std::make_signed<T>::type;
29 UnsignedDst ux = static_cast<UnsignedDst>(x);
30 UnsignedDst uy = static_cast<UnsignedDst>(y);
31 UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy);
32 *result = static_cast<T>(uresult);
33 // Addition is valid if the sign of (x + y) is equal to either that of x or
34 // that of y.
36 ? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) >= 0
37 : uresult >= uy; // Unsigned is either valid or underflow.
38}
39
40template <typename T, typename U, class Enable = void>
41struct CheckedAddOp {};
42
43template <typename T, typename U>
45 U,
46 typename std::enable_if<std::is_integral<T>::value &&
47 std::is_integral<U>::value>::type> {
49 template <typename V>
50 static constexpr bool Do(T x, U y, V* result) {
51 // TODO(jschuh) Make this "constexpr if" once we're C++17.
54
55 // Double the underlying type up to a full machine word.
56 using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
57 using Promotion =
58 typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
61 FastPromotion>::type;
62 // Fail if either operand is out of range for the promoted type.
63 // TODO(jschuh): This could be made to work for a broader range of values.
64 if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
65 !IsValueInRangeForNumericType<Promotion>(y))) {
66 return false;
67 }
68
69 Promotion presult = {};
70 bool is_valid = true;
72 presult = static_cast<Promotion>(x) + static_cast<Promotion>(y);
73 } else {
74 is_valid = CheckedAddImpl(static_cast<Promotion>(x),
75 static_cast<Promotion>(y), &presult);
76 }
77 *result = static_cast<V>(presult);
78 return is_valid && IsValueInRangeForNumericType<V>(presult);
79 }
80};
81
82template <typename T>
83constexpr bool CheckedSubImpl(T x, T y, T* result) {
84 static_assert(std::is_integral<T>::value, "Type must be integral");
85 // Since the value of x+y is undefined if we have a signed type, we compute
86 // it using the unsigned type of the same size.
87 using UnsignedDst = typename std::make_unsigned<T>::type;
88 using SignedDst = typename std::make_signed<T>::type;
89 UnsignedDst ux = static_cast<UnsignedDst>(x);
90 UnsignedDst uy = static_cast<UnsignedDst>(y);
91 UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy);
92 *result = static_cast<T>(uresult);
93 // Subtraction is valid if either x and y have same sign, or (x-y) and x have
94 // the same sign.
96 ? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) >= 0
97 : x >= y;
98}
99
100template <typename T, typename U, class Enable = void>
101struct CheckedSubOp {};
102
103template <typename T, typename U>
105 U,
106 typename std::enable_if<std::is_integral<T>::value &&
107 std::is_integral<U>::value>::type> {
109 template <typename V>
110 static constexpr bool Do(T x, U y, V* result) {
111 // TODO(jschuh) Make this "constexpr if" once we're C++17.
114
115 // Double the underlying type up to a full machine word.
116 using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
117 using Promotion =
118 typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
121 FastPromotion>::type;
122 // Fail if either operand is out of range for the promoted type.
123 // TODO(jschuh): This could be made to work for a broader range of values.
124 if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
125 !IsValueInRangeForNumericType<Promotion>(y))) {
126 return false;
127 }
128
129 Promotion presult = {};
130 bool is_valid = true;
132 presult = static_cast<Promotion>(x) - static_cast<Promotion>(y);
133 } else {
134 is_valid = CheckedSubImpl(static_cast<Promotion>(x),
135 static_cast<Promotion>(y), &presult);
136 }
137 *result = static_cast<V>(presult);
138 return is_valid && IsValueInRangeForNumericType<V>(presult);
139 }
140};
141
142template <typename T>
143constexpr bool CheckedMulImpl(T x, T y, T* result) {
144 static_assert(std::is_integral<T>::value, "Type must be integral");
145 // Since the value of x*y is potentially undefined if we have a signed type,
146 // we compute it using the unsigned type of the same size.
147 using UnsignedDst = typename std::make_unsigned<T>::type;
148 using SignedDst = typename std::make_signed<T>::type;
149 const UnsignedDst ux = SafeUnsignedAbs(x);
150 const UnsignedDst uy = SafeUnsignedAbs(y);
151 UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
152 const bool is_negative =
153 std::is_signed<T>::value && static_cast<SignedDst>(x ^ y) < 0;
154 *result = is_negative ? 0 - uresult : uresult;
155 // We have a fast out for unsigned identity or zero on the second operand.
156 // After that it's an unsigned overflow check on the absolute value, with
157 // a +1 bound for a negative result.
158 return uy <= UnsignedDst(!std::is_signed<T>::value || is_negative) ||
159 ux <= (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy;
160}
161
162template <typename T, typename U, class Enable = void>
163struct CheckedMulOp {};
164
165template <typename T, typename U>
167 U,
168 typename std::enable_if<std::is_integral<T>::value &&
169 std::is_integral<U>::value>::type> {
171 template <typename V>
172 static constexpr bool Do(T x, U y, V* result) {
173 // TODO(jschuh) Make this "constexpr if" once we're C++17.
176
177 using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
178 // Verify the destination type can hold the result (always true for 0).
179 if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
180 !IsValueInRangeForNumericType<Promotion>(y)) &&
181 x && y)) {
182 return false;
183 }
184
185 Promotion presult = {};
186 bool is_valid = true;
188 // The fast op may be available with the promoted type.
191 presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
192 } else {
193 is_valid = CheckedMulImpl(static_cast<Promotion>(x),
194 static_cast<Promotion>(y), &presult);
195 }
196 *result = static_cast<V>(presult);
197 return is_valid && IsValueInRangeForNumericType<V>(presult);
198 }
199};
200
201// Division just requires a check for a zero denominator or an invalid negation
202// on signed min/-1.
203template <typename T, typename U, class Enable = void>
204struct CheckedDivOp {};
205
206template <typename T, typename U>
208 U,
209 typename std::enable_if<std::is_integral<T>::value &&
210 std::is_integral<U>::value>::type> {
212 template <typename V>
213 static constexpr bool Do(T x, U y, V* result) {
215 return false;
216
217 // The overflow check can be compiled away if we don't have the exact
218 // combination of types needed to trigger this case.
219 using Promotion = typename BigEnoughPromotion<T, U>::type;
223 static_cast<Promotion>(x) ==
224 std::numeric_limits<Promotion>::lowest() &&
225 y == static_cast<U>(-1)))) {
226 return false;
227 }
228
229 // This branch always compiles away if the above branch wasn't removed.
230 if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
231 !IsValueInRangeForNumericType<Promotion>(y)) &&
232 x)) {
233 return false;
234 }
235
236 Promotion presult = Promotion(x) / Promotion(y);
237 *result = static_cast<V>(presult);
238 return IsValueInRangeForNumericType<V>(presult);
239 }
240};
241
242template <typename T, typename U, class Enable = void>
243struct CheckedModOp {};
244
245template <typename T, typename U>
247 U,
248 typename std::enable_if<std::is_integral<T>::value &&
249 std::is_integral<U>::value>::type> {
251 template <typename V>
252 static constexpr bool Do(T x, U y, V* result) {
254 return false;
255
256 using Promotion = typename BigEnoughPromotion<T, U>::type;
260 static_cast<Promotion>(x) ==
261 std::numeric_limits<Promotion>::lowest() &&
262 y == static_cast<U>(-1)))) {
263 *result = 0;
264 return true;
265 }
266
267 Promotion presult = static_cast<Promotion>(x) % static_cast<Promotion>(y);
268 *result = static_cast<Promotion>(presult);
269 return IsValueInRangeForNumericType<V>(presult);
270 }
271};
272
273template <typename T, typename U, class Enable = void>
274struct CheckedLshOp {};
275
276// Left shift. Shifts less than 0 or greater than or equal to the number
277// of bits in the promoted type are undefined. Shifts of negative values
278// are undefined. Otherwise it is defined when the result fits.
279template <typename T, typename U>
281 U,
282 typename std::enable_if<std::is_integral<T>::value &&
283 std::is_integral<U>::value>::type> {
284 using result_type = T;
285 template <typename V>
286 static constexpr bool Do(T x, U shift, V* result) {
287 // Disallow negative numbers and verify the shift is in bounds.
289 as_unsigned(shift) <
290 as_unsigned(std::numeric_limits<T>::digits))) {
291 // Shift as unsigned to avoid undefined behavior.
292 *result = static_cast<V>(as_unsigned(x) << shift);
293 // If the shift can be reversed, we know it was valid.
294 return *result >> shift == x;
295 }
296
297 // Handle the legal corner-case of a full-width signed shift of zero.
298 return std::is_signed<T>::value && !x &&
299 as_unsigned(shift) == as_unsigned(std::numeric_limits<T>::digits);
300 }
301};
302
303template <typename T, typename U, class Enable = void>
304struct CheckedRshOp {};
305
306// Right shift. Shifts less than 0 or greater than or equal to the number
307// of bits in the promoted type are undefined. Otherwise, it is always defined,
308// but a right shift of a negative value is implementation-dependent.
309template <typename T, typename U>
311 U,
312 typename std::enable_if<std::is_integral<T>::value &&
313 std::is_integral<U>::value>::type> {
314 using result_type = T;
315 template <typename V>
316 static bool Do(T x, U shift, V* result) {
317 // Use the type conversion push negative values out of range.
320 T tmp = x >> shift;
321 *result = static_cast<V>(tmp);
322 return IsValueInRangeForNumericType<V>(tmp);
323 }
324 return false;
325 }
326};
327
328template <typename T, typename U, class Enable = void>
329struct CheckedAndOp {};
330
331// For simplicity we support only unsigned integer results.
332template <typename T, typename U>
334 U,
335 typename std::enable_if<std::is_integral<T>::value &&
336 std::is_integral<U>::value>::type> {
337 using result_type = typename std::make_unsigned<
339 template <typename V>
340 static constexpr bool Do(T x, U y, V* result) {
341 result_type tmp = static_cast<result_type>(x) & static_cast<result_type>(y);
342 *result = static_cast<V>(tmp);
343 return IsValueInRangeForNumericType<V>(tmp);
344 }
345};
346
347template <typename T, typename U, class Enable = void>
348struct CheckedOrOp {};
349
350// For simplicity we support only unsigned integers.
351template <typename T, typename U>
353 U,
354 typename std::enable_if<std::is_integral<T>::value &&
355 std::is_integral<U>::value>::type> {
356 using result_type = typename std::make_unsigned<
358 template <typename V>
359 static constexpr bool Do(T x, U y, V* result) {
360 result_type tmp = static_cast<result_type>(x) | static_cast<result_type>(y);
361 *result = static_cast<V>(tmp);
362 return IsValueInRangeForNumericType<V>(tmp);
363 }
364};
365
366template <typename T, typename U, class Enable = void>
367struct CheckedXorOp {};
368
369// For simplicity we support only unsigned integers.
370template <typename T, typename U>
372 U,
373 typename std::enable_if<std::is_integral<T>::value &&
374 std::is_integral<U>::value>::type> {
375 using result_type = typename std::make_unsigned<
377 template <typename V>
378 static constexpr bool Do(T x, U y, V* result) {
379 result_type tmp = static_cast<result_type>(x) ^ static_cast<result_type>(y);
380 *result = static_cast<V>(tmp);
381 return IsValueInRangeForNumericType<V>(tmp);
382 }
383};
384
385// Max doesn't really need to be implemented this way because it can't fail,
386// but it makes the code much cleaner to use the MathOp wrappers.
387template <typename T, typename U, class Enable = void>
388struct CheckedMaxOp {};
389
390template <typename T, typename U>
392 T,
393 U,
394 typename std::enable_if<std::is_arithmetic<T>::value &&
395 std::is_arithmetic<U>::value>::type> {
397 template <typename V>
398 static constexpr bool Do(T x, U y, V* result) {
399 result_type tmp = IsGreater<T, U>::Test(x, y) ? static_cast<result_type>(x)
400 : static_cast<result_type>(y);
401 *result = static_cast<V>(tmp);
402 return IsValueInRangeForNumericType<V>(tmp);
403 }
404};
405
406// Min doesn't really need to be implemented this way because it can't fail,
407// but it makes the code much cleaner to use the MathOp wrappers.
408template <typename T, typename U, class Enable = void>
409struct CheckedMinOp {};
410
411template <typename T, typename U>
413 T,
414 U,
415 typename std::enable_if<std::is_arithmetic<T>::value &&
416 std::is_arithmetic<U>::value>::type> {
418 template <typename V>
419 static constexpr bool Do(T x, U y, V* result) {
420 result_type tmp = IsLess<T, U>::Test(x, y) ? static_cast<result_type>(x)
421 : static_cast<result_type>(y);
422 *result = static_cast<V>(tmp);
423 return IsValueInRangeForNumericType<V>(tmp);
424 }
425};
426
427// This is just boilerplate that wraps the standard floating point arithmetic.
428// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
429#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
430 template <typename T, typename U> \
431 struct Checked##NAME##Op< \
432 T, U, \
433 typename std::enable_if<std::is_floating_point<T>::value || \
434 std::is_floating_point<U>::value>::type> { \
435 using result_type = typename MaxExponentPromotion<T, U>::type; \
436 template <typename V> \
437 static constexpr bool Do(T x, U y, V* result) { \
438 using Promotion = typename MaxExponentPromotion<T, U>::type; \
439 Promotion presult = x OP y; \
440 *result = static_cast<V>(presult); \
441 return IsValueInRangeForNumericType<V>(presult); \
442 } \
443 };
444
449
450#undef BASE_FLOAT_ARITHMETIC_OPS
451
452// Floats carry around their validity state with them, but integers do not. So,
453// we wrap the underlying value in a specialization in order to hide that detail
454// and expose an interface via accessors.
460
461template <typename NumericType>
468};
469
470template <typename T,
473
474// Integrals require quite a bit of additional housekeeping to manage state.
475template <typename T>
477 private:
478 // is_valid_ precedes value_ because member intializers in the constructors
479 // are evaluated in field order, and is_valid_ must be read when initializing
480 // value_.
481 bool is_valid_;
482 T value_;
483
484 // Ensures that a type conversion does not trigger undefined behavior.
485 template <typename Src>
486 static constexpr T WellDefinedConversionOrZero(const Src value,
487 const bool is_valid) {
490 ? static_cast<T>(value)
491 : static_cast<T>(0);
492 }
493
494 public:
495 template <typename Src, NumericRepresentation type>
497
498 constexpr CheckedNumericState() : is_valid_(true), value_(0) {}
499
500 template <typename Src>
501 constexpr CheckedNumericState(Src value, bool is_valid)
503 value_(WellDefinedConversionOrZero(value, is_valid_)) {
504 static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
505 }
506
507 // Copy constructor.
508 template <typename Src>
510 : is_valid_(rhs.IsValid()),
511 value_(WellDefinedConversionOrZero(rhs.value(), is_valid_)) {}
512
513 template <typename Src>
514 constexpr explicit CheckedNumericState(Src value)
515 : is_valid_(IsValueInRangeForNumericType<T>(value)),
516 value_(WellDefinedConversionOrZero(value, is_valid_)) {}
517
518 constexpr bool is_valid() const { return is_valid_; }
519 constexpr T value() const { return value_; }
520};
521
522// Floating points maintain their own validity, but need translation wrappers.
523template <typename T>
525 private:
526 T value_;
527
528 // Ensures that a type conversion does not trigger undefined behavior.
529 template <typename Src>
530 static constexpr T WellDefinedConversionOrNaN(const Src value,
531 const bool is_valid) {
535 is_valid)
536 ? static_cast<T>(value)
537 : std::numeric_limits<T>::quiet_NaN();
538 }
539
540 public:
541 template <typename Src, NumericRepresentation type>
543
544 constexpr CheckedNumericState() : value_(0.0) {}
545
546 template <typename Src>
547 constexpr CheckedNumericState(Src value, bool is_valid)
548 : value_(WellDefinedConversionOrNaN(value, is_valid)) {}
549
550 template <typename Src>
551 constexpr explicit CheckedNumericState(Src value)
552 : value_(WellDefinedConversionOrNaN(
553 value,
555
556 // Copy constructor.
557 template <typename Src>
559 : value_(WellDefinedConversionOrNaN(
560 rhs.value(),
561 rhs.is_valid() && IsValueInRangeForNumericType<T>(rhs.value()))) {}
562
563 constexpr bool is_valid() const {
564 // Written this way because std::isfinite is not reliably constexpr.
565 return MustTreatAsConstexpr(value_)
567 value_ >= std::numeric_limits<T>::lowest()
568 : std::isfinite(value_);
569 }
570 constexpr T value() const { return value_; }
571};
572
573} // namespace internal
574} // namespace base
575
576#endif // BASE_NUMERICS_CHECKED_MATH_IMPL_H_
static bool is_valid(SkISize dim)
GLenum type
#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP)
constexpr CheckedNumericState(const CheckedNumericState< Src > &rhs)
constexpr CheckedNumericState(const CheckedNumericState< Src > &rhs)
uint8_t value
GAsyncResult * result
static float max(float r, float g, float b)
Definition: hsl.cpp:49
T __attribute__((ext_vector_type(N))) V
double y
double x
constexpr bool CheckedMulImpl(T x, T y, T *result)
constexpr bool CheckedSubImpl(T x, T y, T *result)
constexpr bool CheckedAddImpl(T x, T y, T *result)
constexpr bool IsValueNegative(T value)
constexpr std::make_unsigned< T >::type SafeUnsignedAbs(T value)
constexpr std::make_unsigned< typenamebase::internal::UnderlyingType< Src >::type >::type as_unsigned(const Src value)
constexpr bool IsValueInRangeForNumericType(Src value)
constexpr bool MustTreatAsConstexpr(const T v)
SINT bool isfinite(const Vec< N, T > &v)
Definition: SkVx.h:1003
Definition: ref_ptr.h:256
#define T
Definition: precompiler.cc:65
#define BASE_NUMERICS_LIKELY(x)
#define BASE_NUMERICS_UNLIKELY(x)
static constexpr bool Do(T, U, V *)
static constexpr bool Do(T, U, V *)
static constexpr bool Do(T, U, V *)
static const NumericRepresentation value
static constexpr bool Test(const L lhs, const R rhs)
static constexpr bool Test(const L lhs, const R rhs)
typename ArithmeticOrUnderlyingEnum< T >::type type
SrcType
Definition: xfermodes.cpp:29