fixed_point.hpp
Go to the documentation of this file.
1 /*
2  * SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
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 
30 namespace CUDF_EXPORT numeric {
31 
38 enum scale_type : int32_t {};
39 
49 enum class Radix : int32_t { BASE_2 = 2, BASE_10 = 10 };
50 
57 template <typename T>
59 {
60  return cuda::std::is_same_v<T, int32_t> || //
61  cuda::std::is_same_v<T, int64_t> || //
62  cuda::std::is_same_v<T, __int128_t>;
63 }
64  // end of group
66 
67 // Helper functions for `fixed_point` type
68 namespace detail {
69 
78 template <typename Rep,
79  Radix Base,
80  typename T,
81  typename cuda::std::enable_if_t<(cuda::std::is_same_v<int32_t, T> &&
82  cuda::std::is_integral_v<Rep>)>* = nullptr>
83 CUDF_HOST_DEVICE inline constexpr Rep ipow(T exponent)
84 {
85  cudf_assert(exponent >= 0 && "integer exponentiation with negative exponent is not possible.");
86 
87  if constexpr (Base == numeric::Radix::BASE_2) { return static_cast<Rep>(1) << exponent; }
88 
89  // Note: Including an array here introduces too much register pressure
90  // https://simple.wikipedia.org/wiki/Exponentiation_by_squaring
91  // This is the iterative equivalent of the recursive definition (faster)
92  // Quick-bench for squaring: http://quick-bench.com/Wg7o7HYQC9FW5M0CO0wQAjSwP_Y
93  if (exponent == 0) { return static_cast<Rep>(1); }
94  auto extra = static_cast<Rep>(1);
95  auto square = static_cast<Rep>(Base);
96  while (exponent > 1) {
97  if (exponent & 1) { extra *= square; }
98  exponent >>= 1;
99  square *= square;
100  }
101  return square * extra;
102 }
103 
115 template <typename Rep, Radix Rad, typename T>
116 CUDF_HOST_DEVICE inline constexpr T right_shift(T const& val, scale_type const& scale)
117 {
118  return val / ipow<Rep, Rad>(static_cast<int32_t>(scale));
119 }
120 
132 template <typename Rep, Radix Rad, typename T>
133 CUDF_HOST_DEVICE inline constexpr T left_shift(T const& val, scale_type const& scale)
134 {
135  return val * ipow<Rep, Rad>(static_cast<int32_t>(-scale));
136 }
137 
151 template <typename Rep, Radix Rad, typename T>
152 CUDF_HOST_DEVICE inline constexpr T shift(T const& val, scale_type const& scale)
153 {
154  if (scale == 0) { return val; }
155  if (scale > 0) { return right_shift<Rep, Rad>(val, scale); }
156  return left_shift<Rep, Rad>(val, scale);
157 }
158 
159 } // namespace detail
160 
177 template <typename Rep,
178  typename cuda::std::enable_if_t<is_supported_representation_type<Rep>()>* = nullptr>
180  Rep value;
188  CUDF_HOST_DEVICE inline explicit scaled_integer(Rep v, scale_type s) : value{v}, scale{s} {}
189 };
190 
200 template <typename Rep, Radix Rad>
201 class fixed_point {
202  Rep _value{};
203  scale_type _scale;
204 
205  public:
206  using rep = Rep;
207  static constexpr auto rad = Rad;
208 
217  template <typename T,
218  typename cuda::std::enable_if_t<cuda::std::is_integral_v<T> &&
219  is_supported_representation_type<Rep>()>* = nullptr>
220  CUDF_HOST_DEVICE inline explicit fixed_point(T const& value, scale_type const& scale)
221  // `value` is cast to `Rep` to avoid overflow in cases where
222  // constructing to `Rep` that is wider than `T`
223  : _value{detail::shift<Rep, Rad>(static_cast<Rep>(value), scale)}, _scale{scale}
224  {
225  }
226 
233  : _value{s.value}, _scale{s.scale}
234  {
235  }
236 
244  template <typename T, typename cuda::std::enable_if_t<cuda::std::is_integral_v<T>>* = nullptr>
245  CUDF_HOST_DEVICE inline fixed_point(T const& value)
246  : _value{static_cast<Rep>(value)}, _scale{scale_type{0}}
247  {
248  }
249 
254  CUDF_HOST_DEVICE inline fixed_point() : _scale{scale_type{0}} {}
255 
262  template <typename U, typename cuda::std::enable_if_t<cuda::std::is_integral_v<U>>* = nullptr>
263  CUDF_HOST_DEVICE explicit constexpr operator U() const
264  {
265  // Cast to the larger of the two types (of U and Rep) before converting to Rep because in
266  // certain cases casting to U before shifting will result in integer overflow (i.e. if U =
267  // int32_t, Rep = int64_t and _value > 2 billion)
268  auto const value = cuda::std::common_type_t<U, Rep>(_value);
269  return static_cast<U>(detail::shift<Rep, Rad>(value, scale_type{-_scale}));
270  }
271 
277  CUDF_HOST_DEVICE inline operator scaled_integer<Rep>() const
278  {
279  return scaled_integer<Rep>{_value, _scale};
280  }
281 
287  CUDF_HOST_DEVICE [[nodiscard]] inline rep value() const { return _value; }
288 
294  CUDF_HOST_DEVICE [[nodiscard]] inline scale_type scale() const { return _scale; }
295 
301  CUDF_HOST_DEVICE inline explicit constexpr operator bool() const
302  {
303  return static_cast<bool>(_value);
304  }
305 
314  template <typename Rep1, Radix Rad1>
316  {
317  *this = *this + rhs;
318  return *this;
319  }
320 
329  template <typename Rep1, Radix Rad1>
331  {
332  *this = *this * rhs;
333  return *this;
334  }
335 
344  template <typename Rep1, Radix Rad1>
346  {
347  *this = *this - rhs;
348  return *this;
349  }
350 
359  template <typename Rep1, Radix Rad1>
361  {
362  *this = *this / rhs;
363  return *this;
364  }
365 
372  {
373  *this = *this + fixed_point<Rep, Rad>{1, scale_type{_scale}};
374  return *this;
375  }
376 
390  template <typename Rep1, Radix Rad1>
392  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
393 
407  template <typename Rep1, Radix Rad1>
409  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
410 
422  template <typename Rep1, Radix Rad1>
424  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
425 
437  template <typename Rep1, Radix Rad1>
439  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
440 
454  template <typename Rep1, Radix Rad1>
456  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
457 
471  template <typename Rep1, Radix Rad1>
472  CUDF_HOST_DEVICE inline friend bool operator==(fixed_point<Rep1, Rad1> const& lhs,
473  fixed_point<Rep1, Rad1> const& rhs);
474 
488  template <typename Rep1, Radix Rad1>
489  CUDF_HOST_DEVICE inline friend bool operator!=(fixed_point<Rep1, Rad1> const& lhs,
490  fixed_point<Rep1, Rad1> const& rhs);
491 
505  template <typename Rep1, Radix Rad1>
506  CUDF_HOST_DEVICE inline friend bool operator<=(fixed_point<Rep1, Rad1> const& lhs,
507  fixed_point<Rep1, Rad1> const& rhs);
508 
522  template <typename Rep1, Radix Rad1>
523  CUDF_HOST_DEVICE inline friend bool operator>=(fixed_point<Rep1, Rad1> const& lhs,
524  fixed_point<Rep1, Rad1> const& rhs);
525 
539  template <typename Rep1, Radix Rad1>
540  CUDF_HOST_DEVICE inline friend bool operator<(fixed_point<Rep1, Rad1> const& lhs,
541  fixed_point<Rep1, Rad1> const& rhs);
542 
556  template <typename Rep1, Radix Rad1>
557  CUDF_HOST_DEVICE inline friend bool operator>(fixed_point<Rep1, Rad1> const& lhs,
558  fixed_point<Rep1, Rad1> const& rhs);
559 
569  CUDF_HOST_DEVICE [[nodiscard]] inline fixed_point<Rep, Rad> rescaled(scale_type scale) const
570  {
571  if (scale == _scale) { return *this; }
572  Rep const value = detail::shift<Rep, Rad>(_value, scale_type{scale - _scale});
573  return fixed_point<Rep, Rad>{scaled_integer<Rep>{value, scale}};
574  }
575 
576 #ifndef __CUDACC_RTC__
577 
581  explicit operator std::string() const
582  {
583  if (_scale < 0) {
584  auto const av = detail::abs(_value);
585  Rep const n = detail::exp10<Rep>(-_scale);
586  Rep const f = av % n;
587  auto const num_zeros =
588  std::max(0, (-_scale - static_cast<int32_t>(detail::to_string(f).size())));
589  auto const zeros = std::string(num_zeros, '0');
590  auto const sign = _value < 0 ? std::string("-") : std::string();
591  return sign + detail::to_string(av / n) + std::string(".") + zeros +
592  detail::to_string(av % n);
593  }
594  auto const zeros = std::string(_scale, '0');
595  return detail::to_string(_value) + zeros;
596  }
597 
598 #endif
599 };
600 
610 template <typename Rep, typename T>
611 CUDF_HOST_DEVICE inline auto addition_overflow(T lhs, T rhs)
612 {
613  return cuda::add_overflow<Rep>(lhs, rhs).overflow;
614 }
615 
624 template <typename Rep, typename T>
625 CUDF_HOST_DEVICE inline auto subtraction_overflow(T lhs, T rhs)
626 {
627  return cuda::sub_overflow<Rep>(lhs, rhs).overflow;
628 }
629 
638 template <typename Rep, typename T>
639 CUDF_HOST_DEVICE inline auto division_overflow(T lhs, T rhs)
640 {
641  return cuda::div_overflow<Rep>(lhs, rhs).overflow;
642 }
643 
652 template <typename Rep, typename T>
653 CUDF_HOST_DEVICE inline auto multiplication_overflow(T lhs, T rhs)
654 {
655  return cuda::mul_overflow<Rep>(lhs, rhs).overflow;
656 }
657 
658 // PLUS Operation
659 template <typename Rep1, Radix Rad1>
661  fixed_point<Rep1, Rad1> const& rhs)
662 {
663  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
664  auto const sum = lhs.rescaled(scale)._value + rhs.rescaled(scale)._value;
665 
666 #if defined(__CUDACC_DEBUG__)
667 
668  assert(!addition_overflow<Rep1>(lhs.rescaled(scale)._value, rhs.rescaled(scale)._value) &&
669  "fixed_point overflow");
670 
671 #endif
672 
673  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{sum, scale}};
674 }
675 
676 // MINUS Operation
677 template <typename Rep1, Radix Rad1>
679  fixed_point<Rep1, Rad1> const& rhs)
680 {
681  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
682  auto const diff = lhs.rescaled(scale)._value - rhs.rescaled(scale)._value;
683 
684 #if defined(__CUDACC_DEBUG__)
685 
686  assert(!subtraction_overflow<Rep1>(lhs.rescaled(scale)._value, rhs.rescaled(scale)._value) &&
687  "fixed_point overflow");
688 
689 #endif
690 
691  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{diff, scale}};
692 }
693 
694 // MULTIPLIES Operation
695 template <typename Rep1, Radix Rad1>
697  fixed_point<Rep1, Rad1> const& rhs)
698 {
699 #if defined(__CUDACC_DEBUG__)
700 
701  assert(!multiplication_overflow<Rep1>(lhs._value, rhs._value) && "fixed_point overflow");
702 
703 #endif
704 
706  scaled_integer<Rep1>(lhs._value * rhs._value, scale_type{lhs._scale + rhs._scale})};
707 }
708 
709 // DIVISION Operation
710 template <typename Rep1, Radix Rad1>
712  fixed_point<Rep1, Rad1> const& rhs)
713 {
714 #if defined(__CUDACC_DEBUG__)
715 
716  assert(!division_overflow<Rep1>(lhs._value, rhs._value) && "fixed_point overflow");
717 
718 #endif
719 
721  scaled_integer<Rep1>(lhs._value / rhs._value, scale_type{lhs._scale - rhs._scale})};
722 }
723 
724 // EQUALITY COMPARISON Operation
725 template <typename Rep1, Radix Rad1>
727  fixed_point<Rep1, Rad1> const& rhs)
728 {
729  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
730  return lhs.rescaled(scale)._value == rhs.rescaled(scale)._value;
731 }
732 
733 // EQUALITY NOT COMPARISON Operation
734 template <typename Rep1, Radix Rad1>
736  fixed_point<Rep1, Rad1> const& rhs)
737 {
738  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
739  return lhs.rescaled(scale)._value != rhs.rescaled(scale)._value;
740 }
741 
742 // LESS THAN OR EQUAL TO Operation
743 template <typename Rep1, Radix Rad1>
745  fixed_point<Rep1, Rad1> const& rhs)
746 {
747  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
748  return lhs.rescaled(scale)._value <= rhs.rescaled(scale)._value;
749 }
750 
751 // GREATER THAN OR EQUAL TO Operation
752 template <typename Rep1, Radix Rad1>
754  fixed_point<Rep1, Rad1> const& rhs)
755 {
756  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
757  return lhs.rescaled(scale)._value >= rhs.rescaled(scale)._value;
758 }
759 
760 // LESS THAN Operation
761 template <typename Rep1, Radix Rad1>
763  fixed_point<Rep1, Rad1> const& rhs)
764 {
765  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
766  return lhs.rescaled(scale)._value < rhs.rescaled(scale)._value;
767 }
768 
769 // GREATER THAN Operation
770 template <typename Rep1, Radix Rad1>
772  fixed_point<Rep1, Rad1> const& rhs)
773 {
774  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
775  return lhs.rescaled(scale)._value > rhs.rescaled(scale)._value;
776 }
777 
778 // MODULO OPERATION
779 template <typename Rep1, Radix Rad1>
781  fixed_point<Rep1, Rad1> const& rhs)
782 {
783  auto const scale = cuda::std::min(lhs._scale, rhs._scale);
784  auto const remainder = lhs.rescaled(scale)._value % rhs.rescaled(scale)._value;
785  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{remainder, scale}};
786 }
787 
788 template <typename Rep>
789 using decimal =
794  // end of group
796 } // 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:83
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:49
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:38
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:58
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)
constexpr char const * to_string(errc error)
Convert an errc error code to a human-readable string.
Definition: errc.hpp:37
fixed_point and supporting types
Definition: fixed_point.hpp:30
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