General way to print floats without the .0 part

I’m building SVG code using data interpolation (f-strings and .format), and I have elements (the size of the graph for one) that are internally floats but which are usually integers.
But when printing floats, the .0 part is always included.
Is there a standard str-interpolation idiom that turns 24.125 into “24.125” but 25.0 into “25” ?

I don’t think there’s a str format code for this (I’m sure someone will correct me if I’m wrong) but you can do this:

f"{float_value:.5f}".rstrip(".0")

The only gotcha here being that 0.0 turns into "" (everything is removed), so you’d need to special-case it somehow. One option would be to always keep the first character. At that point I’d define a function:

def fmt(v, d=5):
    s = f"{v:.{d}f}"
    return f"{s[0]}{s[1:].rstrip('.0')}"

You can use the .removesuffix method (Python 3.9+):

>>> str(24.125).removesuffix(".0")
'24.125'
>>> str(25.0).removesuffix(".0")
'25'
3 Likes

Totally forgot (or never really knew) that method existed. The only issue with this is annoying floating-point representations like '0.30000000000000004'

Don’t have time to look up the specification now but I think the ‘g’ format specifier either with or without the alternate mode flag ‘#’ or zero flag ‘0’ can do this…

1 Like

It does, but can also truncate digits depending on the value, e.g. f"{12325.04:g}" becomes '12325'.

Well, you could do what you suggested, but in 2 steps:

f"{float_value:.5f}".rstrip("0").rstrip(".")

3 Likes

I think this should do it. This will convert both 3.0 and 3. to 3, but will not change 3.1 which is probably what is desired.

@Gouvernathor I work on a package that provides more control over number → string formatting than the built in format-specification-mini-language does. For those purposes I’m interested: Why exactly do you want to format strings in this way?

You say the elements are internally floats but usually integers. Are the data float-type or integer-type? Or is it a mix of what should be thought of as floats and what should be thought of as integers? If the number is not an integer how are you controlling its display? Are you interested to show a specified number of digits past the decimal point? Do you want to show a specific number of significant figures (bearing in mind # of digits past the decimal point != # of sig figs)?

This is the right way.

It’s just a matter of specifying enough precision:

>>> f'{12345.6789:.12g}'
'12345.6789'
>>> f'{12345.678:.12g}'
'12345.678'
>>> f'{12345.67:.12g}'
'12345.67'
>>> f'{12345.6:.12g}'
'12345.6'
>>> f'{12345.0:.12g}'
'12345'
1 Like

Er, yes of course, but if you’re looking for a catch-all way to format values in this way, then you don’t want to have to specify the precision every time.

I suppose you’re saying “specify much higher precision than you need”?

The g modifier with no precision passed turns the number to scientific notation above a certain value, so that won’t work for me.

>>> f"{2500000000000000000}"
'2500000000000000000'
>>> f"{2500000000000000000:g}"
'2.5e+18'

Adding a precision value doesn’t sound like it would stop that.

Because I want to avoid putting floats anywhere without a good reason. Depending on the parameters passed to the function, the values may end up being non-integers but in the canonical case they are integers. Anything getting the data downhill should not get an excuse to upen its calculations to floating point errors.
That should take care of your other questions. I am not interested in showing a certain number of decimal points, I trust python to know what that number is, and I want to display the number as precisely as needed - but not more, hence why .0 is not necessary - so no fixed set precision is good in my case.

I don’t think there’s a str format code for this (I’m sure someone will
correct me if I’m wrong) but you can do this:

f"{float_value:.5f}".rstrip(".0")

rstrip is not removesuffix:

 >>> float_value=100.0
 >>> f"{float_value:.5f}".rstrip(".0")
 '1'
1 Like

I have a function intif(f) which returns a int if the int round
trips with the float. So:

 f'{intif(float_value)}`

is what I would do. It’s in my cs.numeric module on PyPI (with its
vast suite of … 3 functions, of which I basicly only use intif()).

Or you could just have the code:

 def intif(f: float) -> Union[int, float]:
     ''' Return `int(f)` if that equals `f`, otherwise `f`.
     '''
     i = int(f)
     return i if i == f else f

Got a lot of numbers?

 list(map(intif, your_numbers_here))

:man_facepalming: oh yeah, integers can end in zero

I see so the canonical type is integer, but there’s some “special” cases that produce floats.

This makes me worried.
Consider

x = 6.0000000001

print(x)
# 6.0000000001

print(str(x))
# 6.0000000001

print(repr(x))
# 6.0000000001

print(f'{x:f}')
# 6.000000

print(f'{x:g}')
# 6

What is the correct behavior for this input? Python makes somewhat arbitrary choices about how it round/displays floats when the user doesn’t pass in precision. This is one of the reasons I branched out and made my own more simply specified formatting package. It’s a little tricky to fulfil your requirements as is because I’d say they’re technically unspecified. I assume all of the inputs to your formatting function are floats but you’re wanting some means to detect if they should actually be ints. If you specified that then it would be straightforward to give a function that does what you want.

But anyways, it sounds like you already have some code that converts floats to strings. If you’re happy with that code but just want to strip trailing zeros and decimal points then yes, you can just do

a_float = ...
a_string = a_formatter(a_float)
a_stripped_string = a_string.rstrip("0").rstrip(".")

as @MRAB already suggested.

It does in my testing, though. On the other hand, large integers do eventually suffer floating-point imprecision.

So, you will prefer

>>> int(1.112e300)
1112000000000000040541090502053918562982619084393466258691307356536390824614403601923157083164605542274579567588290665455175400355405122194661985962943949671437439594201616784360817134610285868833781357985714687096059854983114770185830029367436957396086948488984006213091443359003355428309206667624448

to

>>> 1.112e300
1.112e+300

?
(Both representations are exact.)

Then, I guess, repr() is something you are looking for. (BTW float.hex() also prints precise float value.) Existing format() types use a precision of 6 digits per default. For IEEE floats you could pass precision=17 for “g” format to have enough precision to read back same float value. But that’s exactly what repr() is doing.

int/float’s have is_integer() method, so you can print integer-valued float’s separately:

repr(int(x)) if x.is_integer() else repr(x)

You passed an integer, if you use a float this also happens:

>>> print(f"{2500000000000000000.0}")
2.5e+18

You can use this for example:

def float2str(value):
    return str(value) if isinstance(value, int) else f'{value:.16g}'

The precision matches that of normal floats.

I recently wrote a function to support huge integers for the g format specifier:

expand
def format_real(
    real: float,
    *,
    decimal_point: str = ".",
    thousands_sep: str = "",
) -> str:
    """Format real using specified format."""
    string: str
    if isinstance(real, float) or -float_info.max <= real <= float_info.max:
        string = f"{real:,g}"
    else:
        # Calculate manually, int too large
        exponent: int = int(log10(abs(real)))
        mantissa: float = round(real / 10 ** exponent, 5)
        if abs(mantissa) == 10:
            exponent, mantissa = exponent + 1, mantissa / 10

        string = f"{mantissa:,g}e{exponent:+}"

    return string.translate({
        ord("."): decimal_point,
        ord(","): thousands_sep,
    })

I’m not the one making the choice, and it’s not a matter of preference : I’m writing values to a SVG, and there’s only specific formats that are recognized by that format, and scientific notation is not recognized by browsers.