Allow fractions.Fraction to return 1/6 for "0.17", "0.167", "0.1667", etc

(edited bullet number 4 to refer to the correct steps.)

Lots of questions – thank you – I think these will help our conversation converge.

Looking back on all this, the part of my proposal that is most important to me is that we need a mechanism by which the relevant interval will be inferred from the user-supplied string. That is the essence of the idea that I am trying to convey. There are other aspects that are relevant, but they are not fundamental.

  1. I have brainstormed that this should be done with a flag in the constructor and have even brainstormed possible names for the flag. If anything I have said here implies that I won’t settle for anything different from these brainstormed ideas – I have erred. I defer to others for these details. Absolutely it could instead be an alternative constructor. Although I don’t see how it could be a function that is applied to an already formed Fraction (for example, as is done with limit_denominator), if you do see how and think that that is the right path forward then please speak up.
  2. My creativity extends to applying this to strings. If you have ideas on how to apply this to other types, I would be excited to hear about them.
  3. I have proposed that (1) the interval inference from a string and (2) the selection of the fraction from within that interval both be achievable with a single call / construction. If people see good reasons to separate this into two calls, I would be excited to hear about them. However, if we then decide to dump the first step… that is what I am urging we not do.
  4. Asking the user to specify how many digits that the decimal is rounded to is a possibility. This alternate way of specifying an interval could be a fine way of achieving the first step in the previous bullet. At the risk of taking on too much, I am not prepared to support or oppose that facet at this time. My emphasis is on my proposed way of achieving the first step: guessing the interval from the provided string (so that the interval can then be used in the second step).
  5. Once the interval is determined there is the question of how to pick the best fraction from it. I initially proposed an approach that balances accuracy with the desire for low numerator and denominator. Although the algorithm is well defined, it has proved harder to express in words than an alternative strategy, the one employed by simplefractions that shoots for lowest numerator and denominator subject only to the restriction of being in the interval. I would be happy if either of these two approaches could make it into the implementation.
  6. I am proposing that we do want to rely on trailing zeros being significant. That is, “3.14” could reasonably be rounded from 22/7 but “3.140” could not be. If folks also want to support a mode where this is not the case then that’s worth discussing, but I wouldn’t want to lose the possibility of the "3.14" != "3.140" functionality along the way.

If I missed a question / topic that you consider essential, my apologies, please raise it again.

Nobody else seems to me to have expressed a need such a mechanism, and yet you state this as if it’s a self-evident fact. I don’t doubt that you need such a mechanism, but you can get that by writing your own function. Where’s your evidence that this is a common need?

Your proposed solutions are not the question here, it’s the fundamental matter of whether enough people need this ability at all to justify it being in the standard library[1], that you are failing to answer.


  1. It’s easy to justify it being in a package on PyPI, but the bar for the stdlib is much higher. ↩︎

Yes, this is a question that hits at the heart of the matter! My answer: I have no survey or similar evidence. The best I can do is to appeal to the experience of the posters here. Do you think it would be useful to have available a simple way to convert “0.167”, “0.1667”, and “0.16667” to 1/6 in the fractions library? I am thinking that whether or not we have consensus around this central issue is what we want to establish first. If we achieve a consensus of “yes” then we can worry about alternative constructors, flag names, etc.

I’ve said this before, but to repeat: No, not if that way also converts something like “0.3” to 1/3 rather than to 3/10 (or any of the many other “this doesn’t do what I expect” examples that have been repeatedly raised here).

No, I do not think such behavior is common enough to need in the standard library.

You are quite emphatic about that being a “no” so I ask this question with quite a bit of trepidation. However, I ask it because you didn’t actually answer the question as it pertains to the values of “0.167”, “0.1667”, and “0.16667”. If we could provide that functionality for these values without also doing something bad with “0.3” (and similar) would that still be “no”?

Prove it. Demonstrate your proposed functionality on PyPI. I’m done with this sort of theoretical hairsplitting.

No I don’t think “it would be useful to have available a simple way to convert “0.167”, “0.1667”, and “0.16667” to 1/6 in the fractions library”.

We worked hard at establishing this core question, and I thank you for answering it.

Because of the edit conflict / timing, I just noticed the above. Thank you. That makes (at least) two opposed to the core question. That’s good enough for me. Thank you all for your help in keeping Python slithering!

Lee asked:

“”"

Do you think it would be useful to have available a simple way to convert “0.167”, “0.1667”, and “0.16667” to 1/6 in the fractions library?

“”"

Is that a trick question? We already have such functionality.


from fractions import Fraction

for s in ["0.167", "0.1667", "0.16667"]:

    x = Fraction(s)

    print(x.limit_denominator(10))



prints 1/6 three times.

I think that your argument in this thread has been failing because you assume you already know what the fraction should be.

If you already know that the correct fraction is, let’s say, 9/13, then you should design your api to accept “9/13” as input rather than “0.7” (rounded to 1 decimal place) and then some how infer that of all the possible fractions that round to 0.7, only 9/13 is The One.

Instead, I believe you should take an alternate approach to this.

If we are given some string, like “0.7”, and told that it has been rounded to 1 decimal place, and nothing else, we have no way of knowing what fraction that might have been formed by.

But we can ask what is the best possible fraction that could give that result, with an objective meaning to “best”. (Not just “the fraction I am thinking of”.) And there is a simple algorithm using continued fractions which give that fraction.

The fraction it returns may not be the fraction you were thinking of, but that’s okay because it may be the fraction someone else was thinking of. And it will be the objectively mathematically best fraction that rounds to the string.

My attempt was to introduce a mechanism that does not require that the user guess what argument to limit_denominator will produce the fraction according to …

Yes, that’s exactly what I was attempting to do. If the user supplies “0.167” and indicates (via constructor flag or alternative constructor or whatever approach consensus arrives at) that it has been rounded to exactly as many digits as have been supplied then the algorithm comes up with the best fraction according to a well-defined criterion. (My preference is for a the well-defined approach that I supplied in the code snippet, but another good way is the approach of simplefractions. Both are based upon continued fractions. I’d be happy with either.)

However, unless you can persuade those who think that this functionality is not useful enough to come around to our side, the issue appears moot.