3
\$\begingroup\$

For my project, I need to convert dollar to cents, so I wrote the following function to safely do the conversion. The input dollar is float and the output should be int.

def safe_dollar_to_cent(dollar):
    parts = str(dollar).split('.')

    msg = 'success'
    status = 0
    if len(parts) == 1:
        return status, int(parts[0])*100, msg

    decimal_part = parts[1]
    if len(decimal_part) > 2:
        decimal_part = decimal_part[0:2]
        msg = 'dollar has been truncated: {} -> {}'.\
              format(parts[1], decimal_part)
        status = 1

    ret = int(parts[0]) * 100
    multiplier = 10
    for i, s in enumerate(decimal_part):
        ret += int(s) * multiplier
        multiplier /= 10

    return status, ret, msg

I am posting here for seeking other some pythonic way of doing this job.

Update:

my input is expected to be float, the return value should be int. The reason of this implementation is that I found the following incorrect computation.

18.90 * 100 = 1889.9999999999998

\$\endgroup\$
5
  • \$\begingroup\$ What are you expecting dollar to be, a number or a string? \$\endgroup\$ Commented Feb 25, 2016 at 11:56
  • 7
    \$\begingroup\$ Just to understand what seems overly complicated, what was wrong with return int(dollar * 100)? \$\endgroup\$ Commented Feb 25, 2016 at 11:58
  • 4
    \$\begingroup\$ You aren't using fixed-point dollar values? Shame on whoever provided you that interface, floating-point math is anathema for financial stuff. docs.python.org/2/library/decimal.html \$\endgroup\$
    – JAB
    Commented Feb 25, 2016 at 15:47
  • \$\begingroup\$ If you are in total control of your project, consider using cents instead of dollars. Otherwise, consider using Decimal numbers and you w ill have no issue by directly doing int(d * 100.), ((d * 100.) - int(d * 100.)) / 100. to return a pair having the result and the data you lost \$\endgroup\$ Commented Feb 25, 2016 at 17:53
  • \$\begingroup\$ About your update: you should not use floating point to store money values: what you type as 18.90 is really stored as a binary fractional number, with a little error. But anyway if you use doubles (8 bytes, precision 52 bits), all you need is rounding after the multiply. The error will disappear \$\endgroup\$
    – edc65
    Commented Feb 25, 2016 at 20:25

3 Answers 3

12
\$\begingroup\$

I don't like the 3 argument return you're doing. You're using status and msg to actually do the same thing. They both return a signal of success unless the dollar is truncated. You don't need both pieces of information. Personally, I'd just say that you only need to print a note about the dollar being truncated so that the function only returns the cents value.

Now, you're also overlooking a very simple formula to convert this:

cents = dollars * 100

You don't need anything more complicated than that for the basic function:

def safe_dollar_to_cent(dollar):
    cents = dollar * 100
    return cents

If you're not sure that dollar will be a number, you can try converting it:

def safe_dollar_to_cent(dollar):
    cents = float(dollar) * 100
    return cents

As for truncating, I think it's better as an option. Let the user choose whether or not something will be truncated:

def safe_dollar_to_cent(dollar, truncate=True):
    cents = float(dollar) * 100
    if truncate:
        return int(cents)
    else:
        return cents

int will turn cents into a whole number instead of a decimal, so it's effectively truncating everything after the ..

\$\endgroup\$
4
  • 1
    \$\begingroup\$ I think the statements in the if truncate are swapped. Also, instead of printing a note, maybe use warnings.warn? \$\endgroup\$ Commented Feb 25, 2016 at 15:15
  • \$\begingroup\$ @SjoerdJobPostmus You are correct! Thanks for catching that. You could post a separate answer about warnings.warn. \$\endgroup\$ Commented Feb 25, 2016 at 15:40
  • 6
    \$\begingroup\$ 18.90 * 100 = 1889.9999999999998 in my python 2.7.5. \$\endgroup\$
    – Alex
    Commented Feb 25, 2016 at 17:49
  • 1
    \$\begingroup\$ @alex is right. this answer is wrong. \$\endgroup\$ Commented Jan 24, 2019 at 3:40
1
\$\begingroup\$

First of all, good job on the solution. It works nicely. Contrary to the other answer here, returning boolean success and message is a industry convention.

What I would suggest is to split the validation and conversion into two functions like this:

def validate_dollar(dollar):
    return dollar * 100 % 1 == 0

def safe_dollar_to_cent(dollar):
    if validate_dollar(dollar):
        return {'success': True, 'cents': int(dollar*100)}
    else:
        return {'success': False, 'msg': 'Malformed input'}

print(safe_dollar_to_cent(10.9))
print(safe_dollar_to_cent(10))
print(safe_dollar_to_cent(10.90))
print(safe_dollar_to_cent(10.999))
\$\endgroup\$
1
\$\begingroup\$

As stated by OP some values are not directly representable in float thus yielding the nearest value. I can offer two alternatives:

  • Adding a call to the round() function to the proposed solutions of just multiply by 100

    int(round(float(dollar)*100))
    
  • Just removing the decimal point by editing as a string

    ("%.2f" % float(amount)).replace('.', '')
    
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.