fixed_point.hpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020-2025, NVIDIA CORPORATION.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <cudf/detail/utilities/assert.cuh>
20 #include <cudf/fixed_point/temporary.hpp>
21 #include <cudf/types.hpp>
22 
23 #include <cuda/std/limits>
24 #include <cuda/std/type_traits>
25 #include <cuda/std/utility>
26 
27 #include <algorithm>
28 #include <cassert>
29 #include <cmath>
30 #include <string>
31 
33 namespace CUDF_EXPORT numeric {
34 
43 enum scale_type : int32_t {};
44 
54 enum class Radix : int32_t { BASE_2 = 2, BASE_10 = 10 };
55 
62 template <typename T>
64 {
65  return cuda::std::is_same_v<T, int32_t> || //
66  cuda::std::is_same_v<T, int64_t> || //
67  cuda::std::is_same_v<T, __int128_t>;
68 }
69  // end of group
71 
72 // Helper functions for `fixed_point` type
73 namespace detail {
74 
82 CUDF_HOST_DEVICE constexpr inline scale_type min(scale_type const& a, scale_type const& b)
83 {
84  // TODO This is a temporary workaround because <cuda/std/functional> is not self-contained when
85  // built with NVRTC 11.8. Replace this with cuda::std::min once the underlying issue is resolved.
86 #ifdef __CUDA_ARCH__
87  return scale_type{min(static_cast<int>(a), static_cast<int>(b))};
88 #else
89  return std::min(a, b);
90 #endif
91 }
92 
101 template <typename Rep,
102  Radix Base,
103  typename T,
104  typename cuda::std::enable_if_t<(cuda::std::is_same_v<int32_t, T> &&
105  cuda::std::is_integral_v<Rep>)>* = nullptr>
106 CUDF_HOST_DEVICE inline constexpr Rep ipow(T exponent)
107 {
108  cudf_assert(exponent >= 0 && "integer exponentiation with negative exponent is not possible.");
109 
110  if constexpr (Base == numeric::Radix::BASE_2) { return static_cast<Rep>(1) << exponent; }
111 
112  // Note: Including an array here introduces too much register pressure
113  // https://simple.wikipedia.org/wiki/Exponentiation_by_squaring
114  // This is the iterative equivalent of the recursive definition (faster)
115  // Quick-bench for squaring: http://quick-bench.com/Wg7o7HYQC9FW5M0CO0wQAjSwP_Y
116  if (exponent == 0) { return static_cast<Rep>(1); }
117  auto extra = static_cast<Rep>(1);
118  auto square = static_cast<Rep>(Base);
119  while (exponent > 1) {
120  if (exponent & 1) { extra *= square; }
121  exponent >>= 1;
122  square *= square;
123  }
124  return square * extra;
125 }
126 
138 template <typename Rep, Radix Rad, typename T>
139 CUDF_HOST_DEVICE inline constexpr T right_shift(T const& val, scale_type const& scale)
140 {
141  return val / ipow<Rep, Rad>(static_cast<int32_t>(scale));
142 }
143 
155 template <typename Rep, Radix Rad, typename T>
156 CUDF_HOST_DEVICE inline constexpr T left_shift(T const& val, scale_type const& scale)
157 {
158  return val * ipow<Rep, Rad>(static_cast<int32_t>(-scale));
159 }
160 
174 template <typename Rep, Radix Rad, typename T>
175 CUDF_HOST_DEVICE inline constexpr T shift(T const& val, scale_type const& scale)
176 {
177  if (scale == 0) { return val; }
178  if (scale > 0) { return right_shift<Rep, Rad>(val, scale); }
179  return left_shift<Rep, Rad>(val, scale);
180 }
181 
182 } // namespace detail
183 
202 template <typename Rep,
203  typename cuda::std::enable_if_t<is_supported_representation_type<Rep>()>* = nullptr>
205  Rep value;
213  CUDF_HOST_DEVICE inline explicit scaled_integer(Rep v, scale_type s) : value{v}, scale{s} {}
214 };
215 
225 template <typename Rep, Radix Rad>
226 class fixed_point {
227  Rep _value{};
228  scale_type _scale;
229 
230  public:
231  using rep = Rep;
232  static constexpr auto rad = Rad;
233 
242  template <typename T,
243  typename cuda::std::enable_if_t<cuda::std::is_integral_v<T> &&
244  is_supported_representation_type<Rep>()>* = nullptr>
245  CUDF_HOST_DEVICE inline explicit fixed_point(T const& value, scale_type const& scale)
246  // `value` is cast to `Rep` to avoid overflow in cases where
247  // constructing to `Rep` that is wider than `T`
248  : _value{detail::shift<Rep, Rad>(static_cast<Rep>(value), scale)}, _scale{scale}
249  {
250  }
251 
258  : _value{s.value}, _scale{s.scale}
259  {
260  }
261 
269  template <typename T, typename cuda::std::enable_if_t<cuda::std::is_integral_v<T>>* = nullptr>
270  CUDF_HOST_DEVICE inline fixed_point(T const& value)
271  : _value{static_cast<Rep>(value)}, _scale{scale_type{0}}
272  {
273  }
274 
279  CUDF_HOST_DEVICE inline fixed_point() : _scale{scale_type{0}} {}
280 
287  template <typename U, typename cuda::std::enable_if_t<cuda::std::is_integral_v<U>>* = nullptr>
288  CUDF_HOST_DEVICE explicit constexpr operator U() const
289  {
290  // Cast to the larger of the two types (of U and Rep) before converting to Rep because in
291  // certain cases casting to U before shifting will result in integer overflow (i.e. if U =
292  // int32_t, Rep = int64_t and _value > 2 billion)
293  auto const value = cuda::std::common_type_t<U, Rep>(_value);
294  return static_cast<U>(detail::shift<Rep, Rad>(value, scale_type{-_scale}));
295  }
296 
302  CUDF_HOST_DEVICE inline operator scaled_integer<Rep>() const
303  {
304  return scaled_integer<Rep>{_value, _scale};
305  }
306 
312  CUDF_HOST_DEVICE [[nodiscard]] inline rep value() const { return _value; }
313 
319  CUDF_HOST_DEVICE [[nodiscard]] inline scale_type scale() const { return _scale; }
320 
326  CUDF_HOST_DEVICE inline explicit constexpr operator bool() const
327  {
328  return static_cast<bool>(_value);
329  }
330 
339  template <typename Rep1, Radix Rad1>
341  {
342  *this = *this + rhs;
343  return *this;
344  }
345 
354  template <typename Rep1, Radix Rad1>
356  {
357  *this = *this * rhs;
358  return *this;
359  }
360 
369  template <typename Rep1, Radix Rad1>
371  {
372  *this = *this - rhs;
373  return *this;
374  }
375 
384  template <typename Rep1, Radix Rad1>
386  {
387  *this = *this / rhs;
388  return *this;
389  }
390 
397  {
398  *this = *this + fixed_point<Rep, Rad>{1, scale_type{_scale}};
399  return *this;
400  }
401 
415  template <typename Rep1, Radix Rad1>
417  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
418 
432  template <typename Rep1, Radix Rad1>
434  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
435 
447  template <typename Rep1, Radix Rad1>
449  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
450 
462  template <typename Rep1, Radix Rad1>
464  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
465 
479  template <typename Rep1, Radix Rad1>
481  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
482 
496  template <typename Rep1, Radix Rad1>
497  CUDF_HOST_DEVICE inline friend bool operator==(fixed_point<Rep1, Rad1> const& lhs,
498  fixed_point<Rep1, Rad1> const& rhs);
499 
513  template <typename Rep1, Radix Rad1>
514  CUDF_HOST_DEVICE inline friend bool operator!=(fixed_point<Rep1, Rad1> const& lhs,
515  fixed_point<Rep1, Rad1> const& rhs);
516 
530  template <typename Rep1, Radix Rad1>
531  CUDF_HOST_DEVICE inline friend bool operator<=(fixed_point<Rep1, Rad1> const& lhs,
532  fixed_point<Rep1, Rad1> const& rhs);
533 
547  template <typename Rep1, Radix Rad1>
548  CUDF_HOST_DEVICE inline friend bool operator>=(fixed_point<Rep1, Rad1> const& lhs,
549  fixed_point<Rep1, Rad1> const& rhs);
550 
564  template <typename Rep1, Radix Rad1>
565  CUDF_HOST_DEVICE inline friend bool operator<(fixed_point<Rep1, Rad1> const& lhs,
566  fixed_point<Rep1, Rad1> const& rhs);
567 
581  template <typename Rep1, Radix Rad1>
582  CUDF_HOST_DEVICE inline friend bool operator>(fixed_point<Rep1, Rad1> const& lhs,
583  fixed_point<Rep1, Rad1> const& rhs);
584 
594  CUDF_HOST_DEVICE [[nodiscard]] inline fixed_point<Rep, Rad> rescaled(scale_type scale) const
595  {
596  if (scale == _scale) { return *this; }
597  Rep const value = detail::shift<Rep, Rad>(_value, scale_type{scale - _scale});
598  return fixed_point<Rep, Rad>{scaled_integer<Rep>{value, scale}};
599  }
600 
604  explicit operator std::string() const
605  {
606  if (_scale < 0) {
607  auto const av = detail::abs(_value);
608  Rep const n = detail::exp10<Rep>(-_scale);
609  Rep const f = av % n;
610  auto const num_zeros =
611  std::max(0, (-_scale - static_cast<int32_t>(detail::to_string(f).size())));
612  auto const zeros = std::string(num_zeros, '0');
613  auto const sign = _value < 0 ? std::string("-") : std::string();
614  return sign + detail::to_string(av / n) + std::string(".") + zeros +
615  detail::to_string(av % n);
616  }
617  auto const zeros = std::string(_scale, '0');
618  return detail::to_string(_value) + zeros;
619  }
620 };
621 
631 template <typename Rep, typename T>
632 CUDF_HOST_DEVICE inline auto addition_overflow(T lhs, T rhs)
633 {
634  return rhs > 0 ? lhs > cuda::std::numeric_limits<Rep>::max() - rhs
635  : lhs < cuda::std::numeric_limits<Rep>::min() - rhs;
636 }
637 
646 template <typename Rep, typename T>
647 CUDF_HOST_DEVICE inline auto subtraction_overflow(T lhs, T rhs)
648 {
649  return rhs > 0 ? lhs < cuda::std::numeric_limits<Rep>::min() + rhs
650  : lhs > cuda::std::numeric_limits<Rep>::max() + rhs;
651 }
652 
661 template <typename Rep, typename T>
662 CUDF_HOST_DEVICE inline auto division_overflow(T lhs, T rhs)
663 {
664  return lhs == cuda::std::numeric_limits<Rep>::min() && rhs == -1;
665 }
666 
675 template <typename Rep, typename T>
676 CUDF_HOST_DEVICE inline auto multiplication_overflow(T lhs, T rhs)
677 {
678  auto const min = cuda::std::numeric_limits<Rep>::min();
679  auto const max = cuda::std::numeric_limits<Rep>::max();
680  if (rhs > 0) { return lhs > max / rhs || lhs < min / rhs; }
681  if (rhs < -1) { return lhs > min / rhs || lhs < max / rhs; }
682  return rhs == -1 && lhs == min;
683 }
684 
685 // PLUS Operation
686 template <typename Rep1, Radix Rad1>
688  fixed_point<Rep1, Rad1> const& rhs)
689 {
690  auto const scale = detail::min(lhs._scale, rhs._scale);
691  auto const sum = lhs.rescaled(scale)._value + rhs.rescaled(scale)._value;
692 
693 #if defined(__CUDACC_DEBUG__)
694 
695  assert(!addition_overflow<Rep1>(lhs.rescaled(scale)._value, rhs.rescaled(scale)._value) &&
696  "fixed_point overflow");
697 
698 #endif
699 
700  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{sum, scale}};
701 }
702 
703 // MINUS Operation
704 template <typename Rep1, Radix Rad1>
706  fixed_point<Rep1, Rad1> const& rhs)
707 {
708  auto const scale = detail::min(lhs._scale, rhs._scale);
709  auto const diff = lhs.rescaled(scale)._value - rhs.rescaled(scale)._value;
710 
711 #if defined(__CUDACC_DEBUG__)
712 
713  assert(!subtraction_overflow<Rep1>(lhs.rescaled(scale)._value, rhs.rescaled(scale)._value) &&
714  "fixed_point overflow");
715 
716 #endif
717 
718  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{diff, scale}};
719 }
720 
721 // MULTIPLIES Operation
722 template <typename Rep1, Radix Rad1>
724  fixed_point<Rep1, Rad1> const& rhs)
725 {
726 #if defined(__CUDACC_DEBUG__)
727 
728  assert(!multiplication_overflow<Rep1>(lhs._value, rhs._value) && "fixed_point overflow");
729 
730 #endif
731 
733  scaled_integer<Rep1>(lhs._value * rhs._value, scale_type{lhs._scale + rhs._scale})};
734 }
735 
736 // DIVISION Operation
737 template <typename Rep1, Radix Rad1>
739  fixed_point<Rep1, Rad1> const& rhs)
740 {
741 #if defined(__CUDACC_DEBUG__)
742 
743  assert(!division_overflow<Rep1>(lhs._value, rhs._value) && "fixed_point overflow");
744 
745 #endif
746 
748  scaled_integer<Rep1>(lhs._value / rhs._value, scale_type{lhs._scale - rhs._scale})};
749 }
750 
751 // EQUALITY COMPARISON Operation
752 template <typename Rep1, Radix Rad1>
754  fixed_point<Rep1, Rad1> const& rhs)
755 {
756  auto const scale = detail::min(lhs._scale, rhs._scale);
757  return lhs.rescaled(scale)._value == rhs.rescaled(scale)._value;
758 }
759 
760 // EQUALITY NOT COMPARISON Operation
761 template <typename Rep1, Radix Rad1>
763  fixed_point<Rep1, Rad1> const& rhs)
764 {
765  auto const scale = detail::min(lhs._scale, rhs._scale);
766  return lhs.rescaled(scale)._value != rhs.rescaled(scale)._value;
767 }
768 
769 // LESS THAN OR EQUAL TO Operation
770 template <typename Rep1, Radix Rad1>
772  fixed_point<Rep1, Rad1> const& rhs)
773 {
774  auto const scale = detail::min(lhs._scale, rhs._scale);
775  return lhs.rescaled(scale)._value <= rhs.rescaled(scale)._value;
776 }
777 
778 // GREATER THAN OR EQUAL TO Operation
779 template <typename Rep1, Radix Rad1>
781  fixed_point<Rep1, Rad1> const& rhs)
782 {
783  auto const scale = detail::min(lhs._scale, rhs._scale);
784  return lhs.rescaled(scale)._value >= rhs.rescaled(scale)._value;
785 }
786 
787 // LESS THAN Operation
788 template <typename Rep1, Radix Rad1>
790  fixed_point<Rep1, Rad1> const& rhs)
791 {
792  auto const scale = detail::min(lhs._scale, rhs._scale);
793  return lhs.rescaled(scale)._value < rhs.rescaled(scale)._value;
794 }
795 
796 // GREATER THAN Operation
797 template <typename Rep1, Radix Rad1>
799  fixed_point<Rep1, Rad1> const& rhs)
800 {
801  auto const scale = detail::min(lhs._scale, rhs._scale);
802  return lhs.rescaled(scale)._value > rhs.rescaled(scale)._value;
803 }
804 
805 // MODULO OPERATION
806 template <typename Rep1, Radix Rad1>
808  fixed_point<Rep1, Rad1> const& rhs)
809 {
810  auto const scale = detail::min(lhs._scale, rhs._scale);
811  auto const remainder = lhs.rescaled(scale)._value % rhs.rescaled(scale)._value;
812  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{remainder, scale}};
813 }
814 
818  // end of group
820 } // 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 scale_type min(scale_type const &a, scale_type const &b)
Returns the smaller of the given scales.
Definition: fixed_point.hpp:82
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.
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:54
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:43
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:63
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:33
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:32