9

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
4
  • 3
    Multiply by 100, call math.floor(), then divide by 100. Commented Jun 17, 2020 at 16:02
  • @Barmar Perhaps trunc instead of floor to make it also work for negative numbers? (Not relevant, if the input is known to be close to 1 and thus positive) Commented Jun 17, 2020 at 16:19
  • 2
    With numbers "very very close-to-one", the multiplication by 100 incurs a rounding that may form a product == 100.0. Instead, set rounding mode to toward 0 and then print to 17+ decimal places and textually lop off all but 2 digits after the .. Commented Jun 17, 2020 at 17:45
  • 1
    just thinking out loud... if f'...:.2f' is supposed to format text output, then it shouldn't round anything, or at least offer that option. Commented Jun 28, 2022 at 2:18

4 Answers 4

2

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
Sign up to request clarification or add additional context in comments.

11 Comments

This fails for 0.99999999999999988897769753748434595763683319091796875. Then str(a) produces “1.0”.
@EricPostpischil - No, it doesn't. Just tried it now.
It may depend on the Python version and/or implementation.
@EricPostpischil Don't think so; a string is a string and the character in the [4] index position of that string is just that. No rounding function is performed.
0.9899999999999999911182158029987476766109466552734375 is an example that fails in common current Python implementations. This value is representable in IEEE-754 binary64, which is commonly used, but Python’s 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.)
|
1

You could also try this approach by simply cutting of the decimals by cast the number to an integer and back to a float again:

num_decimals = 2
a = 0.99999999999
a = int(a * 10 ** num_decimals)/10 ** num_decimals

print(a) # => prints 0.99

Comments

0

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.

6 Comments

Re “I suspect python can do this - I am not aware.”: Python allows requesting formatting with lots of precision (as with %.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.
Basically, if you want reliable floating-point arithmetic, Python is not a language to use.
@EricPostpischil Thanks for the python input. Without provisions for controlling rounding mode, this task is difficult to well handle all cases. Re: "Whether conversion to string for many digits is implemented correctly", is also a coding hole. I would hope python would move toward correct float to string with IEEE 754 spec of at least 20 digits.
@EricPostpischil that's a pretty strong statement. For CPython at least, all operations on 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.
@MarkRansom: CPython is not Python. My statement is about Python, not CPython, and it remains true that Python does not have a proper specification, let alone a standard, and what documentation it has is lax about floating-point.
|
0

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.