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\$
  • \$\begingroup\$ What are you expecting dollar to be, a number or a string? \$\endgroup\$ – SuperBiasedMan Feb 25 '16 at 11:56
  • 7
    \$\begingroup\$ Just to understand what seems overly complicated, what was wrong with return int(dollar * 100)? \$\endgroup\$ – 409_Conflict Feb 25 '16 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 Feb 25 '16 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\$ – Luis Masuelli Feb 25 '16 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 Feb 25 '16 at 20:25
11
\$\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\$
  • 1
    \$\begingroup\$ I think the statements in the if truncate are swapped. Also, instead of printing a note, maybe use warnings.warn? \$\endgroup\$ – Sjoerd Job Postmus Feb 25 '16 at 15:15
  • \$\begingroup\$ @SjoerdJobPostmus You are correct! Thanks for catching that. You could post a separate answer about warnings.warn. \$\endgroup\$ – SuperBiasedMan Feb 25 '16 at 15:40
  • 5
    \$\begingroup\$ 18.90 * 100 = 1889.9999999999998 in my python 2.7.5. \$\endgroup\$ – Alex Feb 25 '16 at 17:49
  • 1
    \$\begingroup\$ @alex is right. this answer is wrong. \$\endgroup\$ – JonTroncoso Jan 24 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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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