fixed_point.hpp
Go to the documentation of this file.
1 /*
2  * SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION.
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 
6 #pragma once
7 
8 #include <cudf/detail/utilities/assert.cuh>
9 #include <cudf/fixed_point/temporary.hpp>
10 #include <cudf/types.hpp>
11 
12 #include <cuda/std/functional>
13 #include <cuda/std/limits>
14 #include <cuda/std/type_traits>
15 #include <cuda/std/utility>
16 
17 #include <algorithm>
18 #include <cassert>
19 #include <cmath>
20 #include <string>
21 
23 namespace CUDF_EXPORT numeric {
24 
33 enum scale_type : int32_t {};
34 
44 enum class Radix : int32_t { BASE_2 = 2, BASE_10 = 10 };
45 
52 template <typename T>
54 {
55  return cuda::std::is_same_v<T, int32_t> || //
56  cuda::std::is_same_v<T, int64_t> || //
57  cuda::std::is_same_v<T, __int128_t>;
58 }
59  // end of group
61 
62 // Helper functions for `fixed_point` type
63 namespace detail {
64 
73 template <typename Rep,
74  Radix Base,
75  typename T,
76  typename cuda::std::enable_if_t<(cuda::std::is_same_v<int32_t, T> &&
77  cuda::std::is_integral_v<Rep>)>* = nullptr>
78 CUDF_HOST_DEVICE inline constexpr Rep ipow(T exponent)
79 {
80  cudf_assert(exponent >= 0 && "integer exponentiation with negative exponent is not possible.");
81 
82  if constexpr (Base == numeric::Radix::BASE_2) { return static_cast<Rep>(1) << exponent; }
83 
84  // Note: Including an array here introduces too much register pressure
85  // https://simple.wikipedia.org/wiki/Exponentiation_by_squaring
86  // This is the iterative equivalent of the recursive definition (faster)
87  // Quick-bench for squaring: http://quick-bench.com/Wg7o7HYQC9FW5M0CO0wQAjSwP_Y
88  if (exponent == 0) { return static_cast<Rep>(1); }
89  auto extra = static_cast<Rep>(1);
90  auto square = static_cast<Rep>(Base);
91  while (exponent > 1) {
92  if (exponent & 1) { extra *= square; }
93  exponent >>= 1;
94  square *= square;
95  }
96  return square * extra;
97 }
98 
110 template <typename Rep, Radix Rad, typename T>
111 CUDF_HOST_DEVICE inline constexpr T right_shift(T const& val, scale_type const& scale)
112 {
113  return val / ipow<Rep, Rad>(static_cast<int32_t>(scale));
114 }
115 
127 template <typename Rep, Radix Rad, typename T>
128 CUDF_HOST_DEVICE inline constexpr T left_shift(T const& val, scale_type const& scale)
129 {
130  return val * ipow<Rep, Rad>(static_cast<int32_t>(-scale));
131 }
132 
146 template <typename Rep, Radix Rad, typename T>
147 CUDF_HOST_DEVICE inline constexpr T shift(T const& val, scale_type const& scale)
148 {
149  if (scale == 0) { return val; }
150  if (scale > 0) { return right_shift<Rep, Rad>(val, scale); }
151  return left_shift<Rep, Rad>(val, scale);
152 }
153 
154 } // namespace detail
155 
174 template <typename Rep,
175  typename cuda::std::enable_if_t<is_supported_representation_type<Rep>()>* = nullptr>
177  Rep value;
185  CUDF_HOST_DEVICE inline explicit scaled_integer(Rep v, scale_type s) : value{v}, scale{s} {}
186 };
187 
197 template <typename Rep, Radix Rad>
198 class fixed_point {
199  Rep _value{};
200  scale_type _scale;
201 
202  public:
203  using rep = Rep;
204  static constexpr auto rad = Rad;
205 
214  template <typename T,
215  typename cuda::std::enable_if_t<cuda::std::is_integral_v<T> &&
216  is_supported_representation_type<Rep>()>* = nullptr>
217  CUDF_HOST_DEVICE inline explicit fixed_point(T const& value, scale_type const& scale)
218  // `value` is cast to `Rep` to avoid overflow in cases where
219  // constructing to `Rep` that is wider than `T`
220  : _value{detail::shift<Rep, Rad>(static_cast<Rep>(value), scale)}, _scale{scale}
221  {
222  }
223 
230  : _value{s.value}, _scale{s.scale}
231  {
232  }
233 
241  template <typename T, typename cuda::std::enable_if_t<cuda::std::is_integral_v<T>>* = nullptr>
242  CUDF_HOST_DEVICE inline fixed_point(T const& value)
243  : _value{static_cast<Rep>(value)}, _scale{scale_type{0}}
244  {
245  }
246 
251  CUDF_HOST_DEVICE inline fixed_point() : _scale{scale_type{0}} {}
252 
259  template <typename U, typename cuda::std::enable_if_t<cuda::std::is_integral_v<U>>* = nullptr>
260  CUDF_HOST_DEVICE explicit constexpr operator U() const
261  {
262  // Cast to the larger of the two types (of U and Rep) before converting to Rep because in
263  // certain cases casting to U before shifting will result in integer overflow (i.e. if U =
264  // int32_t, Rep = int64_t and _value > 2 billion)
265  auto const value = cuda::std::common_type_t<U, Rep>(_value);
266  return static_cast<U>(detail::shift<Rep, Rad>(value, scale_type{-_scale}));
267  }
268 
274  CUDF_HOST_DEVICE inline operator scaled_integer<Rep>() const
275  {
276  return scaled_integer<Rep>{_value, _scale};
277  }
278 
284  CUDF_HOST_DEVICE [[nodiscard]] inline rep value() const { return _value; }
285 
291  CUDF_HOST_DEVICE [[nodiscard]] inline scale_type scale() const { return _scale; }
292 
298  CUDF_HOST_DEVICE inline explicit constexpr operator bool() const
299  {
300  return static_cast<bool>(_value);
301  }
302 
311  template <typename Rep1, Radix Rad1>
313  {
314  *this = *this + rhs;
315  return *this;
316  }
317 
326  template <typename Rep1, Radix Rad1>
328  {
329  *this = *this * rhs;
330  return *this;
331  }
332 
341  template <typename Rep1, Radix Rad1>
343  {
344  *this = *this - rhs;
345  return *this;
346  }
347 
356  template <typename Rep1, Radix Rad1>
358  {
359  *this = *this / rhs;
360  return *this;
361  }
362 
369  {
370  *this = *this + fixed_point<Rep, Rad>{1, scale_type{_scale}};
371  return *this;
372  }
373 
387  template <typename Rep1, Radix Rad1>
389  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
390 
404  template <typename Rep1, Radix Rad1>
406  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
407 
419  template <typename Rep1, Radix Rad1>
421  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
422 
434  template <typename Rep1, Radix Rad1>
436  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
437 
451  template <typename Rep1, Radix Rad1>
453  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
454 
468  template <typename Rep1, Radix Rad1>
469  CUDF_HOST_DEVICE inline friend bool operator==(fixed_point<Rep1, Rad1> const& lhs,
470  fixed_point<Rep1, Rad1> const& rhs);
471 
485  template <typename Rep1, Radix Rad1>
486  CUDF_HOST_DEVICE inline friend bool operator!=(fixed_point<Rep1, Rad1> const& lhs,
487  fixed_point<Rep1, Rad1> const& rhs);
488 
502  template <typename Rep1, Radix Rad1>
503  CUDF_HOST_DEVICE inline friend bool operator<=(fixed_point<Rep1, Rad1> const& lhs,
504  fixed_point<Rep1, Rad1> const& rhs);
505 
519  template <typename Rep1, Radix Rad1>
520  CUDF_HOST_DEVICE inline friend bool operator>=(fixed_point<Rep1, Rad1> const& lhs,
521  fixed_point<Rep1, Rad1> const& rhs);
522 
536  template <typename Rep1, Radix Rad1>
537  CUDF_HOST_DEVICE inline friend bool operator<(fixed_point<Rep1, Rad1> const& lhs,
538  fixed_point<Rep1, Rad1> const& rhs);
539 
553  template <typename Rep1, Radix Rad1>
554  CUDF_HOST_DEVICE inline friend bool operator>(fixed_point<Rep1, Rad1> const& lhs,
555  fixed_point<Rep1, Rad1> const& rhs);
556 
566  CUDF_HOST_DEVICE [[nodiscard]] inline fixed_point<Rep, Rad> rescaled(scale_type scale) const
567  {
568  if (scale == _scale) { return *this; }
569  Rep const value = detail::shift<Rep, Rad>(_value, scale_type{scale - _scale});
570  return fixed_point<Rep, Rad>{scaled_integer<Rep>{value, scale}};
571  }
572 
576  explicit operator std::string() const
577  {
578  if (_scale < 0) {
579  auto const av = detail::abs(_value);
580  Rep const n = detail::exp10<Rep>(-_scale);
581  Rep const f = av % n;
582  auto const num_zeros =
583  std::max(0, (-_scale - static_cast<int32_t>(detail::to_string(f).size())));
584  auto const zeros = std::string(num_zeros, '0');
585  auto const sign = _value < 0 ? std::string("-") : std::string();
586  return sign + detail::to_string(av / n) + std::string(".") + zeros +
587  detail::to_string(av % n);
588  }
589  auto const zeros = std::string(_scale, '0');
590  return detail::to_string(_value) + zeros;
591  }
592 };
593 
603 template <typename Rep, typename T>
604 CUDF_HOST_DEVICE inline auto addition_overflow(T lhs, T rhs)
605 {
606  return rhs > 0 ? lhs > cuda::std::numeric_limits<Rep>::max() - rhs
607  : lhs < cuda::std::numeric_limits<Rep>::min() - rhs;
608 }
609 
618 template <typename Rep, typename T>
619 CUDF_HOST_DEVICE inline auto subtraction_overflow(T lhs, T rhs)
620 {
621  return rhs > 0 ? lhs < cuda::std::numeric_limits<Rep>::min() + rhs
622  : lhs > cuda::std::numeric_limits<Rep>::max() + rhs;
623 }
624 
633 template <typename Rep, typename T>
634 CUDF_HOST_DEVICE inline auto division_overflow(T lhs, T rhs)
635 {
636  return lhs == cuda::std::numeric_limits<Rep>::min() && rhs == -1;
637 }
638 
647 template <typename Rep, typename T>
648 CUDF_HOST_DEVICE inline auto multiplication_overflow(T lhs, T rhs)
649 {
650  auto const min = cuda::std::numeric_limits<Rep>::min();
651  auto const max = cuda::std::numeric_limits<Rep>::max();
652  if (rhs > 0) { return lhs > max / rhs || lhs < min / rhs; }
653  if (rhs < -1) { return lhs > min / rhs || lhs < max / rhs; }
654  return rhs == -1 && lhs == min;
655 }
656 
657 // PLUS Operation
658 template <typename Rep1, Radix Rad1>
660  fixed_point<Rep1, Rad1> const& rhs)
661 {
662  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
663  auto const sum = lhs.rescaled(scale)._value + rhs.rescaled(scale)._value;
664 
665 #if defined(__CUDACC_DEBUG__)
666 
667  assert(!addition_overflow<Rep1>(lhs.rescaled(scale)._value, rhs.rescaled(scale)._value) &&
668  "fixed_point overflow");
669 
670 #endif
671 
672  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{sum, scale}};
673 }
674 
675 // MINUS Operation
676 template <typename Rep1, Radix Rad1>
678  fixed_point<Rep1, Rad1> const& rhs)
679 {
680  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
681  auto const diff = lhs.rescaled(scale)._value - rhs.rescaled(scale)._value;
682 
683 #if defined(__CUDACC_DEBUG__)
684 
685  assert(!subtraction_overflow<Rep1>(lhs.rescaled(scale)._value, rhs.rescaled(scale)._value) &&
686  "fixed_point overflow");
687 
688 #endif
689 
690  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{diff, scale}};
691 }
692 
693 // MULTIPLIES Operation
694 template <typename Rep1, Radix Rad1>
696  fixed_point<Rep1, Rad1> const& rhs)
697 {
698 #if defined(__CUDACC_DEBUG__)
699 
700  assert(!multiplication_overflow<Rep1>(lhs._value, rhs._value) && "fixed_point overflow");
701 
702 #endif
703 
705  scaled_integer<Rep1>(lhs._value * rhs._value, scale_type{lhs._scale + rhs._scale})};
706 }
707 
708 // DIVISION Operation
709 template <typename Rep1, Radix Rad1>
711  fixed_point<Rep1, Rad1> const& rhs)
712 {
713 #if defined(__CUDACC_DEBUG__)
714 
715  assert(!division_overflow<Rep1>(lhs._value, rhs._value) && "fixed_point overflow");
716 
717 #endif
718 
720  scaled_integer<Rep1>(lhs._value / rhs._value, scale_type{lhs._scale - rhs._scale})};
721 }
722 
723 // EQUALITY COMPARISON Operation
724 template <typename Rep1, Radix Rad1>
726  fixed_point<Rep1, Rad1> const& rhs)
727 {
728  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
729  return lhs.rescaled(scale)._value == rhs.rescaled(scale)._value;
730 }
731 
732 // EQUALITY NOT COMPARISON Operation
733 template <typename Rep1, Radix Rad1>
735  fixed_point<Rep1, Rad1> const& rhs)
736 {
737  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
738  return lhs.rescaled(scale)._value != rhs.rescaled(scale)._value;
739 }
740 
741 // LESS THAN OR EQUAL TO Operation
742 template <typename Rep1, Radix Rad1>
744  fixed_point<Rep1, Rad1> const& rhs)
745 {
746  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
747  return lhs.rescaled(scale)._value <= rhs.rescaled(scale)._value;
748 }
749 
750 // GREATER THAN OR EQUAL TO Operation
751 template <typename Rep1, Radix Rad1>
753  fixed_point<Rep1, Rad1> const& rhs)
754 {
755  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
756  return lhs.rescaled(scale)._value >= rhs.rescaled(scale)._value;
757 }
758 
759 // LESS THAN Operation
760 template <typename Rep1, Radix Rad1>
762  fixed_point<Rep1, Rad1> const& rhs)
763 {
764  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
765  return lhs.rescaled(scale)._value < rhs.rescaled(scale)._value;
766 }
767 
768 // GREATER THAN Operation
769 template <typename Rep1, Radix Rad1>
771  fixed_point<Rep1, Rad1> const& rhs)
772 {
773  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
774  return lhs.rescaled(scale)._value > rhs.rescaled(scale)._value;
775 }
776 
777 // MODULO OPERATION
778 template <typename Rep1, Radix Rad1>
780  fixed_point<Rep1, Rad1> const& rhs)
781 {
782  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
783  auto const remainder = lhs.rescaled(scale)._value % rhs.rescaled(scale)._value;
784  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{remainder, scale}};
785 }
786 
790  // end of group
792 } // namespace CUDF_EXPORT numeric
A type for representing a number with a fixed amount of precision.
CUDF_HOST_DEVICE fixed_point(scaled_integer< Rep > s)
Constructor that will not perform shifting (assumes value already shifted)
CUDF_HOST_DEVICE fixed_point< Rep, Rad > rescaled(scale_type scale) const
Method for creating a fixed_point number with a new scale
CUDF_HOST_DEVICE rep value() const
Method that returns the underlying value of the fixed_point number.
CUDF_HOST_DEVICE fixed_point< Rep1, Rad1 > & operator*=(fixed_point< Rep1, Rad1 > const &rhs)
operator *=
CUDF_HOST_DEVICE scale_type scale() const
Method that returns the scale of the fixed_point number.
CUDF_HOST_DEVICE fixed_point(T const &value)
"Scale-less" constructor that constructs fixed_point number with a specified value and scale of zero
CUDF_HOST_DEVICE fixed_point< Rep1, Rad1 > & operator-=(fixed_point< Rep1, Rad1 > const &rhs)
operator -=
CUDF_HOST_DEVICE fixed_point< Rep1, Rad1 > & operator+=(fixed_point< Rep1, Rad1 > const &rhs)
operator +=
Rep rep
The representation type.
CUDF_HOST_DEVICE fixed_point()
Default constructor that constructs fixed_point number with a value and scale of zero.
CUDF_HOST_DEVICE fixed_point< Rep1, Rad1 > & operator/=(fixed_point< Rep1, Rad1 > const &rhs)
operator /=
CUDF_HOST_DEVICE fixed_point(T const &value, scale_type const &scale)
Constructor that will perform shifting to store value appropriately (from integral types)
CUDF_HOST_DEVICE fixed_point< Rep, Rad > & operator++()
operator ++ (post-increment)
constexpr CUDF_HOST_DEVICE T left_shift(T const &val, scale_type const &scale)
Function that performs a left shift scale "times" on the val
constexpr CUDF_HOST_DEVICE Rep ipow(T exponent)
A function for integer exponentiation by squaring.
Definition: fixed_point.hpp:78
constexpr CUDF_HOST_DEVICE T right_shift(T const &val, scale_type const &scale)
Function that performs a right shift scale "times" on the val
Radix
Scoped enumerator to use when constructing fixed_point
Definition: fixed_point.hpp:44
CUDF_HOST_DEVICE fixed_point< Rep1, Rad1 > operator-(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
CUDF_HOST_DEVICE bool operator>=(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
CUDF_HOST_DEVICE bool operator<=(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
CUDF_HOST_DEVICE bool operator==(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
CUDF_HOST_DEVICE fixed_point< Rep1, Rad1 > operator%(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
CUDF_HOST_DEVICE auto division_overflow(T lhs, T rhs)
Function for identifying integer overflow when dividing.
CUDF_HOST_DEVICE fixed_point< Rep1, Rad1 > operator/(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
scale_type
The scale type for fixed_point.
Definition: fixed_point.hpp:33
CUDF_HOST_DEVICE fixed_point< Rep1, Rad1 > operator*(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
CUDF_HOST_DEVICE bool operator>(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
CUDF_HOST_DEVICE auto addition_overflow(T lhs, T rhs)
Function for identifying integer overflow when adding.
CUDF_HOST_DEVICE fixed_point< Rep1, Rad1 > operator+(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
CUDF_HOST_DEVICE auto multiplication_overflow(T lhs, T rhs)
Function for identifying integer overflow when multiplying.
constexpr CUDF_HOST_DEVICE auto is_supported_representation_type()
Returns true if the representation type is supported by fixed_point
Definition: fixed_point.hpp:53
CUDF_HOST_DEVICE bool operator!=(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
CUDF_HOST_DEVICE auto subtraction_overflow(T lhs, T rhs)
Function for identifying integer overflow when subtracting.
CUDF_HOST_DEVICE bool operator<(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
fixed_point and supporting types
Definition: fixed_point.hpp:23
Helper struct for constructing fixed_point when value is already shifted.
Rep value
The value of the fixed point number.
CUDF_HOST_DEVICE scaled_integer(Rep v, scale_type s)
Constructor for scaled_integer
scale_type scale
The scale of the value.
Type declarations for libcudf.
#define CUDF_HOST_DEVICE
Indicates that the function or method is usable on host and device.
Definition: types.hpp:21