Re: [Python-Dev] Mixing float and Decimal -- thread reboot
Raymond Hettinger wrote:
Since decimal also allows arbitrary sizes, all long ints can be exactly represented (this was even one of the design goals for the decimal module).
There may be something we need to clarify here. I've been imagining that the implicit conversions to Decimal that we're talking about would be done to whatever precision is set in the context. Am I wrong about that? Is the intention to always use enough digits to get an exact representation? -- Greg
On Mar 21, 2010, at 3:50 PM, Greg Ewing wrote:
Raymond Hettinger wrote:
Since decimal also allows arbitrary sizes, all long ints can be exactly represented (this was even one of the design goals for the decimal module).
There may be something we need to clarify here. I've been imagining that the implicit conversions to Decimal that we're talking about would be done to whatever precision is set in the context. Am I wrong about that?
Yes. The conversion to decimal is independent of the current context. The background docs for the decimal module makes its design intention clear. All numbers are exact. Rounding and context adjustments only apply to the *results (of operations. The module is designed that way. Its background documents confirm that viewpoint. And its operations are designed to support that world view (i.e. unary plus is an operation that can change a value). Also, we confirmed that point-of-view with the person who wrote the spec.
Is the intention to always use enough digits to get an exact representation?
Yes. That is in-fact what Decimal.fromfloat() does. That agrees with the decimal constructor itself which is not context sensitive: >>> decimal.getcontext().prec = 5 >>> decimal.Decimal('3.1415926535') # Notice this value doesn't get rounded Decimal('3.1415926535') >>> decimal.Decimal('3.1415926535') + 0 # This result does get rounded. Decimal('3.1416') >>> # Also, rounding does not get applied during an equality check >>> decimal.getcontext().prec = 5 >>> decimal.Decimal('3.1415926535') == decimal.Decimal('3.1416') False Raymond P.S. Thanks for asking the question.
On Sun, Mar 21, 2010 at 10:50 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Raymond Hettinger wrote:
Since decimal also allows arbitrary sizes, all long ints can be exactly represented (this was even one of the design goals for the decimal module).
There may be something we need to clarify here. I've been imagining that the implicit conversions to Decimal that we're talking about would be done to whatever precision is set in the context. Am I wrong about that? Is the intention to always use enough digits to get an exact representation?
I've been thinking about this, too. Currently, Decimal + integer -> Decimal converts the integer losslessly, with no reference to the Decimal context. But the Fraction type is going to mess this up: for Decimal + Fraction -> Decimal, I don't see any other sensible option than to convert the Fraction using the current context, since lossless conversion isn't generally possible. So with the above two conventions, we'd potentially end up with Decimal('1.23') + 314159 giving a different result from Decimal('1.23') + Fraction(314159, 1) (depending on the context). It may be that we should reconsider Decimal + int interactions, making the implicit int->Decimal conversion lossy. Decimal would then match the way that float behaves: float + int and float + Fraction both do a lossy conversion of the non-Fraction argument to Fraction. But then we're changing established behaviour of the Decimal module, which could cause problems for existing users. Note that comparisons are a separate issue: those always need to be done exactly (at least for equality, and once you're doing it for equality it makes sense to make the other comaprisons exact as well), else the rule that x == y implies hash(x) == hash(y) would become untenable. Again, this is the pattern that already exists for int<->float and Fraction<->float interactions: comparisons are exact, but arithmetic operations involve a lossy conversion. Mark
Mark Dickinson wrote:
But the Fraction type is going to mess this up: for Decimal + Fraction -> Decimal, I don't see any other sensible option than to convert the Fraction using the current context, since lossless conversion isn't generally possible.
Be able to duck this question was precisely why I put Decimal to the left of Fraction in my pragmatic tower, btw. There are all sorts of good reasons why that order was mathematically wrong (Real vs Rational, representability of NaN/Inf, etc), but from the point of view of providing clearly defined behaviour for an atypical operation, it should be a lot easier. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia ---------------------------------------------------------------
On Mar 22, 2010, at 2:23 AM, Mark Dickinson wrote:
Note that comparisons are a separate issue: those always need to be done exactly (at least for equality, and once you're doing it for equality it makes sense to make the other comaprisons exact as well), else the rule that x == y implies hash(x) == hash(y) would become untenable. Again, this is the pattern that already exists for int<->float and Fraction<->float interactions: comparisons are exact, but arithmetic operations involve a lossy conversion.
My instinct says that we're asking for trouble if comparisons have different coercion rules than arithmetic operations. That would suggest that we follow the chain of lossless conversions: Fraction + float --> Fraction Fraction + decimal --> Fraction Decimal + float --> Decimal That way, arithmetic coercions match comparison coercions. We preserve get Mark's fast, clean new universal hash function. The equality/hash homomorphism is preserved. And a decimal context is not needed for any of the coercions (it is needed for the addition between two decimals once coercion has happened, but that is completely normal for Decimal so there are no surprises). Raymond
On Mon, Mar 22, 2010 at 8:39 AM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
My instinct says that we're asking for trouble if comparisons have different coercion rules than arithmetic operations.
Sorry, no, this is a station we passed long ago when float-long comparison was fixed to do the comparison exactly (but other mixed ops continue to convert the long to a float). Otherwise you'll get a violation of "if x < y and y < z then it follows that x < z". (FWIW, my instinct used to be the same as yours, but I got better. :-)
That would suggest that we follow the chain of lossless conversions:
Fraction + float --> Fraction
Besides the obvious backwards compatibility problem, I also just really think that this would be a mistake. Fraction corresponds to Rational in PEP 3141's numeric tower and float corresponds to Real (as does Decimal). The convention is to only move to the more general type in the numeric tower. Now we all know that floats (being a fixed number of bits) are actually representable as rationals as well, but *that's not how we think about them!*
Fraction + decimal --> Fraction
This also violates the natural direction in the numeric tower.
Decimal + float --> Decimal
If everybody associated with the Decimal implementation wants this I won't stop you; as I repeatedly said my intuition about this one (as opposed to the other two above) is very weak.
That way, arithmetic coercions match comparison coercions.
Which is not a requirement.
We preserve get Mark's fast, clean new universal hash function.
That hash function (or another that matches the requirements) is a given.
The equality/hash homomorphism is preserved.
That is also a given (except I don't know if homomorphism is the right word to use).
And a decimal context is not needed for any of the coercions (it is needed for the addition between two decimals once coercion has happened, but that is completely normal for Decimal so there are no surprises).
Doesn't strike me as a deciding argument. -- --Guido van Rossum (python.org/~guido)
On Mar 22, 2010, at 10:00 AM, Guido van Rossum wrote:
Decimal + float --> Decimal
If everybody associated with the Decimal implementation wants this I won't stop you; as I repeatedly said my intuition about this one (as opposed to the other two above) is very weak.
That's my vote. I believe Nick chimed-in in agreement. Mark, do you concur? Raymond
On Mon, Mar 22, 2010 at 1:56 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On Mar 22, 2010, at 10:00 AM, Guido van Rossum wrote:
Decimal + float --> Decimal
If everybody associated with the Decimal implementation wants this I won't stop you; as I repeatedly said my intuition about this one (as opposed to the other two above) is very weak.
That's my vote.
I've been lurking on this thread so far, but let me add my +1 to this option. My reasoning is that Decimal is a "better" model of Real than float and mixed operations should not degrade the result. "Better" can mean different things to different people, but to me the tie breaker is the support for contexts. I would not want precision to suddenly change in the middle of calculation I add 1.0 instead of 1. This behavior will also be familiar to users of other "enhanced" numeric types such as NumPy scalars. Note that in the older Numeric, it was the other way around, but after considerable discussion, the behavior was changed.
On Mon, Mar 22, 2010 at 10:22 AM, Alexander Belopolsky <alexander.belopolsky@gmail.com> wrote:
On Mon, Mar 22, 2010 at 1:56 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On Mar 22, 2010, at 10:00 AM, Guido van Rossum wrote:
Decimal + float --> Decimal
If everybody associated with the Decimal implementation wants this I won't stop you; as I repeatedly said my intuition about this one (as opposed to the other two above) is very weak.
That's my vote.
I've been lurking on this thread so far, but let me add my +1 to this option. My reasoning is that Decimal is a "better" model of Real than float and mixed operations should not degrade the result. "Better" can mean different things to different people, but to me the tie breaker is the support for contexts. I would not want precision to suddenly change in the middle of calculation I add 1.0 instead of 1.
This behavior will also be familiar to users of other "enhanced" numeric types such as NumPy scalars. Note that in the older Numeric, it was the other way around, but after considerable discussion, the behavior was changed.
Thanks, "better" is a great way to express this. -- --Guido van Rossum (python.org/~guido)
On Mon, Mar 22, 2010 at 5:56 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On Mar 22, 2010, at 10:00 AM, Guido van Rossum wrote:
Decimal + float --> Decimal
If everybody associated with the Decimal implementation wants this I won't stop you; as I repeatedly said my intuition about this one (as opposed to the other two above) is very weak.
That's my vote. I believe Nick chimed-in in agreement. Mark, do you concur?
Yes; Decimal + float -> Decimal works for me; the greater flexibility of the Decimal type is the main plus for me. I don't think my own intuition is particularly strong here, either. It would be interesting to hear from major users of the decimal module (I have to confess to not actually using it myself very much). I argued earlier for Decimal + float -> Decimal on the basis that the float->Decimal conversion can be done exactly; but having thought about Decimal + Fraction it's no longer clear to me that this makes sense. Having Decimal + float -> Decimal round the float using the current Decimal context still seems like a reasonable option. Just for the record, I'd also prefer Decimal + Fraction -> Decimal. I don't want to let the abstractions of the numeric tower get in the way of the practicalities: we should modify the abstractions if necessary! In particular, it's not clear to me that all numeric types have to be comparable with each other. It might make sense for Decimal + complex mixed-type operations to be disallowed, for example. Mark
On Mar 22, 2010, at 11:26 AM, Mark Dickinson wrote:
On Mon, Mar 22, 2010 at 5:56 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On Mar 22, 2010, at 10:00 AM, Guido van Rossum wrote:
Decimal + float --> Decimal
If everybody associated with the Decimal implementation wants this I won't stop you; as I repeatedly said my intuition about this one (as opposed to the other two above) is very weak.
That's my vote. I believe Nick chimed-in in agreement. Mark, do you concur?
Yes; Decimal + float -> Decimal works for me; the greater flexibility of the Decimal type is the main plus for me.
On Mar 22, 2010, at 11:22 AM, Alexander Belopolsky wrote:
I've been lurking on this thread so far, but let me add my +1 to this option.
On Mar 22, 2010, at 11:24 AM, Guido van Rossum wrote:
Thanks, "better" is a great way to express this.
OMG ;-) It looks like there is a consensus on: Decimal + float --> Decimal We have progress. Raymond
On Mon, Mar 22, 2010 at 12:26, Mark Dickinson <dickinsm@gmail.com> wrote:
I don't want to let the abstractions of the numeric tower get in the way of the practicalities: we should modify the abstractions if necessary! In particular, it's not clear to me that all numeric types have to be comparable with each other. It might make sense for Decimal + complex mixed-type operations to be disallowed, for example.
Only until a needy user breaks out the duck tape and builds a ComplexDecimal type. ;) The nature of the beast is more will be added on later. So long as Decimal == complex works right I don't see a problem with Decimal + complex raising an exception. -- Adam Olsen, aka Rhamphoryncus
On Mon, Mar 22, 2010 at 10:26 AM, Mark Dickinson <dickinsm@gmail.com> wrote:
On Mon, Mar 22, 2010 at 5:56 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On Mar 22, 2010, at 10:00 AM, Guido van Rossum wrote:
Decimal + float --> Decimal
If everybody associated with the Decimal implementation wants this I won't stop you; as I repeatedly said my intuition about this one (as opposed to the other two above) is very weak.
That's my vote. I believe Nick chimed-in in agreement. Mark, do you concur?
Yes; Decimal + float -> Decimal works for me; the greater flexibility of the Decimal type is the main plus for me. I don't think my own intuition is particularly strong here, either. It would be interesting to hear from major users of the decimal module (I have to confess to not actually using it myself very much).
You're unlikely to hear from them here... :-( Though maybe Raymond qualifies.
I argued earlier for Decimal + float -> Decimal on the basis that the float->Decimal conversion can be done exactly; but having thought about Decimal + Fraction it's no longer clear to me that this makes sense. Having Decimal + float -> Decimal round the float using the current Decimal context still seems like a reasonable option.
So now we have a second-order decision to make -- whether Decimal+float should convert the float to Decimal using the current context's precision, or do it exactly. I think we should just follow Decimal.from_float() here, which AFAIK does the conversion exactly -- the operation will already round properly if needed, and I don't think it is a good idea to round twice in the same operation. (Even though this is what long+float does -- but the situation is different there since float has no variable precision.)
Just for the record, I'd also prefer Decimal + Fraction -> Decimal.
I think that is pretty much uncontested. Though there are second-order details to decide here too: Fraction + float converts the Fraction to a float first and then just adds the floats; but but Fraction / Decimal cannot always be computed exactly as a Decimal so here I'd suggest rounding first because we have to in some cases (it's better to be consistent, except for comparisons, which should always be done exactly if at all possible).
I don't want to let the abstractions of the numeric tower get in the way of the practicalities: we should modify the abstractions if necessary! In particular, it's not clear to me that all numeric types have to be comparable with each other. It might make sense for Decimal + complex mixed-type operations to be disallowed, for example.
One of the reasons for having a numeric tower is actually that unless you do at least comparisons right, you get into inconsistencies like a == b, b ==c, a != c (and similar for orderings). Of course complex doesn't play along with the orderings, but it does play along with ==. Real and Complex are distinct stations in the numeric tower, and the tower defines mixed Real-Complex operations as returning Complex results, but it allows raising exceptions as well. Since Decimal(-1) ** Decimal('0.5') already raises an exception instead of returning a complex number, I'm fine with following that example -- complex numbers are definitely a specialty area. PS. PEP 3141 defines some miscellaneous operations that any number in the numeric tower ought to implement, e.g. x.imag, x.real and x.conjugate(). -- --Guido van Rossum (python.org/~guido)
On Mar 22, 2010, at 11:54 AM, Guido van Rossum wrote:
So now we have a second-order decision to make -- whether Decimal+float should convert the float to Decimal using the current context's precision, or do it exactly. I think we should just follow Decimal.from_float() here, which AFAIK does the conversion exactly -- the operation will already round properly if needed, and I don't think it is a good idea to round twice in the same operation. (Even though this is what long+float does -- but the situation is different there since float has no variable precision.)
I concur. That is consistent with the basic design of the decimal module which treats inputs as exact and only applies rounding to the results of operations. FWIW, Mark and I have both been bitten severely by doubling rounding in the world of binary floats. Avoiding double rounding is a darned good idea. Raymond
On Mar 22, 2010, at 11:26 AM, Mark Dickinson wrote:
Just for the record, I'd also prefer Decimal + Fraction -> Decimal.
Guido was persuasive on why float + Fraction --> float, so this makes sense for the same reasons. For the implementation, is there a way to avoid the double rounding in myfloat + myfrac.numerator / myfrac.denominator? Perhaps translate it to: f = Fractions.from_decimal(myfloat) + myfract # Lossless, exact addition return f.numerator / f.denominator # Only one decimal context rounding applied. Elsewhere in the decimal module, there is a fundamental notion that numbers are exact and only the results of operations are rounded. For example, it is possible in decimal to add together two high precision numbers but do so in a low precision context. Raymond
On Mon, Mar 22, 2010 at 11:00 AM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On Mar 22, 2010, at 11:26 AM, Mark Dickinson wrote:
Just for the record, I'd also prefer Decimal + Fraction -> Decimal.
Guido was persuasive on why float + Fraction --> float, so this makes sense for the same reasons.
For the implementation, is there a way to avoid the double rounding in myfloat + myfrac.numerator / myfrac.denominator?
Perhaps translate it to:
f = Fractions.from_decimal(myfloat) + myfract # Lossless, exact addition return f.numerator / f.denominator # Only one decimal context rounding applied.
Elsewhere in the decimal module, there is a fundamental notion that numbers are exact and only the results of operations are rounded. For example, it is possible in decimal to add together two high precision numbers but do so in a low precision context.
Works for me. -- --Guido van Rossum (python.org/~guido)
One other thought. The Decimal constructor should now accept floats as a possible input type. Formerly, we separated that out to Decimal.from_float() because decimals weren't interoperable with floats. This will put decimal and float back on equal footing so that we have both: float(some_decimal) # coerce to binary float and Decimal(some_float) # coerce to decimal float That will also save us from odd idioms like: d = some_float + Decimal(0) # coerce to decimal then apply context rounding This also matches the behavior of other constructors: int(some_float) or int(some_decimal) or str(some_float) or str(some_decimal) Raymond
On Mon, Mar 22, 2010 at 11:36 AM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
One other thought.
The Decimal constructor should now accept floats as a possible input type. Formerly, we separated that out to Decimal.from_float() because decimals weren't interoperable with floats.
Not sure this follows; Fraction(1.1) raises an exception, you have to use Fraction.from_float().
This will put decimal and float back on equal footing so that we have both: float(some_decimal) # coerce to binary float and Decimal(some_float) # coerce to decimal float
If you really want this I won't stop you.
That will also save us from odd idioms like:
d = some_float + Decimal(0) # coerce to decimal then apply context rounding
Although this isn't too bad: d = Decimal.from_float(some_float)
This also matches the behavior of other constructors: int(some_float) or int(some_decimal) or str(some_float) or str(some_decimal)
True, I don't care strongly. -- --Guido van Rossum (python.org/~guido)
While we're on the topic, I think you should consider allowing the Fraction() constructor to accept a decimal input. This corresponds to common schoolbook problems and simple client requests: "Express 3.5 as a fraction". >>> Fraction(Decimal('3.5')) Fraction(7, 2) Unlike typical binary floats which use full precision, it is not uncommon to have decimal floats with only a few digits of precision where the expression as a fraction is both useful and unsurprising. Raymond
On Mon, Mar 22, 2010 at 12:33 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
While we're on the topic, I think you should consider allowing the Fraction() constructor to accept a decimal input.
This corresponds to common schoolbook problems and simple client requests: "Express 3.5 as a fraction".
>>> Fraction(Decimal('3.5')) Fraction(7, 2)
Unlike typical binary floats which use full precision, it is not uncommon to have decimal floats with only a few digits of precision where the expression as a fraction is both useful and unsurprising.
There is already a Fraction.from_decimal() class method. I understand the reasons for not allowing floats in the unadorned Fraction constructor don't quite hold for Decimal, but at the same time I'm not sure how compelling the use case is for such downcasts given that it can be done easily with an explicit call. (Same actually for requiring Decimal.from_float().) -- --Guido van Rossum (python.org/~guido)
On Mon, Mar 22, 2010 at 8:44 PM, Guido van Rossum <guido@python.org> wrote:
On Mon, Mar 22, 2010 at 12:33 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
While we're on the topic, I think you should consider allowing the Fraction() constructor to accept a decimal input.
This corresponds to common schoolbook problems and simple client requests: "Express 3.5 as a fraction".
>>> Fraction(Decimal('3.5')) Fraction(7, 2)
There is already a Fraction.from_decimal() class method.
So there is; I'd failed to notice that! So decimal -> fraction conversion is implemented twice in the Fractions module---once in __new__ and once in from_decimal. Hmm. Mark
On Mon, Mar 22, 2010 at 8:33 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
While we're on the topic, I think you should consider allowing the Fraction() constructor to accept a decimal input.
This corresponds to common schoolbook problems and simple client requests: "Express 3.5 as a fraction".
>>> Fraction(Decimal('3.5')) Fraction(7, 2)
Unlike typical binary floats which use full precision, it is not uncommon to have decimal floats with only a few digits of precision where the expression as a fraction is both useful and unsurprising.
Sounds fine to me. Fraction already accepts decimal floating-point strings, so the implementation of this would be trivial (convert to string, then call the Fraction constructor). Mark
On Mon, Mar 22, 2010 at 12:45 PM, Mark Dickinson <dickinsm@gmail.com> wrote:
On Mon, Mar 22, 2010 at 8:33 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
While we're on the topic, I think you should consider allowing the Fraction() constructor to accept a decimal input.
This corresponds to common schoolbook problems and simple client requests: "Express 3.5 as a fraction".
>>> Fraction(Decimal('3.5')) Fraction(7, 2)
Unlike typical binary floats which use full precision, it is not uncommon to have decimal floats with only a few digits of precision where the expression as a fraction is both useful and unsurprising.
Sounds fine to me. Fraction already accepts decimal floating-point strings, so the implementation of this would be trivial (convert to string, then call the Fraction constructor).
Better, Fraction.from_decimal() already exists. ;-) -- --Guido van Rossum (python.org/~guido)
On Mon, Mar 22, 2010 at 7:52 PM, Guido van Rossum <guido@python.org> wrote:
On Mon, Mar 22, 2010 at 11:36 AM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
One other thought.
The Decimal constructor should now accept floats as a possible input type. Formerly, we separated that out to Decimal.from_float() because decimals weren't interoperable with floats.
Not sure this follows; Fraction(1.1) raises an exception, you have to use Fraction.from_float().
Is there any good reason for this, other than a parallel with Decimal? It seems to me that Raymond's arguments for allowing direct construction of a Decimal from a float apply equally well to the Fraction type. If we're going to allow Decimal(1.1), I'd like to allow Fraction(1.1) to succeed as well (giving the equivalent of Fraction.from_float(1.1)). The main argument against allowing this (for both Fraction and Decimal) seems to be that the result of Decimal(1.1) or Fraction(1.1) could be confusing. But it's an immediate, explicit confusion, which can be quickly resolved by pointing the confusee to the section on floating-point in the appendix, so I don't find this objection particularly compelling. Mark
On Fri, Apr 2, 2010 at 2:38 AM, Mark Dickinson <dickinsm@gmail.com> wrote:
On Mon, Mar 22, 2010 at 7:52 PM, Guido van Rossum <guido@python.org> wrote:
On Mon, Mar 22, 2010 at 11:36 AM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
One other thought.
The Decimal constructor should now accept floats as a possible input type. Formerly, we separated that out to Decimal.from_float() because decimals weren't interoperable with floats.
Not sure this follows; Fraction(1.1) raises an exception, you have to use Fraction.from_float().
Is there any good reason for this, other than a parallel with Decimal? It seems to me that Raymond's arguments for allowing direct construction of a Decimal from a float apply equally well to the Fraction type.
If we're going to allow Decimal(1.1), I'd like to allow Fraction(1.1) to succeed as well (giving the equivalent of Fraction.from_float(1.1)).
The main argument against allowing this (for both Fraction and Decimal) seems to be that the result of Decimal(1.1) or Fraction(1.1) could be confusing. But it's an immediate, explicit confusion, which can be quickly resolved by pointing the confusee to the section on floating-point in the appendix, so I don't find this objection particularly compelling.
Agreed. If people don't learn about from_float() they might do weird stuff like Decimal(0) + 1.1. Making Fraction and Decimal do the same thing should be easy enough. -- --Guido van Rossum (python.org/~guido)
For the record, I thought I would take a stab at making a single post that recaps the trade-offs and reasoning behind the decision to have Fraction + decimal/float --> decimal/float. Pros: * While we know that both decimal and binary floats have a fixed internal precision and can be converted losslessly to a rational, that doesn't correspond to the way we think about them. We tend to think of floating point values as real numbers, not as rationals. * There is a notion of fractions being used for unrounded arithmetic and floats operations being rounded arithmetic. So, it doesn't make sense to create the illusion of an unrounded result from inputs that we already subject to rounding. * Backward compatibility. That is what the fractions module already does and we haven't have any problems with it. Cons: * The coercion logic for comparisons won't match the coercion logic for arithmetic operations. The former strives to be exact and to be consistent with hashing while the latter goes in the opposite direction. * Operations such as some_float + some_fraction are subject to double rounding. The potentially produces a different result than the single rounding in: float(Fraction.from_float(some_float) + some_fraction) Raymond
Raymond Hettinger wrote:
* The coercion logic for comparisons won't match the coercion logic for arithmetic operations. The former strives to be exact and to be consistent with hashing while the latter goes in the opposite direction.
Although Guido pointed out that float/long comparisons and coercions blazed the trail on this particular discrepancy years ago. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia ---------------------------------------------------------------
On Mon, Mar 22, 2010 at 7:00 PM, Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On Mar 22, 2010, at 11:26 AM, Mark Dickinson wrote:
Just for the record, I'd also prefer Decimal + Fraction -> Decimal.
Guido was persuasive on why float + Fraction --> float, so this makes sense for the same reasons.
For the implementation, is there a way to avoid the double rounding in myfloat + myfrac.numerator / myfrac.denominator?
Perhaps translate it to:
f = Fractions.from_decimal(myfloat) + myfract # Lossless, exact addition return f.numerator / f.denominator # Only one decimal context rounding applied.
I'm not sure; I see a couple of problems with this. (1) It's fine for the basic arithmetic operations that Fraction already supports, but what about all the other Decimal methods that don't have Fraction counterparts. (2) It bothers me that the Decimal -> Fraction conversion can be inefficient in cases like Decimal('1e<large>'); currently, all Decimal operations are relatively efficient (no exponential-time behaviour) provided only that the coefficients don't get too large; large exponents aren't a problem. I think getting this to work would involve a lot of extra code and significant 'cleverness'. I'd prefer the simple-to-implement and simple-to-explain option of rounding the Fraction before performing the operation, even if this means that the whole operation involves two rounding operations. It's not so different from what currently happens for Fraction+float, or even int+float. Mark
On 22 March 2010 19:32, Mark Dickinson <dickinsm@gmail.com> wrote:
I think getting this to work would involve a lot of extra code and significant 'cleverness'. I'd prefer the simple-to-implement and simple-to-explain option of rounding the Fraction before performing the operation, even if this means that the whole operation involves two rounding operations. It's not so different from what currently happens for Fraction+float, or even int+float.
My instinct throughout this discussion has been that a_decimal + a_fraction is in effect a_decimal + a_fraction.numerator / a_fraction.denominator and as the numerator and denomination can be converted into exact decimals, my instincts "expect" a double rounding. Anything else effectively means implementing partial cases of (a+b/c) treated as a ternary operation. So I guess I'm +1 on Mark's suggestion. Paul.
Raymond Hettinger <raymond.hettinger@gmail.com> wrote:
On Mar 22, 2010, at 11:26 AM, Mark Dickinson wrote:
Just for the record, I'd also prefer Decimal + Fraction -> Decimal.
Guido was persuasive on why float + Fraction --> float, so this makes sense for the same reasons.
For the implementation, is there a way to avoid the double rounding in myfloat + myfrac.numerator / myfrac.denominator?
Perhaps translate it to:
f = Fractions.from_decimal(myfloat) + myfract # Lossless, exact addition return f.numerator / f.denominator # Only one decimal context rounding applied.
Elsewhere in the decimal module, there is a fundamental notion that numbers are exact and only the results of operations are rounded. For example, it is possible in decimal to add together two high precision numbers but do so in a low precision context.
I don't think this will be practical for huge decimal exponents. Also, at first glance I wonder how to integrate this cleanly into the control flow. convert_other() will not work, so there would need to be a special case for fractions in each function. Or do you mean to outsource the whole computation to the fractions module, which calls decimal only for the final division? Stefan Krah
Mark Dickinson wrote:
It might make sense for Decimal + complex mixed-type operations to be disallowed, for example.
As long as you're allowing Decimal-float comparisons, Decimal-complex comparison for equality has an obvious interpretation. -- Greg
On Tue, Mar 23, 2010 at 12:33 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Mark Dickinson wrote:
It might make sense for Decimal + complex mixed-type operations to be disallowed, for example.
As long as you're allowing Decimal-float comparisons, Decimal-complex comparison for equality has an obvious interpretation.
Agreed. Decimal-to-complex equality and inequality comparisons should be permitted. Order comparisons would raise TypeError (just as complex-to-complex and float-to-complex comparisons do at the moment.) Mark
Sorry to intervene out of the blue, but I find the suggested rule for fractional to decimal conversion not as clean as I'd expect. If fractions are converted to decimals when doing arithmetics, would it be worthwhile to at least provide a minimum of fractional conversion integrity? What I have in mind is the following rule: When doing conversion from fraction to decimal, always generate a whole number of repeating digits, always at least twice. Examples, with a precision of 5 in Decimal: 1/2 -> 0.50000 1/3 -> 0.33333 1/11 -> 0.090909 # Note that we produced 6 digits, because # the repeating pattern contains 2 digits. 1/7 -> 0.142857142857 # Always at least two full patterns. The benefits I see are that: 1. If a number can be represented exactly it will be converted exactly. 2. The minimum precision requested is respected. 3. The conversion yields something that will convert back more precisely. Not perfectly, but see the next point. 4. Since the repeating pattern is present at least twice at the end, one can augment the precision of the conversion by detecting the repetition and adding more. This detection is trivial.
On Mon, Mar 22, 2010 at 8:02 PM, Pierre B. <pierrebai@hotmail.com> wrote:
Sorry to intervene out of the blue, but I find the suggested rule for fractional to decimal conversion not as clean as I'd expect.
If fractions are converted to decimals when doing arithmetics, would it be worthwhile to at least provide a minimum of fractional conversion integrity? What I have in mind is the following rule:
When doing conversion from fraction to decimal, always generate a whole number of repeating digits, always at least twice.
Examples, with a precision of 5 in Decimal:
1/2 -> 0.50000
1/3 -> 0.33333
1/11 -> 0.090909 # Note that we produced 6 digits, because # the repeating pattern contains 2 digits.
1/7 -> 0.142857142857 # Always at least two full patterns.
And for 1/123123127? The decimal expansion of this fraction has a period of over 15 million! Sorry, but this doesn't seem like a feasible or desirable strategy. Mark
Pierre B. <pierrebai <at> hotmail.com> writes:
4. Since the repeating pattern is present at least twice at the end, one can augment the precision of the conversion by detecting the repetition and adding more. This detection is trivial.
Wrong of me. This point is acutally invalid, since it is impossible to generally differentiate where the pattern ends. The counter example is: 0.177177177177... vs 0.177.17777777...
Guido van Rossum <guido@python.org> wrote:
Decimal + float --> Decimal
If everybody associated with the Decimal implementation wants this I won't stop you; as I repeatedly said my intuition about this one (as opposed to the other two above) is very weak.
I've been following the discussion only passively so far, but I also think this is the most logical solution. Stefan Krah
Mark Dickinson wrote:
But the Fraction type is going to mess this up: for Decimal + Fraction -> Decimal, I don't see any other sensible option than to convert the Fraction using the current context, since lossless conversion isn't generally possible.
You could convert the Decimal to a Fraction, do the arithmetic as Fractions, and convert the result back to Decimal using the current precision. I think this would satisfy the principle of performing the calculation as though infinite precision were available, and the final rounding is justified, since seen from the outside we are performing a single operation on two numbers. Another approach would be to convert the numerators and denominators separately to Decimal, and then do a/b + c/d = (a*d + b*c) / (b*d) using the usual rules of Decimal arithmetic. This would incur more than one rounding, but the rules for Decimals concern operations between two Decimals, and we have a Decimal and a Fraction here, so all bets could be considered off. -- Greg
Greg Ewing wrote:
Mark Dickinson wrote:
But the Fraction type is going to mess this up: for Decimal + Fraction -> Decimal, I don't see any other sensible option than to convert the Fraction using the current context, since lossless conversion isn't generally possible.
You could convert the Decimal to a Fraction, do the arithmetic as Fractions, and convert the result back to Decimal using the current precision.
It gets rather messy implementation-wise if you do it that way. As Stefan pointed out, given the Decimal module's existing execution model of invoking "convert_other" to create a Decimal instance and then proceeding from there, it seems most straightforward to adopt the same approach for floats and Fractions (i.e. keep the changes to handle the new types inside the existing argument conversion algorithm). Such a change should make Decimal/float and Decimal/Fraction arithmetic work in a comprehensible way, albeit with double-rounding in the case of the latter. Comparisons would still need to be handled separately to avoid the lossy conversions. Regards, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia ---------------------------------------------------------------
participants (10)
-
Adam Olsen
-
Alexander Belopolsky
-
Greg Ewing
-
Guido van Rossum
-
Mark Dickinson
-
Nick Coghlan
-
Paul Moore
-
Pierre B.
-
Raymond Hettinger
-
Stefan Krah