fixed_point.hpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020-2023, 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 
26 #include <algorithm>
27 #include <cassert>
28 #include <cmath>
29 #include <string>
30 
32 namespace numeric {
33 
42 enum scale_type : int32_t {};
43 
53 enum class Radix : int32_t { BASE_2 = 2, BASE_10 = 10 };
54 
61 template <typename T>
62 constexpr inline auto is_supported_representation_type()
63 {
64  return cuda::std::is_same_v<T, int32_t> || //
65  cuda::std::is_same_v<T, int64_t> || //
66  cuda::std::is_same_v<T, __int128_t>;
67 }
68 
75 template <typename T>
76 constexpr inline auto is_supported_construction_value_type()
77 {
78  return cuda::std::is_integral<T>() || cuda::std::is_floating_point_v<T>;
79 }
80  // end of group
82 
83 // Helper functions for `fixed_point` type
84 namespace detail {
97 template <typename Rep,
98  Radix Base,
99  typename T,
100  typename cuda::std::enable_if_t<(cuda::std::is_same_v<int32_t, T> &&
101  is_supported_representation_type<Rep>())>* = nullptr>
102 CUDF_HOST_DEVICE inline Rep ipow(T exponent)
103 {
104  cudf_assert(exponent >= 0 && "integer exponentiation with negative exponent is not possible.");
105  if (exponent == 0) { return static_cast<Rep>(1); }
106 
107  auto extra = static_cast<Rep>(1);
108  auto square = static_cast<Rep>(Base);
109  while (exponent > 1) {
110  if (exponent & 1 /* odd */) {
111  extra *= square;
112  exponent -= 1;
113  }
114  exponent /= 2;
115  square *= square;
116  }
117  return square * extra;
118 }
119 
131 template <typename Rep, Radix Rad, typename T>
132 CUDF_HOST_DEVICE inline constexpr T right_shift(T const& val, scale_type const& scale)
133 {
134  return val / ipow<Rep, Rad>(static_cast<int32_t>(scale));
135 }
136 
148 template <typename Rep, Radix Rad, typename T>
149 CUDF_HOST_DEVICE inline constexpr T left_shift(T const& val, scale_type const& scale)
150 {
151  return val * ipow<Rep, Rad>(static_cast<int32_t>(-scale));
152 }
153 
167 template <typename Rep, Radix Rad, typename T>
168 CUDF_HOST_DEVICE inline constexpr T shift(T const& val, scale_type const& scale)
169 {
170  if (scale == 0) { return val; }
171  if (scale > 0) { return right_shift<Rep, Rad>(val, scale); }
172  return left_shift<Rep, Rad>(val, scale);
173 }
174 
175 } // namespace detail
176 
195 template <typename Rep,
196  typename cuda::std::enable_if_t<is_supported_representation_type<Rep>()>* = nullptr>
198  Rep value;
206  CUDF_HOST_DEVICE inline explicit scaled_integer(Rep v, scale_type s) : value{v}, scale{s} {}
207 };
208 
218 template <typename Rep, Radix Rad>
219 class fixed_point {
220  Rep _value{};
221  scale_type _scale;
222 
223  public:
224  using rep = Rep;
225 
234  template <typename T,
235  typename cuda::std::enable_if_t<cuda::std::is_floating_point<T>() &&
236  is_supported_representation_type<Rep>()>* = nullptr>
237  CUDF_HOST_DEVICE inline explicit fixed_point(T const& value, scale_type const& scale)
238  : _value{static_cast<Rep>(detail::shift<Rep, Rad>(value, scale))}, _scale{scale}
239  {
240  }
241 
250  template <typename T,
251  typename cuda::std::enable_if_t<cuda::std::is_integral<T>() &&
252  is_supported_representation_type<Rep>()>* = nullptr>
253  CUDF_HOST_DEVICE inline explicit fixed_point(T const& value, scale_type const& scale)
254  // `value` is cast to `Rep` to avoid overflow in cases where
255  // constructing to `Rep` that is wider than `T`
256  : _value{detail::shift<Rep, Rad>(static_cast<Rep>(value), scale)}, _scale{scale}
257  {
258  }
259 
266  : _value{s.value}, _scale{s.scale}
267  {
268  }
269 
277  template <typename T,
278  typename cuda::std::enable_if_t<is_supported_construction_value_type<T>()>* = nullptr>
280  : _value{static_cast<Rep>(value)}, _scale{scale_type{0}}
281  {
282  }
283 
288  CUDF_HOST_DEVICE inline fixed_point() : _scale{scale_type{0}} {}
289 
296  template <typename U,
297  typename cuda::std::enable_if_t<cuda::std::is_floating_point_v<U>>* = nullptr>
298  explicit constexpr operator U() const
299  {
300  return detail::shift<Rep, Rad>(static_cast<U>(_value), scale_type{-_scale});
301  }
302 
309  template <typename U, typename cuda::std::enable_if_t<cuda::std::is_integral_v<U>>* = nullptr>
310  explicit constexpr operator U() const
311  {
312  // Cast to the larger of the two types (of U and Rep) before converting to Rep because in
313  // certain cases casting to U before shifting will result in integer overflow (i.e. if U =
314  // int32_t, Rep = int64_t and _value > 2 billion)
315  auto const value = std::common_type_t<U, Rep>(_value);
316  return static_cast<U>(detail::shift<Rep, Rad>(value, scale_type{-_scale}));
317  }
318 
324  CUDF_HOST_DEVICE inline operator scaled_integer<Rep>() const
325  {
326  return scaled_integer<Rep>{_value, _scale};
327  }
328 
334  CUDF_HOST_DEVICE inline rep value() const { return _value; }
335 
341  CUDF_HOST_DEVICE inline scale_type scale() const { return _scale; }
342 
348  CUDF_HOST_DEVICE inline explicit constexpr operator bool() const
349  {
350  return static_cast<bool>(_value);
351  }
352 
361  template <typename Rep1, Radix Rad1>
363  {
364  *this = *this + rhs;
365  return *this;
366  }
367 
376  template <typename Rep1, Radix Rad1>
378  {
379  *this = *this * rhs;
380  return *this;
381  }
382 
391  template <typename Rep1, Radix Rad1>
393  {
394  *this = *this - rhs;
395  return *this;
396  }
397 
406  template <typename Rep1, Radix Rad1>
408  {
409  *this = *this / rhs;
410  return *this;
411  }
412 
419  {
420  *this = *this + fixed_point<Rep, Rad>{1, scale_type{_scale}};
421  return *this;
422  }
423 
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 
469  template <typename Rep1, Radix Rad1>
471  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
472 
484  template <typename Rep1, Radix Rad1>
486  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
487 
501  template <typename Rep1, Radix Rad1>
503  fixed_point<Rep1, Rad1> const& lhs, fixed_point<Rep1, Rad1> const& rhs);
504 
518  template <typename Rep1, Radix Rad1>
519  CUDF_HOST_DEVICE inline friend bool operator==(fixed_point<Rep1, Rad1> const& lhs,
520  fixed_point<Rep1, Rad1> const& rhs);
521 
535  template <typename Rep1, Radix Rad1>
536  CUDF_HOST_DEVICE inline friend bool operator!=(fixed_point<Rep1, Rad1> const& lhs,
537  fixed_point<Rep1, Rad1> const& rhs);
538 
552  template <typename Rep1, Radix Rad1>
553  CUDF_HOST_DEVICE inline friend bool operator<=(fixed_point<Rep1, Rad1> const& lhs,
554  fixed_point<Rep1, Rad1> const& rhs);
555 
569  template <typename Rep1, Radix Rad1>
570  CUDF_HOST_DEVICE inline friend bool operator>=(fixed_point<Rep1, Rad1> const& lhs,
571  fixed_point<Rep1, Rad1> const& rhs);
572 
586  template <typename Rep1, Radix Rad1>
587  CUDF_HOST_DEVICE inline friend bool operator<(fixed_point<Rep1, Rad1> const& lhs,
588  fixed_point<Rep1, Rad1> const& rhs);
589 
603  template <typename Rep1, Radix Rad1>
604  CUDF_HOST_DEVICE inline friend bool operator>(fixed_point<Rep1, Rad1> const& lhs,
605  fixed_point<Rep1, Rad1> const& rhs);
606 
617  {
618  if (scale == _scale) { return *this; }
619  Rep const value = detail::shift<Rep, Rad>(_value, scale_type{scale - _scale});
621  }
622 
626  explicit operator std::string() const
627  {
628  if (_scale < 0) {
629  auto const av = detail::abs(_value);
630  Rep const n = detail::exp10<Rep>(-_scale);
631  Rep const f = av % n;
632  auto const num_zeros =
633  std::max(0, (-_scale - static_cast<int32_t>(detail::to_string(f).size())));
634  auto const zeros = std::string(num_zeros, '0');
635  auto const sign = _value < 0 ? std::string("-") : std::string();
636  return sign + detail::to_string(av / n) + std::string(".") + zeros +
637  detail::to_string(av % n);
638  }
639  auto const zeros = std::string(_scale, '0');
640  return detail::to_string(_value) + zeros;
641  }
642 };
643 
653 template <typename Rep, typename T>
654 CUDF_HOST_DEVICE inline auto addition_overflow(T lhs, T rhs)
655 {
656  return rhs > 0 ? lhs > cuda::std::numeric_limits<Rep>::max() - rhs
657  : lhs < cuda::std::numeric_limits<Rep>::min() - rhs;
658 }
659 
668 template <typename Rep, typename T>
669 CUDF_HOST_DEVICE inline auto subtraction_overflow(T lhs, T rhs)
670 {
671  return rhs > 0 ? lhs < cuda::std::numeric_limits<Rep>::min() + rhs
672  : lhs > cuda::std::numeric_limits<Rep>::max() + rhs;
673 }
674 
683 template <typename Rep, typename T>
684 CUDF_HOST_DEVICE inline auto division_overflow(T lhs, T rhs)
685 {
686  return lhs == cuda::std::numeric_limits<Rep>::min() && rhs == -1;
687 }
688 
697 template <typename Rep, typename T>
698 CUDF_HOST_DEVICE inline auto multiplication_overflow(T lhs, T rhs)
699 {
700  auto const min = cuda::std::numeric_limits<Rep>::min();
701  auto const max = cuda::std::numeric_limits<Rep>::max();
702  if (rhs > 0) { return lhs > max / rhs || lhs < min / rhs; }
703  if (rhs < -1) { return lhs > min / rhs || lhs < max / rhs; }
704  return rhs == -1 && lhs == min;
705 }
706 
707 // PLUS Operation
708 template <typename Rep1, Radix Rad1>
710  fixed_point<Rep1, Rad1> const& rhs)
711 {
712  auto const scale = std::min(lhs._scale, rhs._scale);
713  auto const sum = lhs.rescaled(scale)._value + rhs.rescaled(scale)._value;
714 
715 #if defined(__CUDACC_DEBUG__)
716 
717  assert(!addition_overflow<Rep1>(lhs.rescaled(scale)._value, rhs.rescaled(scale)._value) &&
718  "fixed_point overflow");
719 
720 #endif
721 
722  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{sum, scale}};
723 }
724 
725 // MINUS Operation
726 template <typename Rep1, Radix Rad1>
728  fixed_point<Rep1, Rad1> const& rhs)
729 {
730  auto const scale = std::min(lhs._scale, rhs._scale);
731  auto const diff = lhs.rescaled(scale)._value - rhs.rescaled(scale)._value;
732 
733 #if defined(__CUDACC_DEBUG__)
734 
735  assert(!subtraction_overflow<Rep1>(lhs.rescaled(scale)._value, rhs.rescaled(scale)._value) &&
736  "fixed_point overflow");
737 
738 #endif
739 
740  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{diff, scale}};
741 }
742 
743 // MULTIPLIES Operation
744 template <typename Rep1, Radix Rad1>
746  fixed_point<Rep1, Rad1> const& rhs)
747 {
748 #if defined(__CUDACC_DEBUG__)
749 
750  assert(!multiplication_overflow<Rep1>(lhs._value, rhs._value) && "fixed_point overflow");
751 
752 #endif
753 
755  scaled_integer<Rep1>(lhs._value * rhs._value, scale_type{lhs._scale + rhs._scale})};
756 }
757 
758 // DIVISION Operation
759 template <typename Rep1, Radix Rad1>
761  fixed_point<Rep1, Rad1> const& rhs)
762 {
763 #if defined(__CUDACC_DEBUG__)
764 
765  assert(!division_overflow<Rep1>(lhs._value, rhs._value) && "fixed_point overflow");
766 
767 #endif
768 
770  scaled_integer<Rep1>(lhs._value / rhs._value, scale_type{lhs._scale - rhs._scale})};
771 }
772 
773 // EQUALITY COMPARISON Operation
774 template <typename Rep1, Radix Rad1>
776  fixed_point<Rep1, Rad1> const& rhs)
777 {
778  auto const scale = std::min(lhs._scale, rhs._scale);
779  return lhs.rescaled(scale)._value == rhs.rescaled(scale)._value;
780 }
781 
782 // EQUALITY NOT COMPARISON Operation
783 template <typename Rep1, Radix Rad1>
785  fixed_point<Rep1, Rad1> const& rhs)
786 {
787  auto const scale = std::min(lhs._scale, rhs._scale);
788  return lhs.rescaled(scale)._value != rhs.rescaled(scale)._value;
789 }
790 
791 // LESS THAN OR EQUAL TO Operation
792 template <typename Rep1, Radix Rad1>
794  fixed_point<Rep1, Rad1> const& rhs)
795 {
796  auto const scale = std::min(lhs._scale, rhs._scale);
797  return lhs.rescaled(scale)._value <= rhs.rescaled(scale)._value;
798 }
799 
800 // GREATER THAN OR EQUAL TO Operation
801 template <typename Rep1, Radix Rad1>
803  fixed_point<Rep1, Rad1> const& rhs)
804 {
805  auto const scale = std::min(lhs._scale, rhs._scale);
806  return lhs.rescaled(scale)._value >= rhs.rescaled(scale)._value;
807 }
808 
809 // LESS THAN Operation
810 template <typename Rep1, Radix Rad1>
812  fixed_point<Rep1, Rad1> const& rhs)
813 {
814  auto const scale = std::min(lhs._scale, rhs._scale);
815  return lhs.rescaled(scale)._value < rhs.rescaled(scale)._value;
816 }
817 
818 // GREATER THAN Operation
819 template <typename Rep1, Radix Rad1>
821  fixed_point<Rep1, Rad1> const& rhs)
822 {
823  auto const scale = std::min(lhs._scale, rhs._scale);
824  return lhs.rescaled(scale)._value > rhs.rescaled(scale)._value;
825 }
826 
827 // MODULO OPERATION
828 template <typename Rep1, Radix Rad1>
830  fixed_point<Rep1, Rad1> const& rhs)
831 {
832  auto const scale = std::min(lhs._scale, rhs._scale);
833  auto const remainder = lhs.rescaled(scale)._value % rhs.rescaled(scale)._value;
834  return fixed_point<Rep1, Rad1>{scaled_integer<Rep1>{remainder, scale}};
835 }
836 
840  // end of group
842 } // namespace 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 friend bool operator!=(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
operator != (for comparing two fixed_point numbers)
CUDF_HOST_DEVICE friend fixed_point< Rep1, Rad1 > operator*(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
operator * (for multiplying two fixed_point numbers)
CUDF_HOST_DEVICE friend bool operator==(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
operator == (for comparing two fixed_point numbers)
CUDF_HOST_DEVICE rep value() const
Method that returns the underlying value of the fixed_point number.
CUDF_HOST_DEVICE friend bool operator<(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
operator < (for comparing two fixed_point numbers)
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 friend fixed_point< Rep1, Rad1 > operator/(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
operator / (for dividing two fixed_point numbers)
CUDF_HOST_DEVICE fixed_point< Rep1, Rad1 > & operator+=(fixed_point< Rep1, Rad1 > const &rhs)
operator +=
CUDF_HOST_DEVICE friend fixed_point< Rep1, Rad1 > operator+(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
operator + (for adding two fixed_point numbers)
CUDF_HOST_DEVICE friend bool operator<=(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
operator <= (for comparing two fixed_point numbers)
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 friend fixed_point< Rep1, Rad1 > operator-(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
operator - (for subtracting two fixed_point numbers)
CUDF_HOST_DEVICE friend bool operator>=(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
operator >= (for comparing two fixed_point numbers)
CUDF_HOST_DEVICE fixed_point< Rep1, Rad1 > & operator/=(fixed_point< Rep1, Rad1 > const &rhs)
operator /=
CUDF_HOST_DEVICE friend bool operator>(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
operator > (for comparing two fixed_point numbers)
CUDF_HOST_DEVICE friend fixed_point< Rep1, Rad1 > operator%(fixed_point< Rep1, Rad1 > const &lhs, fixed_point< Rep1, Rad1 > const &rhs)
operator % (for computing the modulo operation of two fixed_point numbers)
CUDF_HOST_DEVICE fixed_point(T const &value, scale_type const &scale)
Constructor that will perform shifting to store value appropriately (from floating point 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 T right_shift(T const &val, scale_type const &scale)
Function that performs a right shift scale "times" on the val
CUDF_HOST_DEVICE Rep ipow(T exponent)
A function for integer exponentiation by squaring.
Radix
Scoped enumerator to use when constructing fixed_point
Definition: fixed_point.hpp:53
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)
constexpr auto is_supported_construction_value_type()
Returns true if the value type is supported for constructing a fixed_point
Definition: fixed_point.hpp:76
scale_type
The scale type for fixed_point.
Definition: fixed_point.hpp:42
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.
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 auto is_supported_representation_type()
Returns true if the representation type is supported by fixed_point
Definition: fixed_point.hpp:62
fixed_point and supporting types
Definition: fixed_point.hpp:32
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