7

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
Akshat Zala
  • 710
  • 1
  • 8
  • 23
Michael Bianconi
  • 5,072
  • 1
  • 10
  • 25
  • 3
    Multiply by 100, call `math.floor()`, then divide by 100. – Barmar Jun 17 '20 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) – chtz Jun 17 '20 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 `.`. – chux - Reinstate Monica Jun 17 '20 at 17:45
  • 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. – 101is5 Jun 28 '22 at 02:18

3 Answers3

1

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
Jack Fleeting
  • 24,385
  • 6
  • 23
  • 45
  • 2
    This fails for 0.99999999999999988897769753748434595763683319091796875. Then `str(a)` produces “1.0”. – Eric Postpischil Jun 17 '20 at 18:39
  • @EricPostpischil - No, it doesn't. Just tried it now. – Jack Fleeting Jun 17 '20 at 18:55
  • It may depend on the Python version and/or implementation. – Eric Postpischil Jun 17 '20 at 18:56
  • @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. – Jack Fleeting Jun 17 '20 at 19:00
  • 3
    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.) – Eric Postpischil Jun 17 '20 at 19:37
  • @JackFleeting Curious, what does `a = 0.99999999999999988897769753748434595763683319091796875 print(str(a))` print for you? Was it the rounded value of `0.99999999999999989`? – chux - Reinstate Monica Jun 18 '20 at 02:36
  • I think I have a case that fails this approach: `a = 0.87`. Since it cannot be saved as a `float` exactly, the nearest value is used: `0.8699999999999999955591...`. I suspect your `str(a)` is printing that to a _rounded_ 17 (or less) significant places or `0.87000000000000000`. Chopping that with `[:4]` leads to `"0.87"`, when, per OP's "truncating it to 2 decimal places without rounding", it should be `"0.86"`. – chux - Reinstate Monica Jun 18 '20 at 02:53
  • @chux-ReinstateMonica - In my case I got `0.9999999999999999`; so the tail end was rounded up, but one decimal point earlier! – Jack Fleeting Jun 18 '20 at 02:53
  • @chux-ReinstateMonica Indeed; `a = 0.8699999999999999` is the longest float that still truncates to `0.86`; add one more `9` (or anything over `4`) and it truncates to `0.87`. – Jack Fleeting Jun 18 '20 at 02:58
  • Jack, Thanks, I think I see the scope of this approach and its rare counter corner cases. – chux - Reinstate Monica Jun 18 '20 at 03:15
  • @chux-ReinstateMonica Interestingly, too, using `a = """0.86999999999999999995""" print(a[:4])` also truncates to `0.86` regardless how many `9`s are involved... So I guess it's a better approach than `str(a)`. – Jack Fleeting Jun 18 '20 at 03:25
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
anka
  • 3,817
  • 1
  • 30
  • 36
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.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 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](https://docs.python.org/3/library/stdtypes.html#typesnumeric) 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. – Eric Postpischil Jun 18 '20 at 11:43
  • Basically, if you want reliable floating-point arithmetic, Python is not a language to use. – Eric Postpischil Jun 18 '20 at 11:44
  • 1
    @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. – chux - Reinstate Monica Jun 18 '20 at 12:30