fixed_point.hpp
Go to the documentation of this file.
1 /*
2  * SPDX-FileCopyrightText: Copyright (c) 2020-2026, 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/numeric>
13 #include <cuda/std/cassert>
14 #include <cuda/std/cmath>
15 #include <cuda/std/functional>
16 #include <cuda/std/limits>
17 #include <cuda/std/type_traits>
18 #include <cuda/std/utility>
19 
20 #ifndef __CUDACC_RTC__
21 #include <algorithm>
22 #endif
23 
25 namespace CUDF_EXPORT numeric {
26 
35 enum scale_type : int32_t {};
36 
46 enum class Radix : int32_t { BASE_2 = 2, BASE_10 = 10 };
47 
54 template <typename T>
56 {
57  return cuda::std::is_same_v<T, int32_t> || //
58  cuda::std::is_same_v<T, int64_t> || //
59  cuda::std::is_same_v<T, __int128_t>;
60 }
61  // end of group
63 
64 // Helper functions for `fixed_point` type
65 namespace detail {
66 
75 template <typename Rep,
76  Radix Base,
77  typename T,
78  typename cuda::std::enable_if_t<(cuda::std::is_same_v<int32_t, T> &&
79  cuda::std::is_integral_v<Rep>)>* = nullptr>
80 CUDF_HOST_DEVICE inline constexpr Rep ipow(T exponent)
81 {
82  cudf_assert(exponent >= 0 && "integer exponentiation with negative exponent is not possible.");
83 
84  if constexpr (Base == numeric::Radix::BASE_2) { return static_cast<Rep>(1) << exponent; }
85 
86  // Note: Including an array here introduces too much register pressure
87  // https://simple.wikipedia.org/wiki/Exponentiation_by_squaring
88  // This is the iterative equivalent of the recursive definition (faster)
89  // Quick-bench for squaring: http://quick-bench.com/Wg7o7HYQC9FW5M0CO0wQAjSwP_Y
90  if (exponent == 0) { return static_cast<Rep>(1); }
91  auto extra = static_cast<Rep>(1);
92  auto square = static_cast<Rep>(Base);
93  while (exponent > 1) {
94  if (exponent & 1) { extra *= square; }
95  exponent >>= 1;
96  square *= square;
97  }
98  return square * extra;
99 }
100 
112 template <typename Rep, Radix Rad, typename T>
113 CUDF_HOST_DEVICE inline constexpr T right_shift(T const& val, scale_type const& scale)
114 {
115  return val / ipow<Rep, Rad>(static_cast<int32_t>(scale));
116 }
117 
129 template <typename Rep, Radix Rad, typename T>
130 CUDF_HOST_DEVICE inline constexpr T left_shift(T const& val, scale_type const& scale)
131 {
132  return val * ipow<Rep, Rad>(static_cast<int32_t>(-scale));
133 }
134 
148 template <typename Rep, Radix Rad, typename T>
149 CUDF_HOST_DEVICE inline constexpr T shift(T const& val, scale_type const& scale)
150 {
151  if (scale == 0) { return val; }
152  if (scale > 0) { return right_shift<Rep, Rad>(val, scale); }
153  return left_shift<Rep, Rad>(val, scale);
154 }
155 
156 } // namespace detail
157 
176 template <typename Rep,
177  typename cuda::std::enable_if_t<is_supported_representation_type<Rep>()>* = nullptr>
179  Rep value;
187  CUDF_HOST_DEVICE inline explicit scaled_integer(Rep v, scale_type s) : value{v}, scale{s} {}
188 };
189 
199 template <typename Rep, Radix Rad>
200 class fixed_point {
201  Rep _value{};
202  scale_type _scale;
203 
204  public:
205  using rep = Rep;
206  static constexpr auto rad = Rad;
207 
216  template <typename T,
217  typename cuda::std::enable_if_t<cuda::std::is_integral_v<T> &&
218  is_supported_representation_type<Rep>()>* = nullptr>
219  CUDF_HOST_DEVICE inline explicit fixed_point(T const& value, scale_type const& scale)
220  // `value` is cast to `Rep` to avoid overflow in cases where
221  // constructing to `Rep` that is wider than `T`
222  : _value{detail::shift<Rep, Rad>(static_cast<Rep>(value), scale)}, _scale{scale}
223  {
224  }
225 
232  : _value{s.value}, _scale{s.scale}
233  {
234  }
235 
243  template <typename T, typename cuda::std::enable_if_t<cuda::std::is_integral_v<T>>* = nullptr>
244  CUDF_HOST_DEVICE inline fixed_point(T const& value)
245  : _value{static_cast<Rep>(value)}, _scale{scale_type{0}}
246  {
247  }
248 
253  CUDF_HOST_DEVICE inline fixed_point() : _scale{scale_type{0}} {}
254 
261  template <typename U, typename cuda::std::enable_if_t<cuda::std::is_integral_v<U>>* = nullptr>
262  CUDF_HOST_DEVICE explicit constexpr operator U() const
263  {
264  // Cast to the larger of the two types (of U and Rep) before converting to Rep because in
265  // certain cases casting to U before shifting will result in integer overflow (i.e. if U =
266  // int32_t, Rep = int64_t and _value > 2 billion)
267  auto const value = cuda::std::common_type_t<U, Rep>(_value);
268  return static_cast<U>(detail::shift<Rep, Rad>(value, scale_type{-_scale}));
269  }
270 
276  CUDF_HOST_DEVICE inline operator scaled_integer<Rep>() const
277  {
278  return scaled_integer<Rep>{_value, _scale};
279  }
280 
286  CUDF_HOST_DEVICE [[nodiscard]] inline rep value() const { return _value; }
287 
293  CUDF_HOST_DEVICE [[nodiscard]] inline scale_type scale() const { return _scale; }
294 
300  CUDF_HOST_DEVICE inline explicit constexpr operator bool() const
301  {
302  return static_cast<bool>(_value);
303  }
304 
313  template <typename Rep1, Radix Rad1>
315  {
316  *this = *this + rhs;
317  return *this;
318  }
319 
328  template <typename Rep1, Radix Rad1>
330  {
331  *this = *this * rhs;
332  return *this;
333  }
334 
343  template <typename Rep1, Radix Rad1>
345  {
346  *this = *this - rhs;
347  return *this;
348  }
349 
358  template <typename Rep1, Radix Rad1>
360  {
361  *this = *this / rhs;
362  return *this;
363  }
364 
371  {
372  *this = *this + fixed_point<Rep, Rad>{1, scale_type{_scale}};
373  return *this;
374  }
375 
389  template <typename Rep1, Radix Rad1>
391  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
392 
406  template <typename Rep1, Radix Rad1>
408  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
409 
421  template <typename Rep1, Radix Rad1>
423  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
424 
436  template <typename Rep1, Radix Rad1>
438  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
439 
453  template <typename Rep1, Radix Rad1>
455  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
456 
470  template <typename Rep1, Radix Rad1>
471  CUDF_HOST_DEVICE inline friend bool operator==(fixed_point<Rep1, Rad1> const& lhs,
472  fixed_point<Rep1, Rad1> const& rhs);
473 
487  template <typename Rep1, Radix Rad1>
488  CUDF_HOST_DEVICE inline friend bool operator!=(fixed_point<Rep1, Rad1> const& lhs,
489  fixed_point<Rep1, Rad1> const& rhs);
490 
504  template <typename Rep1, Radix Rad1>
505  CUDF_HOST_DEVICE inline friend bool operator<=(fixed_point<Rep1, Rad1> const& lhs,
506  fixed_point<Rep1, Rad1> const& rhs);
507 
521  template <typename Rep1, Radix Rad1>
522  CUDF_HOST_DEVICE inline friend bool operator>=(fixed_point<Rep1, Rad1> const& lhs,
523  fixed_point<Rep1, Rad1> const& rhs);
524 
538  template <typename Rep1, Radix Rad1>
539  CUDF_HOST_DEVICE inline friend bool operator<(fixed_point<Rep1, Rad1> const& lhs,
540  fixed_point<Rep1, Rad1> const& rhs);
541 
555  template <typename Rep1, Radix Rad1>
556  CUDF_HOST_DEVICE inline friend bool operator>(fixed_point<Rep1, Rad1> const& lhs,
557  fixed_point<Rep1, Rad1> const& rhs);
558 
568  CUDF_HOST_DEVICE [[nodiscard]] inline fixed_point<Rep, Rad> rescaled(scale_type scale) const
569  {
570  if (scale == _scale) { return *this; }
571  Rep const value = detail::shift<Rep, Rad>(_value, scale_type{scale - _scale});
572  return fixed_point<Rep, Rad>{scaled_integer<Rep>{value, scale}};
573  }
574 
575 #ifndef __CUDACC_RTC__
576 
580  explicit operator std::string() const
581  {
582  if (_scale < 0) {
583  auto const av = detail::abs(_value);
584  Rep const n = detail::exp10<Rep>(-_scale);
585  Rep const f = av % n;
586  auto const num_zeros =
587  std::max(0, (-_scale - static_cast<int32_t>(detail::to_string(f).size())));
588  auto const zeros = std::string(num_zeros, '0');
589  auto const sign = _value < 0 ? std::string("-") : std::string();
590  return sign + detail::to_string(av / n) + std::string(".") + zeros +
591  detail::to_string(av % n);
592  }
593  auto const zeros = std::string(_scale, '0');
594  return detail::to_string(_value) + zeros;
595  }
596 
597 #endif
598 };
599 
609 template <typename Rep, typename T>
610 CUDF_HOST_DEVICE inline auto addition_overflow(T lhs, T rhs)
611 {
612  return cuda::add_overflow<Rep>(lhs, rhs).overflow;
613 }
614 
623 template <typename Rep, typename T>
624 CUDF_HOST_DEVICE inline auto subtraction_overflow(T lhs, T rhs)
625 {
626  return cuda::sub_overflow<Rep>(lhs, rhs).overflow;
627 }
628 
637 template <typename Rep, typename T>
638 CUDF_HOST_DEVICE inline auto division_overflow(T lhs, T rhs)
639 {
640  return cuda::div_overflow<Rep>(lhs, rhs).overflow;
641 }
642 
651 template <typename Rep, typename T>
652 CUDF_HOST_DEVICE inline auto multiplication_overflow(T lhs, T rhs)
653 {
654  return cuda::mul_overflow<Rep>(lhs, rhs).overflow;
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 
787 template <typename Rep>
788 using decimal =
793  // end of group
795 } // 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:80
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:46
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:35
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:55
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:25
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