/src/mongo/util/represent_as.h

https://github.com/paralect/mongo · C Header · 242 lines · 120 code · 37 blank · 85 comment · 25 complexity · 99946d09e9b3c1b0530a83804a602ec2 MD5 · raw file

  1. /**
  2. * Copyright (C) 2018-present MongoDB, Inc.
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the Server Side Public License, version 1,
  6. * as published by MongoDB, Inc.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * Server Side Public License for more details.
  12. *
  13. * You should have received a copy of the Server Side Public License
  14. * along with this program. If not, see
  15. * <http://www.mongodb.com/licensing/server-side-public-license>.
  16. *
  17. * As a special exception, the copyright holders give permission to link the
  18. * code of portions of this program with the OpenSSL library under certain
  19. * conditions as described in each individual source file and distribute
  20. * linked combinations including the program with the OpenSSL library. You
  21. * must comply with the Server Side Public License in all respects for
  22. * all of the code used other than as permitted herein. If you modify file(s)
  23. * with this exception, you may extend this exception to your version of the
  24. * file(s), but you are not obligated to do so. If you do not wish to do so,
  25. * delete this exception statement from your version. If you delete this
  26. * exception statement from all source files in the program, then also delete
  27. * it in the license file.
  28. */
  29. #pragma once
  30. #include <cmath>
  31. #include <limits>
  32. #include <type_traits>
  33. #include <boost/optional.hpp>
  34. #include "mongo/base/static_assert.h"
  35. #include "mongo/stdx/type_traits.h"
  36. namespace mongo {
  37. namespace detail {
  38. /**
  39. * The following three methods are conversion helpers that allow us to promote
  40. * all numerical input to three top-level types: int64_t, uint64_t, and double.
  41. */
  42. // Floating point numbers -> double
  43. template <typename T>
  44. typename stdx::enable_if_t<std::is_floating_point<T>::value, double> upconvert(T t) {
  45. MONGO_STATIC_ASSERT(sizeof(double) >= sizeof(T));
  46. return static_cast<double>(t);
  47. }
  48. // Signed integral types -> int64_t
  49. template <typename T>
  50. typename stdx::enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, int64_t>
  51. upconvert(T t) {
  52. MONGO_STATIC_ASSERT(sizeof(int64_t) >= sizeof(T));
  53. return static_cast<int64_t>(t);
  54. }
  55. // Unsigned integral types -> uint64_t
  56. template <typename T>
  57. typename stdx::enable_if_t<std::is_integral<T>::value && !std::is_signed<T>::value, uint64_t>
  58. upconvert(T t) {
  59. MONGO_STATIC_ASSERT(sizeof(uint64_t) >= sizeof(T));
  60. return static_cast<uint64_t>(t);
  61. }
  62. /**
  63. * Compare two values of the same type. Return -1 if a < b, 0 if they are equal, and
  64. * 1 if a > b.
  65. */
  66. template <typename T>
  67. int identityCompare(T a, T b) {
  68. if (a == b) {
  69. return 0;
  70. }
  71. return (a < b) ? -1 : 1;
  72. }
  73. inline int signedCompare(int64_t a, int64_t b) {
  74. return identityCompare(a, b);
  75. }
  76. inline int signedCompare(double a, double b) {
  77. return identityCompare(a, b);
  78. }
  79. inline int signedCompare(uint64_t a, uint64_t b) {
  80. return identityCompare(a, b);
  81. }
  82. /**
  83. * Compare unsigned and signed integers.
  84. */
  85. inline int signedCompare(int64_t a, uint64_t b) {
  86. if (a < 0) {
  87. return -1;
  88. }
  89. auto aUnsigned = static_cast<uint64_t>(a);
  90. return signedCompare(aUnsigned, b);
  91. }
  92. inline int signedCompare(uint64_t a, int64_t b) {
  93. return -signedCompare(b, a);
  94. }
  95. /**
  96. * Compare doubles and signed integers.
  97. */
  98. inline int signedCompare(double a, int64_t b) {
  99. // Casting int64_ts to doubles will round them
  100. // and give the wrong result, so convert doubles to
  101. // int64_ts if we can, then do the comparison.
  102. if (a < -std::ldexp(1, 63)) {
  103. return -1;
  104. } else if (a >= std::ldexp(1, 63)) {
  105. return 1;
  106. }
  107. auto aAsInt64 = static_cast<int64_t>(a);
  108. return signedCompare(aAsInt64, b);
  109. }
  110. inline int signedCompare(int64_t a, double b) {
  111. return -signedCompare(b, a);
  112. }
  113. /**
  114. * Compare doubles and unsigned integers.
  115. */
  116. inline int signedCompare(double a, uint64_t b) {
  117. if (a < 0) {
  118. return -1;
  119. }
  120. // Casting uint64_ts to doubles will round them
  121. // and give the wrong result, so convert doubles to
  122. // uint64_ts if we can, then do the comparison.
  123. if (a >= std::ldexp(1, 64)) {
  124. return 1;
  125. }
  126. auto aAsUInt64 = static_cast<uint64_t>(a);
  127. return signedCompare(aAsUInt64, b);
  128. }
  129. inline int signedCompare(uint64_t a, double b) {
  130. return -signedCompare(b, a);
  131. }
  132. /**
  133. * For any t and u of types T and U, promote t and u to one of the
  134. * top-level numerical types (int64_t, uint64_t, and double) and
  135. * compare them.
  136. *
  137. * Return -1 if t < u, 0 if they are equal, 1 if t > u.
  138. */
  139. template <typename T, typename U>
  140. int compare(T t, U u) {
  141. return signedCompare(upconvert(t), upconvert(u));
  142. }
  143. } // namespace detail
  144. /**
  145. * Given a number of some type Input and a desired numerical type Output,
  146. * this method represents the input number in the output type if possible.
  147. * If the given number cannot be exactly represented in the output type,
  148. * this method returns a disengaged optional.
  149. *
  150. * ex:
  151. * auto v1 = representAs<int>(2147483647); // v1 holds 2147483647
  152. * auto v2 = representAs<int>(2147483648); // v2 is disengaged
  153. * auto v3 = representAs<int>(10.3); // v3 is disengaged
  154. */
  155. template <typename Output, typename Input>
  156. boost::optional<Output> representAs(Input number) {
  157. if (std::is_same<Input, Output>::value) {
  158. return {static_cast<Output>(number)};
  159. }
  160. // If number is NaN and Output can also represent NaN, return NaN
  161. // Note: We need to specifically handle NaN here because of the way
  162. // detail::compare is implemented.
  163. {
  164. // We use ADL here to allow types, such as Decimal, to supply their
  165. // own definitions of isnan(). If the Input type does not define a
  166. // custom isnan(), then we fall back to using std::isnan().
  167. using std::isnan;
  168. if (std::is_floating_point<Input>::value && isnan(number)) {
  169. if (std::is_floating_point<Output>::value) {
  170. return {static_cast<Output>(number)};
  171. }
  172. }
  173. }
  174. // If Output is integral and number is a non-integral floating point value,
  175. // return a disengaged optional.
  176. if (std::is_floating_point<Input>::value && std::is_integral<Output>::value) {
  177. if (!(std::trunc(number) == number)) {
  178. return {};
  179. }
  180. }
  181. const auto floor = std::numeric_limits<Output>::lowest();
  182. const auto ceiling = std::numeric_limits<Output>::max();
  183. // If number is out-of-bounds for Output type, fail.
  184. if ((detail::compare(number, floor) < 0) || (detail::compare(number, ceiling) > 0)) {
  185. return {};
  186. }
  187. // Our number is within bounds, safe to perform a static_cast.
  188. auto numberOut = static_cast<Output>(number);
  189. // Some integers cannot be exactly represented as floating point numbers.
  190. // To check, we cast back to the input type if we can, and compare.
  191. if (std::is_integral<Input>::value && std::is_floating_point<Output>::value) {
  192. const auto inputFloor = std::numeric_limits<Input>::lowest();
  193. const auto inputCeiling = std::numeric_limits<Input>::max();
  194. // If it is not safe to cast back to the Input type, fail.
  195. if ((detail::compare(numberOut, inputFloor) < 0) ||
  196. (detail::compare(numberOut, inputCeiling) > 0)) {
  197. return {};
  198. }
  199. if (number != static_cast<Input>(numberOut)) {
  200. return {};
  201. }
  202. }
  203. return {static_cast<Output>(numberOut)};
  204. }
  205. } // namespace mongo