Advertisement
Guest User

Untitled

a guest
May 22nd, 2015
113
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 47.95 KB | None | 0 0
  1. """This script is for converting a number into it's text representation, and currently contains
  2. all the values up to 10^3000. It can go further, but will reuse old values once the limit is
  3. hit (eg. 999 nongennovemnonagintillion to 1 thousand nongennovemnonagintillion).
  4.  
  5. It is recommended you input numbers as strings or in Decimal format, since Python integers
  6. and floats don't have enough precision, and the output won't be what is expected.
  7.  
  8.  
  9. LargeNumber(number).to_text() is the main function, though LargeNumber(number).quick() can be
  10. used to easily get a single number with it's prefix.
  11.  
  12. A few parameters can be input to change the output, the main useful ones being max_iterations
  13. and digits. See the to_text() function for more detailed descriptions and examples of them
  14. in use.
  15.  
  16. kwargs:
  17.    digits:
  18.        Default: False
  19.        If the number should be entirely text, or use a mix of digits and text.
  20.    num_decimals:
  21.        Default: None
  22.        Number of decimals to display. None shows all of them.
  23.    force_decimals:
  24.        Default: False
  25.        Force the number of decimals to equal num_decimals.
  26.        If num_decimals is None, make sure each number is a decimal value.
  27.    min_decimals:
  28.        Default: -1
  29.        Minimum amount of decimals to give each number.
  30.        Needs force_decimals enabled to work.
  31.    max_iterations:
  32.        Default: -1
  33.        Maximum number of iterations to use when calculating the number.
  34.        For example, '1.35 thousand' is 0 iterations, '1 thousand and 3.52 hundred' is 1 iteration,
  35.        and '1 thousand, 3 hundred and 52' is 3 iterations.
  36.    min_amount:
  37.        Default: 1
  38.        Minimum amount of an exponential a number can be.
  39.        For example, a min_amount of 0.1 means '1 hundred' will be turned to '0.1 thousand'.
  40.    min_amount_limit:
  41.        Default: -1
  42.        How many iterations to apply min_amount to.
  43.        0 iterations has no effect on output.
  44.    use_fractions:
  45.        Default: False
  46.        If fractions should be calculated.
  47.        Only used if digits is enabled since their text representation is a lot harder to code.
  48.    fraction_precision:
  49.        Default: 100
  50.        Determines how many calculations are done to find a fraction before giving up.
  51.        The value of 100 means 0.01 to 0.99 all have valid fractions.
  52.        
  53. In the descriptions, I use 'prefix' and 'exponential value' or 'suffix', due to no idea of what
  54. to actually use. By suffix or exponential, I mean the name that comes after the number, such as
  55. 'thousand' or 'trillion', and the prefix is the number that relates to these.
  56. """
  57.  
  58. from decimal import Decimal, getcontext
  59. class NumberNames:
  60.     """Build list of numbers from 0 to 10^3000.
  61.    Would do more but I couldn't find the rules for above 3000.
  62.    
  63.    Functions:
  64.        num_dict:
  65.            Not a function, but returns the list of every number suffix.
  66.        all_available_numbers:
  67.            Not a function, but returns a sorted tuple of each num_dict index.
  68.        num_to_text:
  69.            Convert number between 0 and 999 to text.
  70.    """
  71.    
  72.     #Name lists for 0 to 999
  73.     num_units = ['zero','one','two','three','four','five','six','seven','eight','nine']
  74.     num_teens = ['ten','eleven','twelve']+[i+'teen' for i in ['thir','four','fif']+num_units[6:]]
  75.     num_tens = [i+('ty' if i[-1]!='t' else 'y') for i in ['twen','thir','for','fif']+num_units[6:]]
  76.    
  77.     #Name rules for 10^33 to 10^300
  78.     num_exp_prefix = ['', 'un','duo','tre','quattor','quin','sex','septen','octo','novem']
  79.     num_exp_amount = ['']+[i+'int' for i in ['vig','trig','quadrag','quinquag','sexag','septuag','octog','nonag']]
  80.    
  81.     #Name rules for 10^303 to 10^3000
  82.     num_exp_units = [i+'illion' for i in ['m','b','tr','quadr','quint','sext','sept','oct','non']]
  83.     num_exp_tens = [j+'illion' for j in ['dec']+num_exp_amount[1:]]
  84.     num_exp_hundreds = ['']+[i+'en' for i in ['c','duoc','trec','quadring','quing','sesc','septing','octing','nong']]
  85.    
  86.     #Set up dictionary and manually add hundred and thousand which don't follow the rules
  87.     num_dict = {}
  88.     num_dict[2] = 'hundred'
  89.     num_dict[3] = 'thousand'
  90.    
  91.     #Create Million through Nonillion (10^6 to 10^30)
  92.     exp_current = 6
  93.     for i in num_exp_units:
  94.         num_dict[exp_current] = i
  95.         exp_current += 3
  96.        
  97.     #Iterate through exponential hundreds (cen+)
  98.     for prefix_hundreds in num_exp_hundreds:
  99.         #Iterate through exponential tens (decillion+)
  100.         for prefix_tens in num_exp_tens:
  101.             #Iterate through exponential amounts (un, duo, tre, etc)
  102.             for prefix in num_exp_prefix:
  103.                 num_dict[exp_current] = prefix_hundreds+prefix+prefix_tens
  104.                 exp_current += 3
  105.        
  106.         #Add 'tillion' before 'decillion' after the first run
  107.         if not prefix_hundreds:
  108.             num_exp_tens = ['tillion']+num_exp_tens
  109.  
  110.     #Add zero
  111.     num_dict[0] = ''
  112.     num_dict[-1] = ''
  113.    
  114.     all_available_numbers = tuple(sorted(num_dict.keys()))
  115.    
  116.     @classmethod
  117.     def num_to_text(self, input=0, as_digits=False, **kwargs):
  118.         """Convert number between -999 and 999 to text.
  119.        It can convert higher numbers, but will use recursion on LargeNumber().to_text() to do so.
  120.        
  121.        Parameters:
  122.            input:
  123.                Number between 0 and 999 to convert.
  124.                
  125.            as_digits:
  126.                Default: False
  127.                See LargeNumber().to_text()
  128.            
  129.            kwargs:                    
  130.                only_return_decimal:
  131.                    Default: False
  132.                    If it should only return the decimal place, or full number.
  133.                    Mainly used to make the text formatting a little easier.
  134.                
  135.                print_warning:
  136.                    Default: False
  137.                    Prints a warning when a number goes out of range and recursion is used.
  138.                    The prefix to the highest exponential will have the code run on it again, so while it will be
  139.                    technically correct, it can be confusing to read.
  140.                    
  141.                use_fractions:
  142.                    Default: True
  143.                    See LargeNumber().to_text()
  144.                
  145.                fraction_precision:
  146.                    Default: 100
  147.                    See LargeNumber().to_text()
  148.        
  149.        
  150.        General Examples:
  151.            >>> NumberNames.num_to_text()
  152.            'zero'
  153.            >>> NumberNames.num_to_text(999)
  154.            'nine hundred and ninety-nine'
  155.            >>> NumberNames.num_to_text(-999)
  156.            'negative nine hundred and ninety-nine'
  157.            >>> NumberNames.num_to_text(9999)
  158.            'nine thousand, nine hundred and ninety-nine'
  159.            >>> NumberNames.num_to_text(9999, print_warning=True)
  160.            Warning: Number out of range!
  161.            'nine thousand, nine hundred and ninety-nine'
  162.            >>> NumberNames.num_to_text(572.5)
  163.            'five hundred and seventy-two point five'
  164.            >>> NumberNames.num_to_text(572.5, True)
  165.            '572 and 1/2'
  166.            >>> NumberNames.num_to_text(572.5, True, use_fractions=False)
  167.            '572.5'
  168.            >>> NumberNames.num_to_text(572.5, True, only_return_decimal=True)
  169.            '1/2'
  170.            >>> NumberNames.num_to_text(572.5, True, use_fractions=False, only_return_decimal=True)
  171.            '.5'
  172.            >>> NumberNames.num_to_text(572.5, False, only_return_decimal=True)
  173.            'point five'
  174.        
  175.        """
  176.        
  177.         use_fractions = kwargs.get('use_fractions', True)
  178.         only_return_decimal = kwargs.get('only_return_decimal', False)
  179.         fraction_precision = kwargs.get('fraction_precision', 100)
  180.         print_warning = kwargs.get('print_warning', False)
  181.        
  182.         negative_num = False
  183.         #Fix for -0
  184.         if isinstance(input, str):
  185.             if input[0] == '-':
  186.                 input = input[1:]
  187.                 negative_num = True
  188.        
  189.         #Convert to float or int
  190.         num_zeroes = 0
  191.         input = str(input)
  192.            
  193.         #Get number of zeroes that are being removed to add again later
  194.         no_zeroes = input
  195.         if '.' in input:
  196.             no_zeroes = input.rstrip('0')
  197.             num_zeroes = len(input)-len(no_zeroes)
  198.            
  199.             #Fix number of zeros being 1 too high if all decimal points = 0
  200.             if no_zeroes[-1] == '.':
  201.                 num_zeroes -= 1
  202.                 no_zeroes += '0'
  203.        
  204.         input = Decimal(no_zeroes)
  205.        
  206.         #Make sure number is positive
  207.         if input < 0:
  208.             input = input*-1
  209.             negative_num = True
  210.            
  211.         #If only integers should be returned
  212.         if as_digits:
  213.             output = str(input)+'0'*num_zeroes
  214.            
  215.             #Re-add the negative sign
  216.             if negative_num:
  217.                 output = '-' + output
  218.                
  219.             if '.' in output:
  220.                
  221.                 #Split output for when it's been combined with another exponential value (must be between 0 and 1)
  222.                 decimal_output = '0.'+output.split('.')[1]
  223.                
  224.                 #Attempt to calculate fraction of decimal point
  225.                 fraction_output = CalculateFraction.find(float(decimal_output), fraction_precision)
  226.                 if fraction_output and use_fractions:
  227.                     fraction_suffix = CalculateFraction.suffix(*fraction_output)
  228.                     fraction_prefix = ''
  229.                    
  230.                     #Add prefix if not only returning the decimal
  231.                     if not only_return_decimal:
  232.                         fraction_prefix = output.split('.')[0]+' and '
  233.                    
  234.                     return '{p}{0}/{1}{s}'.format(*fraction_output, s=fraction_suffix, p=fraction_prefix)
  235.                    
  236.                 else:
  237.                     #Return decimal points if 0.x
  238.                     if only_return_decimal:
  239.                         return '.'+str(output.split('.')[1])
  240.                     #Return number with decimals if not between 0 and 1
  241.                     else:
  242.                         return str(output)
  243.            
  244.             return output
  245.        
  246.         #If number out of range, recursively use LargeNumber
  247.         if 0 > input or input > 999:
  248.             if print_warning:
  249.                 print "Warning: Number out of range!"
  250.             return LargeNumber(input).to_text(digits=as_digits)
  251.        
  252.         #Break down number into separate parts
  253.         output_hundreds = int(input/100)
  254.         output_tens = int((input%100)/10)
  255.         output_units = int(input%10)
  256.         output_decimals = str(input).split('.')
  257.        
  258.         #Fill output decimals with extra zeroes
  259.         if len(output_decimals)>1:
  260.             output_decimals[1] += '0'*num_zeroes
  261.        
  262.         output_text = ''
  263.         #If number is negative
  264.         if negative_num:
  265.             output_text += 'negative '
  266.         #If number is above 100
  267.         if output_hundreds:
  268.             output_text += self.num_units[output_hundreds] + ' hundred'
  269.         #If last 2 digits are above 10
  270.         if output_tens:
  271.             if output_hundreds:
  272.                 output_text += ' and '
  273.             if output_tens != 1:
  274.                 output_text += self.num_tens[output_tens-2]
  275.             else:
  276.                 output_text += self.num_teens[output_units]
  277.         #If last digit is above 0
  278.         if output_units:
  279.             #If last two digits are not between ten and nineteen (added in the tens)
  280.             if output_tens != 1:
  281.                 if output_tens:
  282.                     output_text += '-'
  283.                 elif output_hundreds:
  284.                     output_text += ' and '
  285.                 output_text += self.num_units[output_units]
  286.         #Add a zero
  287.         if not (output_hundreds or output_tens or output_units) and not only_return_decimal:
  288.             output_text += self.num_units[output_units]
  289.            
  290.         #Add the decimal points
  291.         if len(output_decimals)>1:
  292.             output_text += ' point'
  293.             if only_return_decimal:
  294.                 output_text = 'point'
  295.            
  296.             #Write list of decimals
  297.             for i in output_decimals[1]:
  298.                 output_text += ' '+self.num_units[int(i)]
  299.                
  300.         return output_text
  301.  
  302.  
  303. class CalculateFraction(object):
  304.     """Class containing functions for calculating fractions.
  305.    Can find the numberator and denominator, and calculate the suffix based on those values.
  306.    
  307.    Functions:
  308.        find:
  309.            Convert a decimal value into fraction.
  310.        suffix:
  311.            Calculate suffix for fraction.
  312.    """
  313.  
  314.     @staticmethod
  315.     def find(input, precision=100):
  316.         """Convert a decimal into a fraction if possible.
  317.        
  318.        Will check up to the value of precision, so a precision of 10 will not find 1/11th or below.
  319.        Recommended to use 100, but no more.
  320.        Uses float so isn't too precise, as using Decimal is noticeably slower.
  321.        
  322.        Complexity is the sum of 0 to precision, so be careful as higher values exponentially take longer.
  323.        
  324.        Parameters:
  325.            input:
  326.                Floating point number to find fraction of.
  327.            
  328.            precision:
  329.                Default: 100
  330.                Maximum number of calculations to do.
  331.                The calculation time increases exponentially, as the number of iterations is the sum of
  332.                one to precision. (eg. precision 100 is sum(range(100)) or 4950 calculations)
  333.        
  334.        >>> CalculateFraction.find(0.5)
  335.        (1, 2)
  336.        >>> CalculateFraction.find('0.3')
  337.        (3, 10)
  338.        >>> CalculateFraction.find(1.0/4.0, 4)
  339.        (1, 4)
  340.        >>> CalculateFraction.find(1.0/4.0, 3)
  341.        >>> CalculateFraction.find(5.5)
  342.        (6, 2)
  343.        >>> CalculateFraction.find(5)
  344.        """
  345.        
  346.         #Convert to float
  347.         if not isinstance(input, float):
  348.             input = float(input)
  349.            
  350.         #Make sure it is between 0 and 1
  351.         original_input = int(input)+1
  352.         if not (0 < input < 1):
  353.             input = input%1
  354.            
  355.         #Loop through all calculations until a match is found
  356.         for j in xrange(2, precision+1):
  357.             j = float(j)
  358.             for i in xrange(1, int(j)):
  359.                 if i/j == input:
  360.                     return i*original_input, int(j)
  361.    
  362.     @staticmethod
  363.     def suffix(x, y):
  364.         """Calculate fraction suffix based on numerator (x) and denominator (y).
  365.        Will use plural if numerator is above 1.
  366.        
  367.        >>> CalculateFraction.suffix(1, 3)
  368.        'rd'
  369.        >>> CalculateFraction.suffix(1, 4)
  370.        ''
  371.        >>> CalculateFraction.suffix(1, 5)
  372.        'th'
  373.        >>> CalculateFraction.suffix(2, 11)
  374.        'ths'
  375.        >>> CalculateFraction.suffix(1, 21)
  376.        'st'
  377.        """
  378.        
  379.         #Convert to str and get important characters
  380.         y = str(y)
  381.         x = str(x)
  382.         last_num = y[-1]
  383.         try:
  384.             second_num = y[-2]
  385.         except IndexError:
  386.             second_num = 0
  387.        
  388.         #Define rules
  389.         if y == '1':
  390.             suffix = ''
  391.         elif last_num == '3':
  392.             suffix = 'rd'
  393.         elif second_num == '1':
  394.             suffix = 'th'
  395.         elif last_num == '2':
  396.             if second_num:
  397.                 suffix = 'nd'
  398.             else:
  399.                 suffix = ''
  400.         elif last_num == '4' and not second_num:
  401.             suffix = ''
  402.         elif last_num == '1':
  403.             suffix = 'st'
  404.         else:
  405.             suffix = 'th'
  406.        
  407.         #Make plural if x is above 1
  408.         if x not in ('1', '0') and suffix:
  409.             suffix += 's'
  410.            
  411.         return suffix  
  412.  
  413.  
  414.  
  415. class LargeNumber(Decimal):
  416.     """Convert any number of any precision into text.
  417.    
  418.    Functions:
  419.        to_text:
  420.            Convert a number into text.
  421.        quick:
  422.            Automatically sets some values and runs to_text().
  423.        format_input:
  424.            Convert the input into decimal format and set precision.
  425.        remove_exponent:
  426.            Remove an exponent from a Decimal value.
  427.        _round_with_precision:
  428.            Accurate way of rounding between 0 and 1.
  429.        _find_matching_exp:
  430.            Find the nearest low exponential value.
  431.        _calculate_number_parts:
  432.            Split a number into separate exponentials.
  433.    """
  434.    
  435.     def __init__(self, input, **kwargs):
  436.        
  437.         #Copy over class objects from decimal
  438.         Decimal.__init__(input)
  439.         '''
  440.        To do: add custom functions to output LargeNumber instead of Decimal
  441.        add, subtract, multiply, etc
  442.        '''
  443.         self.input = self.format_input(input, True)
  444.         self.all_available_numbers = NumberNames.all_available_numbers
  445.    
  446.     def __repr__(self):
  447.         """Return class with number."""
  448.        
  449.         #Remove the exponent if a large int/float value is input
  450.         #Squared length seems to stop precision errors but increase if error happens
  451.         if 'E+' in str(self.input):
  452.             original_precision = getcontext().prec
  453.             getcontext().prec = len(str(self.input))**2
  454.             formatted_input = self.remove_exponent(self.input)
  455.             getcontext().prec = original_precision
  456.         else:
  457.             formatted_input = self.input
  458.            
  459.         return "LargeNumber('{}')".format(formatted_input)
  460.    
  461.     @staticmethod
  462.     def format_input(input, set_prec=False):
  463.         """Format the input to remove spaces and new lines.
  464.        Also set the precision to the length of the input, as too low
  465.        can cause errors with calculations on large numbers.
  466.        
  467.        Returns the number in Decimal() format.
  468.        
  469.        Parameters:
  470.            input:
  471.                Number or string to convert to Decimal value.
  472.                
  473.            set_prec:
  474.                If the precision should be set.
  475.                The LargeNumber class needs this doing, but for general use there's no need.
  476.        
  477.        >>> LargeNumber.format_input(4264.425)
  478.        Decimal('4264.425')
  479.        >>> LargeNumber.format_input('''
  480.        ... 1 000 000 000
  481.        ... 000 000 000
  482.        ... ''')
  483.        Decimal('1000000000000000000')
  484.        """
  485.         input = str(input).replace(" ","").replace("\n","")
  486.         getcontext().prec = max(28, len(input))
  487.         return Decimal(input)
  488.    
  489.     @staticmethod
  490.     def _round_with_precision(input, num_decimals=None, force_decimals=False):
  491.         """Accurately round a number between 0 and 1.
  492.        Used because the round() function doesn't have enough precision.
  493.        If the output number is rounded and has zeroes at the end, trim the zeroes only if the
  494.        output number is different from the input, otherwise keep the zeroes.
  495.        
  496.        Parameters:
  497.            
  498.            input:
  499.                Value to round.
  500.                Will only work if value is between 0 and 1, although it won't throw an error otherwise.
  501.                Recommended to input as string or Decimal format, as to not lose precision.
  502.                
  503.            num_decimals:
  504.                Default: None
  505.                See LargeNumber().to_text()
  506.                    
  507.            force_decimals:
  508.                Default: False
  509.                See LargeNumber().to_text()
  510.        
  511.        >>> LargeNumber._round_with_precision(0.3)
  512.        '0.3'
  513.        >>> LargeNumber._round_with_precision(0.54201530)
  514.        '0.5420153'
  515.        >>> LargeNumber._round_with_precision(0.54201530, num_decimals=3)
  516.        '0.542'
  517.        >>> LargeNumber._round_with_precision(0.54201530, num_decimals=4)
  518.        '0.5420'
  519.        >>> LargeNumber._round_with_precision(0.54200000, num_decimals=4)
  520.        '0.542'
  521.        >>> LargeNumber._round_with_precision(0.54200001, num_decimals=4)
  522.        '0.5420'
  523.        >>> LargeNumber._round_with_precision(1)
  524.        '1.0'
  525.        """
  526.         input = str(input)
  527.         if '.' in str(input):
  528.             original_input = input.rstrip('0')
  529.         else:
  530.             original_input = input+'.0'
  531.         input = Decimal(original_input)
  532.        
  533.         #Accurately round the number
  534.         if num_decimals is not None:
  535.            
  536.             #Increase the precision to stop errors on large num_decimals values
  537.             current_context = getcontext().prec
  538.             getcontext().prec = max(current_context, num_decimals*1.1)
  539.             if num_decimals:
  540.                 input = input.quantize(Decimal('0.'+'0'*(num_decimals-1)+'1'))
  541.             else:
  542.                 input = input.quantize(Decimal('1'))
  543.             getcontext().prec = current_context
  544.         input = str(input)
  545.        
  546.         if force_decimals:
  547.            
  548.             #Convert output to string and fill in decimals
  549.             if '.' not in input:
  550.                 if num_decimals is not None:
  551.                     input += '.'+'0'*num_decimals
  552.                 else:
  553.                     input += '.0'
  554.                
  555.             elif num_decimals is not None:
  556.                 current_decimals = len(input.split('.')[1])
  557.                 input += '0'*(num_decimals-current_decimals)
  558.        
  559.         #Trim decimal places if not forcing decimals, and if decimal places haven't been cut off
  560.         # eg. 70500 = 70.5 thousand, no matter the decimal places
  561.         # however, 70500.005 = 70.500005, or to 3 decimal places is 70.500 thousand
  562.         if not force_decimals and original_input == input.rstrip('0'):
  563.             input = input.rstrip('0')
  564.             if input[-1] == '.':
  565.                 input = input[:-1]
  566.                
  567.         return input
  568.    
  569.     @classmethod
  570.     def remove_exponent(self, input):
  571.         '''Remove exponent of a Decimal value.
  572.        If input is in the wrong format, code will run again with the converted input.
  573.    
  574.        >>> LargeNumber.remove_exponent(Decimal('5E+3'))
  575.        Decimal('5000')
  576.        >>> LargeNumber.remove_exponent(5000)
  577.        Decimal('5000')
  578.        '''
  579.         try:
  580.             if input == input.to_integral():
  581.                 return input.quantize(Decimal(1))
  582.             else:
  583.                 return input.normalize()
  584.         except AttributeError:
  585.             return self.remove_exponent(self.format_input(input))
  586.    
  587.     @staticmethod
  588.     def _find_matching_exp(input, all_available_numbers):
  589.         """Iterate through list of numbers to find the nearest low match.
  590.        
  591.        Parameters:
  592.            input:
  593.                Calculated exponential value
  594.                
  595.            all_available_numbers:
  596.                All exponential values
  597.                
  598.        
  599.        >>> all_available_numbers = NumberNames.all_available_numbers
  600.        >>> LargeNumber._find_matching_exp(4, all_available_numbers)
  601.        3
  602.        >>> LargeNumber._find_matching_exp(643, all_available_numbers)
  603.        642
  604.        >>> LargeNumber._find_matching_exp(10000, all_available_numbers)
  605.        3000
  606.        >>> LargeNumber._find_matching_exp(-47, all_available_numbers)
  607.        -1
  608.        """
  609.         for i in xrange(len(all_available_numbers)):
  610.             try:
  611.                 #Return first match
  612.                 if input < all_available_numbers[i+1]:
  613.                     return all_available_numbers[i]
  614.             #If number is higher than max index
  615.             except IndexError:
  616.                 return all_available_numbers[i]
  617.    
  618.     def _calculate_number_parts(self, **kwargs):
  619.         """Used by to_text() for calculating the output. Returns dictionary of numbers matching their exponential.
  620.        The exponentials are calculated from the NumberNames class.
  621.        
  622.        Parameters:
  623.            kwargs:
  624.                min_amount:
  625.                    Default: 1
  626.                    See LargeNumber().to_text()
  627.                    
  628.                min_amount_limit:
  629.                    Default: -1 (infinite)
  630.                    See LargeNumber().to_text()
  631.                    
  632.                max_iterations:
  633.                    Default: -1 (infinite)
  634.                    See LargeNumber().to_text()
  635.                    
  636.                num_decimals:
  637.                    Default: None
  638.                    See LargeNumber().to_text()
  639.                    
  640.                force_decimals:
  641.                    Default: None
  642.                    See LargeNumber().to_text()
  643.                    
  644.        
  645.        Output examples:
  646.        key:value relates to value*(10^key), aside from when the key is -1, in which the value is the decimal remainder.
  647.            >>> LargeNumber(5000)._calculate_number_parts()
  648.            {3: '5', -1: '0'}
  649.            >>> LargeNumber(-123456)._calculate_number_parts()
  650.            {0: '56', 2: '4', 3: '-123', -1: '0'}
  651.            >>> LargeNumber("100.72")._calculate_number_parts()
  652.            {2: '1', -1: '0.72'}
  653.        """
  654.         max_iterations = kwargs.get('max_iterations', -1)
  655.         num_decimals = kwargs.get('num_decimals', None)
  656.         force_decimals = kwargs.get('force_decimals', False)
  657.        
  658.         min_amount = Decimal(str(kwargs.get('min_amount', 1)))
  659.         min_amount_limit = Decimal(str(kwargs.get('min_amount_limit', -1)))
  660.        
  661.         #If more than one iteration, set the min amount to 1 otherwise you get an infinite loop
  662.         if max_iterations and min_amount < 1:
  663.             min_amount = Decimal('1')
  664.            
  665.         min_offset = min_amount.logb()
  666.        
  667.         input = self.input
  668.         num_output = {}
  669.         num_exp = 1
  670.         first_run = True      
  671.        
  672.         #Get multiplier from the min_amount variable
  673.         min_amount_multiplier = Decimal(('%.2E'%min_amount).split('E')[0])
  674.        
  675.         #Match values to exponentials
  676.         count = 0
  677.         while num_exp > 0 and (input >= 1 or not count):
  678.            
  679.             #Reset min_amount to default
  680.             if count == min_amount_limit:
  681.                 min_offset = Decimal('1').logb()
  682.             count += 1
  683.            
  684.             #Figure which name to use
  685.             if input:
  686.                 num_digits = (input/min_amount_multiplier).logb()
  687.             else:
  688.                 #Fix to stop logb() error if input is zero
  689.                 num_digits = Decimal(1).logb()
  690.                
  691.             num_digits -= min_offset
  692.             num_exp = self._find_matching_exp(num_digits, self.all_available_numbers)
  693.            
  694.             #Fix when given a high min_amount value that pushes num_exp below 0
  695.             if num_exp <= 0:
  696.                 num_exp = 0
  697.            
  698.             #Fix for values between 0 and 1
  699.             if -1 < input < 1:
  700.                 num_exp = 0
  701.                
  702.             #Get matching amount
  703.             current_multiplier = pow(Decimal(10), Decimal(num_exp))
  704.             current_output = input/current_multiplier
  705.            
  706.             #Add to output
  707.             if len(num_output)+1 > max_iterations and max_iterations >= 0:
  708.                 current_output = self._round_with_precision(current_output, num_decimals, force_decimals)
  709.                 num_output[num_exp] = (str(current_output))
  710.                 input = 0
  711.                 break
  712.                
  713.             else:
  714.                
  715.                 #Continue
  716.                 num_output[num_exp] = (str(current_output).split('.')[0])
  717.                 input = input%pow(Decimal(10), Decimal(num_exp))
  718.            
  719.             #Make number positive after first run
  720.             if input < 0:
  721.                 input *= -1
  722.                
  723.         num_output[-1] = str(input)
  724.        
  725.         #Re-run the code if only one iteration is output (basically a fix for low min_amount not working)
  726.         if len(num_output) == 2 and min_amount != Decimal(str(kwargs.get('min_amount', 1))) and max_iterations != 0:
  727.             kwargs['max_iterations'] = 0
  728.             num_output = self._calculate_number_parts(**kwargs)
  729.        
  730.         return num_output
  731.  
  732.    
  733.     def to_text(self, **kwargs):
  734.         """Convert the number to a text representation.
  735.        
  736.        Parameters:
  737.            kwargs:
  738.                digits:
  739.                    Default: True
  740.                    If the output should be text and digits or just text (53 thousand or fifty-three thousand).
  741.                    
  742.                    >>> LargeNumber(7654321.5).to_text(digits=True)
  743.                    '7 million, 654 thousand, 3 hundred and 21.5'
  744.                    >>> LargeNumber(7654321.5).to_text(digits=False)
  745.                    'seven million, six hundred and fifty-four thousand, three hundred and twenty-one point five'
  746.                    
  747.                min_amount:
  748.                    Default: 1
  749.                    Minimum amount a value can be to match an exponential.
  750.                    Cannot be below 1 if max_iterations is above 0 (will automatically raise to 1).
  751.                     (eg. You could have '0.25 billion' but not '0.25 billion and 0.6 million and 2')
  752.                    If set below 1 and there is only 1 result, the code will be rerun with max_iterations as 0.
  753.                    Example:
  754.                        The default is 1, so if the input was 1000 and is reduced by 1, it would switch down to hundreds.
  755.                        If it was set to 0.1, 100 and above would display as 0.1 thousand.
  756.                        If it was set to 10, it would move down to the previous prefix, which in this case would be 10 hundred.
  757.                    
  758.                    >>> LargeNumber(7654321).to_text(num_decimals=3, max_iterations=0, min_amount=1)
  759.                    '7.654 million'
  760.                    >>> LargeNumber(7654321).to_text(num_decimals=3, max_iterations=0, min_amount=0.001)
  761.                    '0.008 billion'
  762.                    >>> LargeNumber(7654321).to_text(num_decimals=3, max_iterations=0, min_amount=10)
  763.                    '7654.321 thousand'
  764.                    >>> LargeNumber(7654321).to_text(num_decimals=3, max_iterations=0, min_amount=10000)
  765.                    '76543.21 hundred'
  766.                    >>> LargeNumber(7654321).to_text(num_decimals=3, min_amount=1)
  767.                    '7 million, 654 thousand, 3 hundred and 21'
  768.                    >>> LargeNumber(7654321).to_text(num_decimals=3, min_amount=0.001)
  769.                    '7 million, 654 thousand, 3 hundred and 21'
  770.                    >>> LargeNumber(7654321).to_text(num_decimals=3, min_amount=10)
  771.                    '7654 thousand and 321'
  772.                    
  773.                min_amount_limit:
  774.                    Default: -1 (infinite)
  775.                    How many iterations to apply min_amount to.
  776.                    For example you may only want the min_amount to apply to the first number, and have every subsequent number
  777.                     treated normally.
  778.                    
  779.                    >>> LargeNumber(7654321).to_text(num_decimals=3, min_amount=1)
  780.                    '7 million, 654 thousand, 3 hundred and 21'
  781.                    >>> LargeNumber(7654321).to_text(num_decimals=3, min_amount=10)
  782.                    '7654 thousand and 321'
  783.                    >>> LargeNumber(7654321).to_text(num_decimals=3, min_amount=10, min_amount_limit=1)
  784.                    '7654 thousand, 3 hundred and 21'
  785.                    >>> LargeNumber(7654321).to_text(num_decimals=3, min_amount=10, min_amount_limit=2)
  786.                    '7654 thousand and 321'
  787.                    
  788.                max_iterations:
  789.                    Default: -1 (infinite)
  790.                    Number of separate prefixes to split the number into.
  791.                    Set to 0 to only have a single suffix, or below 0 to display the entire number.
  792.                    If used, the lowest prefix will have a decimal representation of the remaining number.
  793.                     (eg. 5 hundred and 52 turns to 5.52 hundred)
  794.                    
  795.                    >>> LargeNumber(7654321).to_text(num_decimals=3, min_amount=1, max_iterations=-1)
  796.                    '7 million, 654 thousand, 3 hundred and 21'
  797.                    >>> LargeNumber(7654321).to_text(num_decimals=3, min_amount=1, max_iterations=0)
  798.                    '7.654 million'
  799.                    >>> LargeNumber(7654321).to_text(num_decimals=3, min_amount=1, max_iterations=1)
  800.                    '7 million and 654.321 thousand'
  801.                    >>> LargeNumber(7654321).to_text(num_decimals=3, min_amount=1, max_iterations=2)
  802.                    '7 million, 654 thousand and 3.21 hundred'
  803.                    
  804.                use_fractions:
  805.                    Default: False
  806.                    Attempts to convert a decimal point into a fraction through a brute force method.
  807.                    Will also attempt to calculate a prefix.
  808.                    For now, it will only work if decimals is enabled, as I haven't coded in the word representations for everything.
  809.                    Due to the precision of floating point numbers, any recurring numbers must be input as a string or calculated
  810.                     in the Decimal format.
  811.                    It appears to require 16 decimals, where as floating point division gives you 12.
  812.                    
  813.                    >>> LargeNumber(7654321.5).to_text(use_fractions=True)
  814.                    '7 million, 654 thousand, 3 hundred and 21 and 1/2'
  815.                    >>> LargeNumber(7654321.75).to_text(use_fractions=True)
  816.                    '7 million, 654 thousand, 3 hundred and 21 and 3/4'
  817.                    >>> LargeNumber(7654321.39).to_text(use_fractions=True)
  818.                    '7 million, 654 thousand, 3 hundred and 21 and 39/100ths'
  819.                    >>> LargeNumber(7654321.383).to_text(use_fractions=True)
  820.                    '7 million, 654 thousand, 3 hundred and 21.383'
  821.                    >>> LargeNumber(1.0/3.0).to_text(use_fractions=True)
  822.                    '0.333333333333'
  823.                    >>> LargeNumber(Decimal(1)/Decimal(3)).to_text(use_fractions=True)
  824.                    '1/3rd'
  825.                    >>> LargeNumber('0.333333333333333').to_text(use_fractions=True)
  826.                    '0.333333333333333'
  827.                    >>> LargeNumber('0.3333333333333333').to_text(use_fractions=True)
  828.                    '1/3rd'
  829.                    
  830.                fraction_precision:
  831.                    Default: 100
  832.                    The precision of the above mentioned brute force method.
  833.                    Determines the maximum value it will search for.
  834.                    
  835.                    >>> LargeNumber(7654321.39).to_text(use_fractions=True, fraction_precision=100)
  836.                    '7 million, 654 thousand, 3 hundred and 21 and 39/100ths'
  837.                    >>> LargeNumber(7654321.39).to_text(use_fractions=True, fraction_precision=50)
  838.                    '7 million, 654 thousand, 3 hundred and 21.39'
  839.                    >>> LargeNumber(7654321.393).to_text(use_fractions=True, fraction_precision=1000)
  840.                    '7 million, 654 thousand, 3 hundred and 21 and 393/1000ths'
  841.                    
  842.                num_decimals:
  843.                    Default: None (inifinite with no extra zeroes)
  844.                    Number of decimal places to display.
  845.                    
  846.                    >>> import math
  847.                    >>> LargeNumber(math.pi).to_text()
  848.                    '3.14159265359'
  849.                    >>> LargeNumber(math.pi).to_text(num_decimals=2)
  850.                    '3.14'
  851.                    >>> LargeNumber(100).to_text()
  852.                    '1 hundred'
  853.                    >>> LargeNumber(100).to_text(num_decimals=3)
  854.                    '1 hundred'
  855.                    
  856.                force_decimals:
  857.                    Default: False
  858.                    If decimals should be added until the length hits num_decimals.
  859.                    If num_decimals is set, 0's will be added to match the minimum number of decimals.
  860.                    If num_decimals is not set, this will make sure every number is in a decimal format.
  861.                    
  862.                    >>> LargeNumber(100).to_text()
  863.                    '1 hundred'
  864.                    >>> LargeNumber(100).to_text(num_decimals=20)
  865.                    '1 hundred'
  866.                    >>> LargeNumber(100).to_text(force_decimals=True)
  867.                    '1.0 hundred'
  868.                    >>> LargeNumber(100).to_text(force_decimals=True, num_decimals=20)
  869.                    '1.00000000000000000000 hundred'
  870.                    >>> LargeNumber(101).to_text(force_decimals=True, max_iterations=0)
  871.                    '1.01 hundred'
  872.                
  873.                min_decimals:
  874.                    Default: 2
  875.                    Ensures each number has a minimum amount of decimals.
  876.                    Will not work unless force_decimals is True.
  877.                    If num_decimals is set below min_decimals, precision will be lost.
  878.                    
  879.                    >>> LargeNumber(100).to_text(max_iterations=0, force_decimals=True)
  880.                    '1.0 hundred'
  881.                    >>> LargeNumber(100).to_text(max_iterations=0, force_decimals=True, min_decimals=3)
  882.                    '1.000 hundred'
  883.                    >>> LargeNumber(100.31643).to_text(max_iterations=0, force_decimals=True)
  884.                    '1.0031643 hundred'
  885.                    >>> LargeNumber(100.31643).to_text(max_iterations=0, force_decimals=True, min_decimals=3)
  886.                    '1.0031643 hundred'
  887.                    >>> LargeNumber(100).to_text(max_iterations=0, force_decimals=True, num_decimals=2, min_decimals=4)
  888.                    '1.0000 hundred'
  889.                    >>> LargeNumber(0.31643).to_text(force_decimals=True, num_decimals=2, min_decimals=4)
  890.                    '0.0300'
  891.        
  892.        
  893.        General Examples:
  894.            >>> LargeNumber(100).to_text()
  895.            '1 hundred'
  896.            >>> LargeNumber(100).to_text(digits=False)
  897.            'one hundred'
  898.            >>> LargeNumber(-100).to_text()
  899.            '-1 hundred'
  900.            >>> LargeNumber(-100).to_text(digits=False)
  901.            'negative one hundred'
  902.            >>> LargeNumber(100.5).to_text()
  903.            '1 hundred and 0.5'
  904.            >>> LargeNumber(100.5).to_text(digits=False)
  905.            'one hundred point five'
  906.            >>> LargeNumber(100.5).to_text(min_amount=10)
  907.            '100.5'
  908.            >>> LargeNumber(100.5).to_text(min_amount=0.1)
  909.            '0.1005 thousand'
  910.            >>> LargeNumber(100.5).to_text(digits=False, min_amount=0.1, num_decimals=2)
  911.            'zero point one zero thousand'
  912.        
  913.        Precision, Rounding and Iteration Examples:
  914.            >>> LargeNumber(123456789.987654321).to_text()
  915.            '123 million, 456 thousand, 7 hundred and 89.988'
  916.            >>> LargeNumber('123456789.987654321').to_text()
  917.            '123 million, 456 thousand, 7 hundred and 89.987654321'
  918.            >>> LargeNumber(123456789).to_text(force_decimals=True)
  919.            '123 million, 456 thousand, 7 hundred and 89.0'
  920.            >>> LargeNumber(123456789).to_text(force_decimals=True, min_decimals=3)
  921.            '123 million, 456 thousand, 7 hundred and 89.000'
  922.            >>> LargeNumber(123456789).to_text(max_iterations=0, num_decimals=2)
  923.            '123.46 million'
  924.            >>> LargeNumber(123456789).to_text(max_iterations=1, num_decimals=2)
  925.            '123 million and 456.79 thousand'
  926.            
  927.        Minimum Amount Examples:
  928.            >>> input = 123456789987654321
  929.            >>> LargeNumber(input).to_text()
  930.            '123 quadrillion, 456 trillion, 789 billion, 987 million, 654 thousand, 3 hundred and 21'
  931.            >>> LargeNumber(input).to_text(min_amount=1000)
  932.            '123456 trillion, 789987 million, 6543 hundred and 21'
  933.            >>> LargeNumber(input).to_text(min_amount=1000, min_amount_limit=1)
  934.            '123456 trillion, 789 billion, 987 million, 654 thousand, 3 hundred and 21'
  935.            >>> LargeNumber(input).to_text(min_amount=0.001)
  936.            '123 quadrillion, 456 trillion, 789 billion, 987 million, 654 thousand, 3 hundred and 21'
  937.            >>> LargeNumber(input).to_text(min_amount=0.1, max_iterations=0)
  938.            '0.123456789987654321 quintillion'
  939.            >>> LargeNumber(input).to_text(min_amount=1000, max_iterations=0, num_decimals=3)
  940.            '123456.790 trillion'
  941.        
  942.        Fractions Examples:
  943.            >>> LargeNumber(0.5).to_text()
  944.            '0.5'
  945.            >>> LargeNumber(0.5).to_text(use_fractions=True)
  946.            '1/2'
  947.            >>> LargeNumber(0.25).to_text(use_fractions=True)
  948.            '1/4'
  949.            >>> LargeNumber(0.25).to_text(use_fractions=True, fraction_precision=4)
  950.            '1/4'
  951.            >>> LargeNumber(0.25).to_text(use_fractions=True, fraction_precision=3)
  952.            '0.25'
  953.            >>> LargeNumber(0.2).to_text(use_fractions=True)
  954.            '1/5th'
  955.        """
  956.        
  957.        
  958.         use_fractions = kwargs.get('use_fractions', False)
  959.         fraction_precision = kwargs.get('fraction_precision', 100)
  960.        
  961.         num_decimals = kwargs.get('num_decimals', None)
  962.         force_decimals = kwargs.get('force_decimals', False)
  963.         min_decimals = kwargs.get('min_decimals', None)
  964.        
  965.         as_digits = kwargs.get('digits', True)
  966.        
  967.         num_name = []
  968.         num_name_joined = ''
  969.         num_output = self._calculate_number_parts(**kwargs)
  970.        
  971.         #Get remaining decimals for later and remove from output
  972.         remaining_decimals = Decimal(num_output.pop(-1))
  973.        
  974.         #Fix for values between 0 and 1
  975.         if not num_output:
  976.             num_output[0] = 0
  977.        
  978.         #Fix to merge decimal with lowest exponential value to stop errors such as 1.000.35
  979.         if force_decimals and remaining_decimals and num_output.get(0, 0):
  980.             only_exponential = sorted(num_output.keys())[0]
  981.             num_output[only_exponential] = str(self._round_with_precision(str(Decimal(num_output[only_exponential])+
  982.                                                                               Decimal(remaining_decimals)*
  983.                                                                               Decimal('0.'+'0'*(only_exponential-1)+'1')),
  984.                                                                           num_decimals, force_decimals))
  985.             remaining_decimals = 0
  986.    
  987.         #Convert numbers to words
  988.         sorted_keys = sorted(num_output.keys())[::-1]
  989.         for i in sorted_keys:
  990.             current_value = num_output[i]
  991.             additional_value = ''
  992.             if NumberNames.num_dict[i]:
  993.                 additional_value = ' '+NumberNames.num_dict[i]
  994.            
  995.             #Avoid using fractions if using prefix (eg. 0.5 billion not 1/2 billion)
  996.             should_use_fractions = use_fractions
  997.             if i:
  998.                 should_use_fractions = False
  999.            
  1000.             #Add decimals only if it's the last number, and there's not remaining decimals
  1001.             if i == sorted_keys[-1] and not remaining_decimals:
  1002.                
  1003.                 if force_decimals:
  1004.                    
  1005.                     if num_decimals is not None:
  1006.                        
  1007.                         #Check if existing decimal points (fixes things like 46.500.000 million)
  1008.                         current_num_decimals = 0
  1009.                         if '.' in current_value:
  1010.                             current_num_decimals = len(current_value.split('.')[1])
  1011.                         else:
  1012.                             current_value += '.'
  1013.                         required_decimals = max(0, num_decimals-current_num_decimals)
  1014.                         current_value += '0'*required_decimals
  1015.                        
  1016.                     else:
  1017.                         #Stip zeroes from end of number
  1018.                         if '.' in current_value:
  1019.                             current_value = current_value.rstrip('0')
  1020.                            
  1021.                         #Only add zero if the last number is a decimal point, or also add a decimal point
  1022.                         if '.' not in current_value[:-1]:
  1023.                             if current_value[-1] != '.':
  1024.                                 current_value += '.'
  1025.                             current_value += '0'
  1026.                    
  1027.                     #Pad out the decimals
  1028.                     if min_decimals:
  1029.                         num_decimal_points = len(current_value.split('.')[1])
  1030.                         if min_decimals > num_decimal_points:
  1031.                             current_value += '0'*(min_decimals-num_decimal_points)
  1032.            
  1033.             text_num = NumberNames.num_to_text(current_value, as_digits,
  1034.                                    use_fractions=should_use_fractions,
  1035.                                    fraction_precision=fraction_precision,
  1036.                                    print_warning=True) + additional_value
  1037.            
  1038.            
  1039.             #Fix for min_amount under 0
  1040.             if i and current_value[:10] == 'zero point':
  1041.                 text_num = text_num[5:]
  1042.                
  1043.             num_name.append(text_num)
  1044.        
  1045.         #Join list
  1046.         if len(num_name)-1:
  1047.             num_name_joined += ', '.join(num_name[:-1])
  1048.         #If there are decimals, don't use a final and
  1049.         if num_name_joined:
  1050.             if remaining_decimals and as_digits:
  1051.                 num_name_joined += ', '
  1052.             else:
  1053.                 num_name_joined += ' and '
  1054.         num_name_joined += num_name[-1]
  1055.        
  1056.         #Add decimal point
  1057.         if remaining_decimals:
  1058.            
  1059.             if not remaining_decimals:
  1060.                 remaining_decimals = '0.'
  1061.                 if num_decimals is not None:
  1062.                     remaining_decimals += '0'*num_decimals
  1063.                 else:
  1064.                     remaining_decimals += '0'
  1065.            
  1066.             #Convert to text
  1067.             remaining_decimals = self._round_with_precision(remaining_decimals, num_decimals, force_decimals)
  1068.                            
  1069.             decimal_num = NumberNames.num_to_text(remaining_decimals, as_digits,
  1070.                                       use_fractions=use_fractions,
  1071.                                       fraction_precision=fraction_precision,
  1072.                                       only_return_decimal=True,
  1073.                                       print_warning=True)
  1074.            
  1075.             #Add space before 'point five'
  1076.             if not as_digits:
  1077.                 decimal_num = ' '+decimal_num
  1078.            
  1079.             #Fix to stop fractions not removing a zero (eg. 0 and 2/5ths should be 2/5ths)
  1080.             if '/' in decimal_num:
  1081.                 if num_name_joined and num_name_joined != '0':
  1082.                     num_name_joined += ' and '
  1083.                 else:
  1084.                     num_name_joined = ''
  1085.            
  1086.             #Fix if there are no units (1 hundred.5 to 1 hundred and 0.5)
  1087.             if 0 not in num_output:
  1088.                 if as_digits and '/' not in decimal_num:
  1089.                     num_name_joined += ' and 0'
  1090.                 else:
  1091.                     pass
  1092.                     #'one hundred point five' works, leaving this note here in case it needs to change
  1093.                     #num_name_joined += ' and zero'
  1094.                
  1095.             num_name_joined += decimal_num
  1096.        
  1097.         return num_name_joined
  1098.        
  1099.     def quick(self, num_decimals=3):
  1100.         """Quickly format a number with 0 iterations and 3 decimal points.
  1101.        
  1102.        Parameters:
  1103.            num_decimals:
  1104.                See LargeNumber().to_text()
  1105.        
  1106.        >>> LargeNumber('5000').quick()
  1107.        '5.000 thousand'
  1108.        >>> LargeNumber('64321764.24').quick()
  1109.        '64.322 million'
  1110.        >>> LargeNumber('-444443465400.24').quick()
  1111.        '-444.443 billion'
  1112.        """
  1113.         kwargs = {}
  1114.         kwargs['max_iterations'] = 0
  1115.         kwargs['num_decimals'] = num_decimals
  1116.         kwargs['force_decimals'] = True
  1117.         kwargs['min_decimals'] = 3
  1118.         kwargs['use_fractions'] = False
  1119.         kwargs['digits'] = True
  1120.         return self.to_text(**kwargs)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement