# Exercise #33: Comma-Formatted Numbers

commaFormat(12345)   '12,345'

In the US and UK, the digits of numbers are grouped with commas every three digits. For example, the number 79033516 is written as 79,033,516 for readability. In this exercise, you’ll write a function that takes a number and returns a string of the number with comma formatting.

Exercise Description

Write a `commaFormat()` function with a number parameter. The argument for this parameter can be an integer or floating-point number. Your function returns a string of this number with proper US/UK comma formatting. There is a comma after every third digit in the whole number part. There are no commas at all in the fractional part: The proper comma formatting of 1234.5678 is 1,234.5678 and not 1,234.567,8.

These Python `assert` statements stop the program if their condition is `False`. Copy them to the bottom of your solution program. Your solution is correct if the following `assert` statements’ conditions are all True:

assert commaFormat(1) == '1'

assert commaFormat(10) == '10'

assert commaFormat(100) == '100'

assert commaFormat(1000) == '1,000'

assert commaFormat(10000) == '10,000'

assert commaFormat(100000) == '100,000'

assert commaFormat(1000000) == '1,000,000'

assert commaFormat(1234567890) == '1,234,567,890'

assert commaFormat(1000.123456) == '1,000.123456'

Try to write a solution based on the information in this description. If you still have trouble solving this exercise, read the Solution Design and Special Cases and Gotchas sections for additional hints.

Prerequisite concepts: strings, `str()`, `in` operator, `index()`, slices, string concatenation

Solution Design

Despite involving numbers, this exercise is actually about text manipulation. The characters of the string just happen to be numeric digits.

First, we convert the `number` argument to a string with the `str()` function. This will work whether the number is an integer or a floating-point number. Once we have the number as a string, we can check for the existence of a period which indicates it was a floating-point number with a fractional part. The expression `'.' in number` evaluates to True if the string in number has a period character. Next, we can use `number.index('.')` to find the index of this period character. (The `index()` method raises a ValueError exception if `'.'` doesn’t appear in the string, but the previous `'.'` in number expression being `True` guarantees that it does.)

We need to remove this fractional part from `number` while saving it in another variable to add back in later. This way we are only adding commas to the whole number part of the `number` argument, whether or not it was an integer or floating-point number.

Next, let’s start variables named `triplet` and `commaNumber` as blank strings. As we loop over the digits of `number`, the `triplet` variable will store digits until it has three of them, at which point we add them to `commaNumber` (which contains the comma-formatted version of number) with a comma. The first time we add triplet to `commaNumber`, there will be an extra comma at the end of a number. For example, the triplet '248' gets added to `commaNumber` as `'248,'`. We can remove the extra comma just before returning the number.

We need to loop starting at the one’s place in the number and moving left, so our `for` loop should work in reverse: `for i in range(len(number) - 1, -1, -1)`. For example, if `number` is `4096`, then the first iteration of the loop can access `number[3]`, the second iteration can access `number[2]`, and so on. This way the first triplet ends up being `'096'` instead of `'409'`.

If the loop finishes and there are leftover digits in triplet, add them to `commaNumber` with a comma. Finally, return `commaNumber` except with the comma at the end truncated: `commaNumber[:-1]` evaluates to everything in `commaNumber` except the last character.

Finally, we need to add the fractional part back in the number if there was one originally.

Special Cases and Gotchas

Several bugs that can occur in our code. We should consider them ahead of writing our code so we can ensure they don’t sneak past us. These bugs could include:

·       A comma at the end of number, e.g., `386` producing `'386,'`

·       A comma at the front of a number, e.g., `499000` producing `',499,000'`

·       Commas appearing in the fraction part, e.g., `12.3333` producing `'12.3,333'`

·       Grouping triplets in reverse order, e.g., `4096` producing `'409,6'`

However you tackle this exercise, ensure that your code doesn’t make any of these mistakes.

Now try to write a solution based on the information in the previous sections. If you still have trouble solving this exercise, read the Solution Template section for additional hints.

Solution Template

Try to first write a solution from scratch. But if you have difficulty, you can use the following partial program as a starting place. Copy the following code from https://invpy.com/commaformat-template.py and paste it into your code editor. Replace the underscores with code to make a working program:

def commaFormat(number):

# Convert the number to a string:

number = str(____)

# Remember the fractional part and remove it from the number, if any:

if '.' in ____:

fractionalPart = number[number.index(____):]

number = number[:number.index('.')]

else:

fractionalPart = ''

# Create a variable to hold triplets of digits and the

# comma-formatted string as it is built:

triplet = ____

commaNumber = ____

# Loop over the digits starting on the right side and going left:

for i in range(len(number) - 1, ____, ____):

# Add the digits to the triplet variable:

triplet = ____[i] + ____

# When the triplet variable has three digits, add it with a

# comma to the comma-formatted string:

if ____(triplet) == ____:

commaNumber = triplet + ',' + ____

# Reset the triplet variable back to a blank string:

triplet = ____

# If the triplet has any digits left over, add it with a comma

# to the comma-formatted string:

if triplet != '':

commaNumber = ____ + ',' + ____

# Return the comma-formatted string:

return ____[:____] + fractionalPart

The complete solution for this exercise is given in Appendix A and https://invpy.com/commaformat.py. You can view each step of this program as it runs under a debugger at https://invpy.com/commaformat-debug/.