I want to print a very very close-to-one float, truncating it to 2 decimal places without rounding, preferably with the least amount of code possible.
a = 0.99999999999
print(f'{a:0.2f}')
Expected: 0.99
Actual: 1.00
I don't think you need f-strings or math functions, if I understand you correctly. Plain old string manipulation should get you there:
a = 0.987654321
print(str(a)[:4])
output:
0.98
str(a) produces “1.0”.[4] index position of that string is just that. No rounding function is performed.str may produce “0.99” for it, and then the code in the question incorrectly shows “0.99” instead of “0.98”. (Note, contrary to your assertion that no rounding is performed, it does in fact round, since the actual floating-point value passed to it in your example is not 0.987654321 but 0.98765432099999994619565768516622483730316162109375.)Not skilled enough in python yet certainly the floating point math is close enough to C here - which I am versed in.
Python's float, like C's double is base 2, so values like 0.xx, aside from 0.00, 0.25, 0.50, 0.75 cannot be exactly encoded.
Let us try a=0.87. The closest representable python float value is:
# 1234567890123456789012
0.8699999999999999955591...
so the true value is less than 0.87.
Printing that with less than 18 digits (which str() apparently does) results in a rounded up output.
# 12345678901234567
0.87000000000000000
And textually truncating that leads to "0.87", and does not meet "truncating it to 2 decimal places without rounding" as the original value was 0.8699999999999999955591...
To solve, code needs to either change the rounding mode of float math before calling str() to round toward 0 rather than round to nearest or convert to a string with extra precision. I suspect python can do this - I am not aware.
Even with additional precision (still using round to nearest), in the general case, there still may exist cases where the true value is xxx.xx(many 9's)... that round up and thwart our textual truncation.
%.99g % x`), and some implementations may do that correctly. But Python is lax about floating-point; the documentation does not contain strong statements about its characteristics or behavior. Whether conversion to string for many digits is implemented correctly is not specified, so this cannot be relied upon. And I do not think Python has provisions for controlling rounding mode.float to string with IEEE 754 spec of at least 20 digits.float are implemented with the underlying C double type which is pretty reliable. And I think they went to great pains to tune their str conversion so that float(str(v))==v in all cases, while delivering the minimum number of digits to ensure that outcome.The other solutions were always getting me the unwanted rounding, so I put together this helper that works through multiplying the floored decimal number by a coefficient and then dividing by the same coefficient. The coefficient itself is constructed via string interpolation of the "1" digit with as many zeroes as the number of positions you want the number to have.
def trunc_decimal(number: Decimal | float, position: int):
"""
Truncate decimal to position with none of the unwanted rounding.
:param position: decimal position to truncate the decimal to
:return: decimal truncated to position
"""
coefficient = "1" + "0" * position
return floor(number * int(coefficient)) / int(coefficient)
math.floor(), then divide by 100.truncinstead offloorto make it also work for negative numbers? (Not relevant, if the input is known to be close to 1 and thus positive)..