text: # Author: carlo.revelli@berkeley.edu
  #
  # Precise
  # Representation
  # Expanding
  # CCXT
  # Internal
  # Scientific
  # Exponents
  #
  # (╯°□°）╯︵ ┻━┻
  
  
  class Precise:
      def __init__(self, number, decimals=None):
          if decimals is None:
              modifier = 0
              number = number.lower()
              if 'e' in number:
                  number, modifier = number.split('e')
                  modifier = int(modifier)
              decimal_index = number.find('.')
              if decimal_index > -1:
                  self.decimals = len(number) - decimal_index - 1
                  self.integer = int(number.replace('.', ''))
              else:
                  self.decimals = 0
                  self.integer = int(number)
              self.decimals = self.decimals - modifier
          else:
              self.integer = number
              self.decimals = decimals
          self.base = 10
  
      def __add__(self, other):
          return self.add(other)
  
      def __sub__(self, other):
          return self.sub(other)
  
      def __mul__(self, other):
          return self.mul(other)
  
      def __truediv__(self, other):
          return self.div(other)
  
      def __mod__(self, other):
          return self.mod(other)
  
      def __neg__(self):
          return self.neg()
  
      def __abs__(self):
          return self.abs()
  
      def __min__(self, other):
          return self.min(other)
  
      def __max__(self, other):
          return self.max(other)
  
      def __lt__(self, other):
          return self.lt(other)
  
      def __le__(self, other):
          return self.le(other)
  
      def __gt__(self, other):
          return self.gt(other)
  
      def __ge__(self, other):
          return self.ge(other)
  
      def __eq__(self, other):
          if isinstance(other, str):
              # Allow comparisons with Precise("5") == "5"
              return str(self) == other
          return self.equals(other)
  
      def mul(self, other):
          integer_result = self.integer * other.integer
          return Precise(integer_result, self.decimals + other.decimals)
  
      def div(self, other, precision=18):
          distance = precision - self.decimals + other.decimals
          if distance == 0:
              numerator = self.integer
          elif distance < 0:
              exponent = self.base ** -distance
              numerator = self.integer // exponent
          else:
              exponent = self.base ** distance
              numerator = self.integer * exponent
          result, mod = divmod(numerator, other.integer)
          # python floors negative numbers down instead of truncating
          # if mod is zero it will be floored to itself so we do not add one
          result = result + 1 if result < 0 and mod else result
          return Precise(result, precision)
  
      def add(self, other):
          if self.decimals == other.decimals:
              integer_result = self.integer + other.integer
              return Precise(integer_result, self.decimals)
          else:
              smaller, bigger = [other, self] if self.decimals > other.decimals else [self, other]
              exponent = bigger.decimals - smaller.decimals
              normalised = smaller.integer * (self.base ** exponent)
              result = normalised + bigger.integer
              return Precise(result, bigger.decimals)
  
      def sub(self, other):
          negative = Precise(-other.integer, other.decimals)
          return self.add(negative)
  
      def abs(self):
          return Precise(abs(self.integer), self.decimals)
  
      def neg(self):
          return Precise(-self.integer, self.decimals)
  
      def mod(self, other):
          rationizerNumberator = max(-self.decimals + other.decimals, 0)
          numerator = self.integer * (self.base ** rationizerNumberator)
          rationizerDenominator = max(-other.decimals + self.decimals, 0)
          denominator = other.integer * (self.base ** rationizerDenominator)
          result = numerator % denominator
          return Precise(result, rationizerDenominator + other.decimals)
  
      def min(self, other):
          return self if self.lt(other) else other
  
      def max(self, other):
          return self if self.gt(other) else other
  
      def gt(self, other):
          add = self.sub(other)
          return add.integer > 0
  
      def ge(self, other):
          add = self.sub(other)
          return add.integer >= 0
  
      def lt(self, other):
          return other.gt(self)
  
      def le(self, other):
          return other.ge(self)
  
      def reduce(self):
          string = str(self.integer)
          start = len(string) - 1
          if start == 0:
              if string == "0":
                  self.decimals = 0
              return self
          for i in range(start, -1, -1):
              if string[i] != '0':
                  break
          difference = start - i
          if difference == 0:
              return self
          self.decimals -= difference
          self.integer = int(string[:i + 1])
  
      def equals(self, other):
          self.reduce()
          other.reduce()
          return self.decimals == other.decimals and self.integer == other.integer
  
      def __str__(self):
          self.reduce()
          sign = '-' if self.integer < 0 else ''
          integer_array = list(str(abs(self.integer)).rjust(self.decimals, '0'))
          index = len(integer_array) - self.decimals
          if index == 0:
              item = '0.'
          elif self.decimals < 0:
              item = '0' * (-self.decimals)
          elif self.decimals == 0:
              item = ''
          else:
              item = '.'
          integer_array.insert(index, item)
          return sign + ''.join(integer_array)
  
      @staticmethod
      def string_mul(string1, string2):
          if string1 is None or string2 is None:
              return None
          return str(Precise(string1).mul(Precise(string2)))
  
      @staticmethod
      def string_div(string1, string2, precision=18):
          if string1 is None or string2 is None:
              return None
          string2_precise = Precise(string2)
          if string2_precise.integer == 0:
              return None
          return str(Precise(string1).div(string2_precise, precision))
  
      @staticmethod
      def string_add(string1, string2):
          if string1 is None and string2 is None:
              return None
          if string1 is None:
              return string2
          elif string2 is None:
              return string1
          return str(Precise(string1).add(Precise(string2)))
  
      @staticmethod
      def string_sub(string1, string2):
          if string1 is None or string2 is None:
              return None
          return str(Precise(string1).sub(Precise(string2)))
  
      @staticmethod
      def string_abs(string):
          if string is None:
              return None
          return str(Precise(string).abs())
  
      @staticmethod
      def string_neg(string):
          if string is None:
              return None
          return str(Precise(string).neg())
  
      @staticmethod
      def string_mod(string1, string2):
          if string1 is None or string2 is None:
              return None
          return str(Precise(string1).mod(Precise(string2)))
  
      @staticmethod
      def string_equals(string1, string2):
          if string1 is None or string2 is None:
              return None
          return Precise(string1).equals(Precise(string2))
  
      @staticmethod
      def string_eq(string1, string2):
          if string1 is None or string2 is None:
              return None
          return Precise(string1).equals(Precise(string2))
  
      @staticmethod
      def string_min(string1, string2):
          if string1 is None or string2 is None:
              return None
          return str(Precise(string1).min(Precise(string2)))
  
      @staticmethod
      def string_max(string1, string2):
          if string1 is None or string2 is None:
              return None
          return str(Precise(string1).max(Precise(string2)))
  
      @staticmethod
      def string_gt(string1, string2):
          if string1 is None or string2 is None:
              return None
          return Precise(string1).gt(Precise(string2))
  
      @staticmethod
      def string_ge(string1, string2):
          if string1 is None or string2 is None:
              return None
          return Precise(string1).ge(Precise(string2))
  
      @staticmethod
      def string_lt(string1, string2):
          if string1 is None or string2 is None:
              return None
          return Precise(string1).lt(Precise(string2))
  
      @staticmethod
      def string_le(string1, string2):
          if string1 is None or string2 is None:
              return None
          return Precise(string1).le(Precise(string2))
  