2
\$\begingroup\$

I want to fix number of digits after '.' in digit represented by string.

Is there better solution than mine?

My code

def convert(s):
    """ Convert 1234.12345678 to 1234.123"""
    vec = s.split('.')
    if len(vec) == 2 and len(vec[1]) > 3:
        return vec[0] + '.' + vec[1][:3]
    return s
\$\endgroup\$
2
  • \$\begingroup\$ def convert(s): return f'{float(s):.3f}' \$\endgroup\$ – Ted Brownlow Jan 10 at 22:43
  • 11
    \$\begingroup\$ @TedBrownlow The answer box is the place for posting answers. Comments are for requesting clarifications. \$\endgroup\$ – AJNeufeld Jan 10 at 23:42
3
\$\begingroup\$

In fact this would be sufficient:

return '{0:1.3f}'.format(s)

(provided that the argument passed is a float). There is no need for a function.

The above will return a string, if you want to return a float then use the float function:

return float('{0:1.3f}'.format(s))

Or even shorter, taking advantage of F-strings. I prefer this option personally:

return f'{s:.3f}'

Again, this returns a string but you can cast it to a float if necessary.

As a reference: Python f-string tutorial

\$\endgroup\$
1
  • \$\begingroup\$ The f-string is impressively faster than the string format method. If you are in 3.6+ I would go for that option. \$\endgroup\$ – Marcel Wilson Jan 12 at 16:15
2
\$\begingroup\$

Let's raise the bar a bit here.

Function Declaration

def convert(s):

This function converts ... something. Inches to feet? Celsius to Fahrenheit? The function name doesn't give us any clue. The function argument s is similarly non-descriptive.

What does the function do? It truncates a string 3 characters after the first period character, if one is present. We could name the function that:

def truncate_3_characters_after_period(s):

A wee bit too verbose, and it doesn't quite match what appears to be the intended usage. From the doc-string, the function seems to be intended to apply to strings representing floating-point quantities, so maybe:

def truncate_to_3_decimal_places(s):

That is better, but no longer implies the function is expecting a string, or returns a string. Type-hints go a long way here.

def truncate_to_3_decimal_places(s: str) -> str:

The type-hints don't actually do anything; they are ignored by the Python interpreter. They are read by programs like mypy, which can use this static type information to spot possible errors in your program. They are also read and used by some documentation generating programs, which is very useful.

Doc-string

""" Convert 1234.12345678 to 1234.123"""

I love doc-strings. But this one is a little lacking in detail. It looks like it says it converts a floating point value to another floating point value with fewer significant digits. But the function doesn't do that at all; the function expects and returns strings.

"""Convert '1234.12345678' to '1234.123'"""

That is slightly clearer. The astute reader will see the single quotation marks, and realize it is not a numerical transformation, but a textual one.

But, many questions are left unanswered. Does '123.4567' get rounded to '123.457', or truncated to '123.456'?

    """
    Truncate the string representation of a float after 3 decimal places.

    >>> convert('1234.12345678')
    '1234.123'

    >>> convert('123.45678')
    '123.456'

    >>> convert('123')
    '123'
    """

That is better doc-string. It describes, in words, what the function is doing, and then goes on to provide three examples, the second showing the truncation, the third showing what happens when the string doesn't have a decimal point.

As a bonus, the format of the examples in the doc-string allow the doc-string to do double duty. You can run doctest.testmod() on the module, and the examples become test-cases, and the output is compared with the documented output, and deviations are flagged.

Exceptional cases

The function does a few bizarre things.

len(vec) == 2

If and only if the string has 2 parts, will the length of the second part be tested.

>>> convert('192.168.0.1')
'192.168.0.1'

Should the function have returned that value? Would 192.168 be a more expected result? Perhaps raising a ValueError would be even better?

Exponential Notation

>>> convert(str(1234567890123456.0))
'1.234'

>>> convert(str(0.000000001234))
'1.234'

Whoa! Those results are unexpected! Clearly, it does not handle the floating point values in exponential representation.

Is this a bug? Or is this a limitation, which should be spelt out in the docstring?

Alternate representation

>>> convert("123.4")
'123.4'

>>> convert("123.400321")
'123.400'

The returned values would both represent the same floating point values, but their representations are different. Should the function add zeros to pad to 3 decimal places? Should this difference be mentioned in the doc-string?

Quality-of-Life

How is this function used? Do you already have the string representation of the floating point value, or are you converting the float-point value into a string for passing to the function?

A function which takes a float, and converts it to a string, truncated to the desired precision might make handling special cases easier.

Internationalization

Some regions do not use the period (.) as the decimal indicator. A comma (,) may be used to separate the integral part from the fractional. For instance, an approximation to pi might appear as 3,14159. Since you've hard-coded your function to split the string at the period character, the application would break in France.

Using built-in formatting would take care of internationalization concerns. For instance, f"{value:.3f}" would convert value to a fixed-point string with 3 digits after the decimal point, even if the decimal point appears as the comma (,) character. However, it would round based on the digit that comes next, instead of truncating the value, which does not appear to match what you've written.

You could explicitly truncate the value to the required resolution

value = math.trunc(value * 1000) / 1000

and then convert the result to a string using f"{value:.3f}".

\$\endgroup\$
0
\$\begingroup\$

You can just use the format() function to get the result.

def convert(s):
    """ Convert 1234.12345678 to 1234.123"""
    s = print("{:.3f}".format(float(s)))
s = "1234.12345678"
convert(s)

Refer to this link to know more about String Formatting.

\$\endgroup\$
1
  • 4
    \$\begingroup\$ Why do you assign the return value of print() to s? And why do you print in convert() at all instead of just returning the formatted str? \$\endgroup\$ – Richard Neumann Jan 11 at 17:41

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.