Thread: [Pyobjc-dev] NSNumbers and NSDecimalNumbers
Brought to you by:
ronaldoussoren
From: Pierce T.W. I. <pi...@ma...> - 2005-02-04 18:28:11
|
First off, I have a general problem in that NSDecimalNumbers work strangely as released >>> dn = Foundation.NSDecimalNumber.decimalNumberWithString_("10") >>> print dn+1.0 <---- throws an exception, see below So perhaps, this is just a bug in the bridge, I dunno. Next, there's a problem with Foundation (Apple's problem, not PyObjC) that NSDecimalNumbers can really only deal with NSDecimalNumbers. So Objective-C code like this: dn =[NSDecimalNumber decimalNumberWithString:@"10.0"]; n = [NSNumber numberWithInt:1]; dn2 = [NSDecimalNumber decimalNumberByAdding: n]; will crash or return weird results. You can see exactly how weird below. So what I've been doing is changing the mapping as numbers move into and out of Python. Right now, the mapping is as follows: into python: NSNumber -> NSNumber (python wrap of objc object) NSDecimalNumber -> NSDecimalNumber (python wrap of objc object) out of python: PyNumber -> NSNumber of appropriate type So I change the mapping as follows: into python: NSNumber -> NSNumber (python wrap of objc object) (same as before) NSDecimalNumber -> PyFloat (convert to plain number so I can do math) out of python: PyNumber -> NSDecimalNumber (force to NSDecimalNumber) Now I asked about this before, and someone pointed out that forcing everything that came out of Python to be an NSDecimalNumber was kind of bad because then dictionary keys and such would change as they crossed the bridge. Which was true, so I sort of resigned myself to having to always hack the release for my purposes. With Bob's latest changes though, I'm wondering if it's still inappropriate. I'm basing this on two assumptions: 1. That the fact that I can't add a Python float value to an NSDecimalNumber in Python is just a bug. (since I seem to be able to add 1 but not 1.0). That would let me remove the conversion of NSDecimalNumbers to PyFloats. 2. Since we're no longer converting NSNumbers back and forth, that means that if you pass an NSNumber into Python, you get the same value back. Same thing for NSDecimalNumbers as well. That leaves the case where the bridge is having to convert a python value into an object value for objective-C, and it's not clear what the correct thing to do is there anyways. For instance: print Foundation.NSNumber.numberWithChar_(127)+1 gives you 128.0... If I can't convince you all that its ok to return NSDecimalNumbers for any Python object, then I'm wondering if there's any way I can override the conversion at runtime, since I'm always loading my own objective-C frameworks anyways, I could change it in there. Pierce BUG DEMONSTRATIONS: Before my hacks (SVN HEAD): >>> import objc >>> import Foundation >>> dn = Foundation.NSDecimalNumber.decimalNumberWithString_("10") >>> n=Foundation.NSNumber.numberWithDouble_(1) >>> print dn 10 >>> print dn+1 11 >>> print dn+1.0 Traceback (most recent call last): File "<stdin>", line 1, in ? File "build/lib.darwin-7.7.0-Power_Macintosh-2.3/objc/_convenience.py", line 427, in __add__CFNumber return number_wrap(_num_to_python(numA) + _num_to_python(numB)) TypeError: unsupported operand type(s) for +: 'Foundation.NSDecimal' and 'float' >>> dn2 = dn.decimalNumberByAdding_(dn) >>> print dn2 20 >>> dn3= dn.decimalNumberByAdding_(1) >>> print dn3 10 <--- wrong answer, but no error? >>> dn4= dn.decimalNumberByAdding_(n) >>> print dn4 10 <--- wrong answer, but no error? Pretty weird results. You can't just do simple math in Python with the NSDNs, and sometimes you don't even get an error, just the wrong answer! After my hacks: >>> import objc >>> import Foundation >>> dn = Foundation.NSDecimalNumber.decimalNumberWithString_("10") >>> n=Foundation.NSNumber.numberWithDouble_(1) >>> print dn 10.0 <---- Python prints .0 after floats >>> print dn+1 11.0 <---- Again >>> print dn+1.0 11.0 <---- No error, just works. >>> dn2 = dn.decimalNumberByAdding_(dn) Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: 'float' object has no attribute 'decimalNumberByAdding_ Ok, that was as expected really, because I just slam all NSDNs into a PyFloat, so the correct code is that we no longer have to futz around with decimalNumberByAdding: at all: >>> dn2 = dn + dn >>> print dn2 20.0 >>> print n+dn 11.0 >>> dn3= dn+1 >>> print dn3 11.0 |
From: Ronald O. <ron...@ma...> - 2005-02-05 07:01:34
|
On 4-feb-05, at 19:27, Pierce T.Wetter III wrote: > First off, I have a general problem in that NSDecimalNumbers work > strangely as released > > > >>> dn = Foundation.NSDecimalNumber.decimalNumberWithString_("10") > >>> print dn+1.0 <---- throws an exception, see below > > So perhaps, this is just a bug in the bridge, I dunno. No, this is a feature. There is no automatic conversion from float to NSDecimal and NSDecimalNumber. This is done to mimic the decimal.Decimal type (introduced in python 2.4, see the python documentation or the relevant PEP for the rationale for this). > > Next, there's a problem with Foundation (Apple's problem, not PyObjC) > that NSDecimalNumbers can really only deal with NSDecimalNumbers. So > Objective-C code like this: > > dn =[NSDecimalNumber decimalNumberWithString:@"10.0"]; > n = [NSNumber numberWithInt:1]; > dn2 = [NSDecimalNumber decimalNumberByAdding: n]; > > will crash or return weird results. You can see exactly how weird > below. > > So what I've been doing is changing the mapping as numbers move into > and out of > Python. Right now, the mapping is as follows: > > into python: > NSNumber -> NSNumber (python wrap of objc object) > NSDecimalNumber -> NSDecimalNumber (python wrap of objc object) > > out of python: > PyNumber -> NSNumber of appropriate type > > So I change the mapping as follows: > > into python: > NSNumber -> NSNumber (python wrap of objc object) (same as before) > NSDecimalNumber -> PyFloat (convert to plain number so I can do math) We have thought about that in the past, and we rejected this. NSDecimalNumber has a greater range and precision than python float (or C doubles). > > out of python: > PyNumber -> NSDecimalNumber (force to NSDecimalNumber) Dunno about this one. > > Now I asked about this before, and someone pointed out that forcing > everything that > came out of Python to be an NSDecimalNumber was kind of bad because > then dictionary keys > and such would change as they crossed the bridge. > > Which was true, so I sort of resigned myself to having to always hack > the release for > my purposes. With Bob's latest changes though, I'm wondering if it's > still inappropriate. > > I'm basing this on two assumptions: > > 1. That the fact that I can't add a Python float value to an > NSDecimalNumber in Python is > just a bug. (since I seem to be able to add 1 but not 1.0). That > would let me remove the > conversion of NSDecimalNumbers to PyFloats. It is not a bug, see above. > > 2. Since we're no longer converting NSNumbers back and forth, that > means that if you > pass an NSNumber into Python, you get the same value back. Same > thing for > NSDecimalNumbers as well. > > That leaves the case where the bridge is having to convert a > python value into an object > value for objective-C, and it's not clear what the correct thing > to do is there anyways. > For instance: print Foundation.NSNumber.numberWithChar_(127)+1 > gives you 128.0... No it doesn't, it gives 128. This is the right behaviour. The fact that Foundation.NSNumber.numberWithChar_('a') also works is less correct (in this partical case), but I can't help it that C is terribly confused w.r.t. the difference between characters and numbers: 'char' doubles as a small numeric type and the representation of characters. > > If I can't convince you all that its ok to return NSDecimalNumbers for > any Python object, then I'm wondering if there's any way I can > override the conversion at runtime, since I'm always loading my own > objective-C frameworks anyways, I could change it in there. > > Pierce > > BUG DEMONSTRATIONS: > > Before my hacks (SVN HEAD): > > >>> import objc > >>> import Foundation > >>> dn = Foundation.NSDecimalNumber.decimalNumberWithString_("10") > >>> n=Foundation.NSNumber.numberWithDouble_(1) > >>> print dn > 10 > >>> print dn+1 > 11 > >>> print dn+1.0 > Traceback (most recent call last): > File "<stdin>", line 1, in ? > File > "build/lib.darwin-7.7.0-Power_Macintosh-2.3/objc/_convenience.py", > line 427, in __add__CFNumber > return number_wrap(_num_to_python(numA) + _num_to_python(numB)) > TypeError: unsupported operand type(s) for +: 'Foundation.NSDecimal' > and 'float' > > >>> dn2 = dn.decimalNumberByAdding_(dn) > >>> print dn2 > 20 > >>> dn3= dn.decimalNumberByAdding_(1) > >>> print dn3 > 10 <--- wrong answer, but no error? That's a bug in Cocoa. > >>> dn4= dn.decimalNumberByAdding_(n) > >>> print dn4 > 10 <--- wrong answer, but no error? > > > Pretty weird results. You can't just do simple math in Python with the > NSDNs, and sometimes you don't even get an error, just the wrong > answer! > > After my hacks: > > >>> import objc > >>> import Foundation > >>> dn = Foundation.NSDecimalNumber.decimalNumberWithString_("10") > >>> n=Foundation.NSNumber.numberWithDouble_(1) > >>> print dn > 10.0 <---- Python prints .0 after floats > >>> print dn+1 > 11.0 <---- Again > >>> print dn+1.0 > 11.0 <---- No error, just works. > >>> dn2 = dn.decimalNumberByAdding_(dn) > Traceback (most recent call last): > File "<stdin>", line 1, in ? > AttributeError: 'float' object has no attribute 'decimalNumberByAdding_ > > Ok, that was as expected really, because I just slam all NSDNs into a > PyFloat, so > the correct code is that we no longer have to futz around with > decimalNumberByAdding: at all: > > >>> dn2 = dn + dn > >>> print dn2 > 20.0 > >>> print n+dn > 11.0 > >>> dn3= dn+1 > >>> print dn3 > 11.0 |
From: Bob I. <bo...@re...> - 2005-02-05 07:16:56
|
On Feb 5, 2005, at 2:01 AM, Ronald Oussoren wrote: > > On 4-feb-05, at 19:27, Pierce T.Wetter III wrote: > >> First off, I have a general problem in that NSDecimalNumbers work >> strangely as released >> >> >> >>> dn = Foundation.NSDecimalNumber.decimalNumberWithString_("10") >> >>> print dn+1.0 <---- throws an exception, see below >> >> So perhaps, this is just a bug in the bridge, I dunno. > > No, this is a feature. There is no automatic conversion from float to > NSDecimal and NSDecimalNumber. This is done to mimic the > decimal.Decimal type (introduced in python 2.4, see the python > documentation or the relevant PEP for the rationale for this). > >> >> Next, there's a problem with Foundation (Apple's problem, not PyObjC) >> that NSDecimalNumbers can really only deal with NSDecimalNumbers. So >> Objective-C code like this: >> >> dn =[NSDecimalNumber decimalNumberWithString:@"10.0"]; >> n = [NSNumber numberWithInt:1]; >> dn2 = [NSDecimalNumber decimalNumberByAdding: n]; >> >> will crash or return weird results. You can see exactly how weird >> below. This is a bug in the given code! You should've gotten a compiler warning telling you not to do that. -(NSDecimalNumber*)decimalNumberByAdding:(NSDecimalNumber*) says that it takes and returns something that descends from NSDecimalNumber. If you give it something else, the result is undefined. Very very few methods in Apple's frameworks guarantee that they will verify input arguments for performance reasons. >> So what I've been doing is changing the mapping as numbers move into >> and out of >> Python. Right now, the mapping is as follows: >> >> into python: >> NSNumber -> NSNumber (python wrap of objc object) >> NSDecimalNumber -> NSDecimalNumber (python wrap of objc object) >> >> out of python: >> PyNumber -> NSNumber of appropriate type >> >> So I change the mapping as follows: >> >> into python: >> NSNumber -> NSNumber (python wrap of objc object) (same as before) >> NSDecimalNumber -> PyFloat (convert to plain number so I can do math) > We have thought about that in the past, and we rejected this. > NSDecimalNumber has a greater range and precision than python float > (or C doubles). > >> >> out of python: >> PyNumber -> NSDecimalNumber (force to NSDecimalNumber) > Dunno about this one. -1 here. > >> >> Now I asked about this before, and someone pointed out that forcing >> everything that >> came out of Python to be an NSDecimalNumber was kind of bad because >> then dictionary keys >> and such would change as they crossed the bridge. >> >> Which was true, so I sort of resigned myself to having to always hack >> the release for >> my purposes. With Bob's latest changes though, I'm wondering if it's >> still inappropriate. >> >> I'm basing this on two assumptions: >> >> 1. That the fact that I can't add a Python float value to an >> NSDecimalNumber in Python is >> just a bug. (since I seem to be able to add 1 but not 1.0). That >> would let me remove the >> conversion of NSDecimalNumbers to PyFloats. > It is not a bug, see above. > >> >> 2. Since we're no longer converting NSNumbers back and forth, that >> means that if you >> pass an NSNumber into Python, you get the same value back. Same >> thing for >> NSDecimalNumbers as well. >> >> That leaves the case where the bridge is having to convert a >> python value into an object >> value for objective-C, and it's not clear what the correct thing >> to do is there anyways. >> For instance: print Foundation.NSNumber.numberWithChar_(127)+1 >> gives you 128.0... > No it doesn't, it gives 128. This is the right behaviour. The fact > that Foundation.NSNumber.numberWithChar_('a') also works is less > correct (in this partical case), but I can't help it that C is > terribly confused w.r.t. the difference between characters and > numbers: 'char' doubles as a small numeric type and the representation > of characters. > >> >> If I can't convince you all that its ok to return NSDecimalNumbers >> for any Python object, then I'm wondering if there's any way I can >> override the conversion at runtime, since I'm always loading my own >> objective-C frameworks anyways, I could change it in there. >> >> Pierce >> >> BUG DEMONSTRATIONS: >> >> Before my hacks (SVN HEAD): >> >> >>> import objc >> >>> import Foundation >> >>> dn = Foundation.NSDecimalNumber.decimalNumberWithString_("10") >> >>> n=Foundation.NSNumber.numberWithDouble_(1) >> >>> print dn >> 10 >> >>> print dn+1 >> 11 >> >>> print dn+1.0 >> Traceback (most recent call last): >> File "<stdin>", line 1, in ? >> File >> "build/lib.darwin-7.7.0-Power_Macintosh-2.3/objc/_convenience.py", >> line 427, in __add__CFNumber >> return number_wrap(_num_to_python(numA) + _num_to_python(numB)) >> TypeError: unsupported operand type(s) for +: 'Foundation.NSDecimal' >> and 'float' >> >> >>> dn2 = dn.decimalNumberByAdding_(dn) >> >>> print dn2 >> 20 >> >>> dn3= dn.decimalNumberByAdding_(1) >> >>> print dn3 >> 10 <--- wrong answer, but no error? > > That's a bug in Cocoa. >> >>> dn4= dn.decimalNumberByAdding_(n) >> >>> print dn4 >> 10 <--- wrong answer, but no error? Actually, again, this is a bug in the given code! 'decimalNumberByAdding:' says that it takes a NSDecimalNumber* as input. If you give it something that does not descend from NSDecimalNumber*, the result is undefined. The correct code is either to use Python operators (we go through a lot of trouble to make this possible and correct with similar semantics as decimal.Decimal from Python 2.4), or to pass it NSDecimalNumber.numberWithInt_(1). >> Pretty weird results. You can't just do simple math in Python with >> the NSDNs, and sometimes you don't even get an error, just the wrong >> answer! Nope, the results are completely expected. Also, you can definitely do simple math with NSDecimalNumber using Python operators, you just can't mix them with Python floats. PyObjC generally does not try and contort semantics of Foundation classes because we think it might be "easier to use". PyObjC is a language bridge, not a new set of Foundation classes tailored for Python users. -bob |
From: Pierce T.W. I. <pi...@ma...> - 2005-02-05 08:43:22
|
Ok, two things: You're copying a bad spec. It's insane that dn + 1 would work, while dn + 1.0 wouldn't. That is inconsistent with every other similar type of math in Python: int + float -> promotes to float float + complex -> promotes to complex int + decimal -> promotes to decimal float + decimal -> won't promote But ok, I guess I would need to convince the people who wrote that PEP, not you. One of the reasons I always converted NSDNs to PyFloats was because in past versions, you couldn't print the value of an NSDN, though that seems to be fixed now. However, the bridge is not entirely blameless. It assumes that if it has a python value, and it needs to convert that number to an id type, that NSNumber is the correct conversion. Ideally, if it had a way to extract the type in more detail from a method signature: dn.decimalNumberByAdding_(1) wouldn't fail, because the bridge would know it needed to make an NSDecimalNumber, not an NSNumber. The real subtle problem with this is accessors: - (void) setValue: (NSDecimalNumber *) newValue; obj.setValue_(1) Won't fail immediately, it will fail when obj.value() is used much, much later. So in the case of NSDecimalNumbers, the bridge is not garunteed to do the right thing, because it assumes that all Python numbers can be converted to NSNumbers. As an aside, the bridge also assumes that an NSNumber of the minimum range is the appropriate conversion, which also may not make sense. So print Foundation.NSNumber.numberWithChar_(126)+1 outputs 127 while print Foundation.NSNumber.numberWithChar_(127)+1 outputs 128.0, because 128 is out of the range of Py_Char, so its now a different type of NSNumber. So if you do math with an NSNumber, you may get a different type of NSNumber back. > PyObjC generally does not try and contort semantics of Foundation > classes because we think it might be "easier to use". PyObjC is a > language bridge, not a new set of Foundation classes tailored for > Python users. If that were true then you would have to rip out all the automatic conversion of python numbers to NSNumbers, since: - (void) setValue: (NSNumber *) newValue; obj.setValue_(1) Isn't Foundation semantics either, given that the equivalent objc code would be: [obj setValue: [NSNumber numberWithInt:1]]; the python code should be: obj.setValue_(NSNumber.numberWithInt_()) instead. So given all that, I'm sure I'm not going to convince you of the utility of the way I'm doing it, but I am going to mock you a little bit because you aren't self consistent. So...is there any way to override the handling of NSNumbers at runtime. I see some dead code that used to use a mapping table? Pierce |
From: Ronald O. <ron...@ma...> - 2005-02-05 14:01:10
|
On 5-feb-05, at 9:43, Pierce T.Wetter III wrote: > Ok, two things: > > > You're copying a bad spec. It's insane that > > dn + 1 > > would work, while > > dn + 1.0 > > wouldn't. That is inconsistent with every other similar type of math > in Python: Read the spec. There are very good reasons for this. > > int + float -> promotes to float > float + complex -> promotes to complex > int + decimal -> promotes to decimal > float + decimal -> won't promote > > But ok, I guess I would need to convince the people who wrote that > PEP, not you. One of the reasons I always converted NSDNs to PyFloats > was because in past versions, you couldn't print the value of an NSDN, > though that seems to be fixed now. > > However, the bridge is not entirely blameless. It assumes that if it > has a python value, and it needs to convert that number to an id type, > that NSNumber is the correct conversion. Ideally, if it had a way to > extract the type in more detail from a method signature: > > dn.decimalNumberByAdding_(1) > > wouldn't fail, because the bridge would know it needed to make an > NSDecimalNumber, not an NSNumber. The information is not available to the bridge. Use dn + 1 instead. > > The real subtle problem with this is accessors: > > - (void) setValue: (NSDecimalNumber *) newValue; > > obj.setValue_(1) > > Won't fail immediately, it will fail when obj.value() is used much, > much later. So in the case of NSDecimalNumbers, the bridge is not > garunteed to do the right thing, because it assumes that all Python > numbers can be converted to NSNumbers. It does do the right thing. Your code is buggy. > > As an aside, the bridge also assumes that an NSNumber of the minimum > range is the appropriate conversion, which also may not make sense. > > So > > print Foundation.NSNumber.numberWithChar_(126)+1 > > outputs 127 > > while > > print Foundation.NSNumber.numberWithChar_(127)+1 > > outputs 128.0, because 128 is out of the range of Py_Char, so its > now a different type of NSNumber. So if you do math with an NSNumber, > you may get a different type of NSNumber back. What version of pyobjc are you using? SVN trunk says: >>> Foundation.NSNumber.numberWithChar_(127)+1 128 >>> type(_) <objective-c class NSCFNumber at 0x300620> >>> Foundation.NSNumber.numberWithChar_(126)+1 127 >>> type(_) <objective-c class NSCFNumber at 0x300620> > >> PyObjC generally does not try and contort semantics of Foundation >> classes because we think it might be "easier to use". PyObjC is a >> language bridge, not a new set of Foundation classes tailored for >> Python users. > > If that were true then you would have to rip out all the automatic > conversion of python numbers to NSNumbers, since: > > - (void) setValue: (NSNumber *) newValue; > > obj.setValue_(1) > > Isn't Foundation semantics either, given that the equivalent objc > code would be: > > [obj setValue: [NSNumber numberWithInt:1]]; > > the python code should be: > > obj.setValue_(NSNumber.numberWithInt_()) instead. Both NSNumber and python's int() are classes. > > So given all that, I'm sure I'm not going to convince you of the > utility of the way I'm doing it, but I am going to mock you a little > bit because you aren't self consistent. > > So...is there any way to override the handling of NSNumbers at > runtime. I see some dead code that used to use a mapping table? I am thinking of adding a OC_Number class to wrap python numbers, this might be needed to ensure that identity is preserved acros the bridge. Ronald |
From: Pierce T.W. I. <pi...@ma...> - 2005-02-05 17:22:25
|
>> Ok, two things: >> >> >> You're copying a bad spec. It's insane that >> >> dn + 1 >> >> would work, while >> >> dn + 1.0 >> >> wouldn't. That is inconsistent with every other similar type of >> math in Python: > > Read the spec. There are very good reasons for this. I will. Reading... Here's the link: http://www.python.org/peps/pep-0327.html#id17 While their logic is correct as far as it goes, strictly speaking, as the spec points out, its not always an error to convert between float and Decimal, they just punted because it was hard. I think that was a mistake (this could be handled in their Context method), but I'll take it up elsewhere. As a sample, after reading the PEP, what's wrong with this code? a= b + c As for the rest of your and Bob's arguments, let me restate from a high level. First, NSNumber is not a class, its a class cluster. NSNumber.numberWithInt() and NSNumber.numberWithLong() return two different subclasses of NSNumber. In 1.0-1.2, PyObjC always converted NSNumbers to their python equivalent objects, and then converted them back. This introduced a subtle error because NSNumber is not a class, but a class cluster: changing class types can change hash values, etc. This caused problems with reading plists and other files, so it went away. In 1.3, PyObjC no longer converts NSNumbers which is a good thing for identity reasons according to the code. In both versions, there is a subtle point of confusion in that the bridge will attempt to guess at the type of NSNumber desired based on its Python type when the bridge needs an "id" variable type. It is important to realize that: 1. If the method was expecting the NSDecimalNumber variant of NSNumber, it can fail in subtle ways much, much later (in objective-C code for instance). You may not consider it a "bug", but its definitely a "gotcha". 2. If the method was expecting a particular subclass of the NSNumber class cluster, the value it gets back might be different then expected. Mostly this only matters for things that call hash() and such, but since most of the hash values for the same numbers by design are the same for different NSNumber class clusters, you're generally ok. The work around would be to build the NSNumber of the specific type, but again, its a very subtle failure. One that might be so rare as to be non-existent in 99.99999% of the code out there. So the whole NSNumber/NSDecimalNumber <-> python conversion is not trouble free: print Foundation.NSNumber.numberWithUnsignedLong_(4000000000) -294967296 >> >> So given all that, I'm sure I'm not going to convince you of the >> utility of the way I'm doing it, but I am going to mock you a little >> bit because you aren't self consistent. >> >> So...is there any way to override the handling of NSNumbers at >> runtime. I see some dead code that used to use a mapping table? > > I am thinking of adding a OC_Number class to wrap python numbers, this > might be needed to ensure that identity is preserved acros the bridge. Hmm. Ok. Pierce |
From: Bob I. <bo...@re...> - 2005-02-05 14:22:10
|
On Feb 5, 2005, at 3:43 AM, Pierce T.Wetter III wrote: > Ok, two things: > > > You're copying a bad spec. It's insane that > > dn + 1 > > would work, while > > dn + 1.0 > > wouldn't. That is inconsistent with every other similar type of math > in Python: > > int + float -> promotes to float > float + complex -> promotes to complex > int + decimal -> promotes to decimal > float + decimal -> won't promote > > But ok, I guess I would need to convince the people who wrote that > PEP, not you. One of the reasons I always converted NSDNs to PyFloats > was because in past versions, you couldn't print the value of an NSDN, > though that seems to be fixed now. int -> float doesn't lose precision, float -> complex doesn't lose precision, int -> decimal doesn't lose precision. float -> decimal *does* lose precision. It's already inconsistent with every similar type of math in Python. > However, the bridge is not entirely blameless. It assumes that if it > has a python value, and it needs to convert that number to an id type, > that NSNumber is the correct conversion. Ideally, if it had a way to > extract the type in more detail from a method signature: > > dn.decimalNumberByAdding_(1) > > wouldn't fail, because the bridge would know it needed to make an > NSDecimalNumber, not an NSNumber. Yes, when you ask PyObjC to convert a Python int, long or float to an id then it will use NSNumber. > The real subtle problem with this is accessors: > > - (void) setValue: (NSDecimalNumber *) newValue; > > obj.setValue_(1) > > Won't fail immediately, it will fail when obj.value() is used much, > much later. So in the case of NSDecimalNumbers, the bridge is not > garunteed to do the right thing, because it assumes that all Python > numbers can be converted to NSNumbers. What's your point? -(void)setValue:(NSString *)newValue obj.setValue_(1) Can still fail, perhaps not immediately. The same is true of Python. PyObjC can not possibly verify all arguments and return values. > As an aside, the bridge also assumes that an NSNumber of the minimum > range is the appropriate conversion, which also may not make sense. > > So > > print Foundation.NSNumber.numberWithChar_(126)+1 > > outputs 127 > > while > > print Foundation.NSNumber.numberWithChar_(127)+1 > > outputs 128.0, because 128 is out of the range of Py_Char, so its > now a different type of NSNumber. So if you do math with an NSNumber, > you may get a different type of NSNumber back. Two things: (a) PyObjC 1.3a0 doesn't have that behavior, because it currently converts the result of an operation involving NSNumber subclasses *back* to a NSNumber, and NSNumber always uses the integer value for its description if is an integer regardless of what you initialized it with. (b) NSNumber is effectively a "single class". There is NO DIFFERENCE between NSNumber.numberWithFloat_(10.0) and NSNumber.numberWithChar_(10). It may use a different internal representation, but that is not of your concern. >> PyObjC generally does not try and contort semantics of Foundation >> classes because we think it might be "easier to use". PyObjC is a >> language bridge, not a new set of Foundation classes tailored for >> Python users. > > If that were true then you would have to rip out all the automatic > conversion of python numbers to NSNumbers, since: > > - (void) setValue: (NSNumber *) newValue; > > obj.setValue_(1) > > Isn't Foundation semantics either, given that the equivalent objc > code would be: > > [obj setValue: [NSNumber numberWithInt:1]]; > > the python code should be: > > obj.setValue_(NSNumber.numberWithInt_()) instead. > > So given all that, I'm sure I'm not going to convince you of the > utility of the way I'm doing it, but I am going to mock you a little > bit because you aren't self consistent. > > So...is there any way to override the handling of NSNumbers at > runtime. I see some dead code that used to use a mapping table? It is consistent. PyObjC automatically converts several Python types to id (the Objective-C type system NEVER cares which class name was declared, only whether it is an id or not). If you write [obj setValue:1] then you would be completely violating the type system, and the compiler would complain about that. This is not invalidating any semantics of Foundation classes because it simply is not possible to do [obj setValue:1] in Foundation. It's also not possible to say anObject[someKey] in Foundation either, or nsNumber + anotherNSNumber, but we allow that too. These are *conveniences* where we allow behavior that would otherwise simply raise an exception because such an operation can't even cross the bridge. All conversions we do are lossless. -bob |
From: Ronald O. <ron...@ma...> - 2005-02-05 14:45:14
|
On 5-feb-05, at 15:22, Bob Ippolito wrote: >> > > int -> float doesn't lose precision, float -> complex doesn't lose > precision, int -> decimal doesn't lose precision. float -> decimal > *does* lose precision. It's already inconsistent with every similar > type of math in Python. The problem is not that float -> decimal would loose precission (although it sometimes does), but that float is a base-2 floating point number while decimal is base-10. Because float is base-2 it cannot exactly represent some numbers, which confuses people at first. E.g. repr(1.1) == '1.1000000000000001' on most systems. What would NSDecimalNumber(1.1) return? The same as NSDecimalNumber("1.1") or the same as NSDecimalNumber("1.1000000000000001")? The first is incorrect, the seconds is likely to confuse some people. > >> However, the bridge is not entirely blameless. It assumes that if it >> has a python value, and it needs to convert that number to an id >> type, that NSNumber is the correct conversion. Ideally, if it had a >> way to extract the type in more detail from a method signature: >> >> dn.decimalNumberByAdding_(1) >> >> wouldn't fail, because the bridge would know it needed to make an >> NSDecimalNumber, not an NSNumber. > > Yes, when you ask PyObjC to convert a Python int, long or float to an > id then it will use NSNumber. > >> The real subtle problem with this is accessors: >> >> - (void) setValue: (NSDecimalNumber *) newValue; >> >> obj.setValue_(1) >> >> Won't fail immediately, it will fail when obj.value() is used much, >> much later. So in the case of NSDecimalNumbers, the bridge is not >> garunteed to do the right thing, because it assumes that all Python >> numbers can be converted to NSNumbers. > > What's your point? > > -(void)setValue:(NSString *)newValue > > obj.setValue_(1) > > Can still fail, perhaps not immediately. The same is true of Python. > PyObjC can not possibly verify all arguments and return values. This is not a pyobjc isssue: NSObject* foo = [NSNumber numberWithInt:1]; [obj setValue:foo]; This code is likely to compile and will fail at runtime, just like the python equivalent. > >> As an aside, the bridge also assumes that an NSNumber of the >> minimum range is the appropriate conversion, which also may not make >> sense. >> >> So >> >> print Foundation.NSNumber.numberWithChar_(126)+1 >> >> outputs 127 >> >> while >> >> print Foundation.NSNumber.numberWithChar_(127)+1 >> >> outputs 128.0, because 128 is out of the range of Py_Char, so its >> now a different type of NSNumber. So if you do math with an NSNumber, >> you may get a different type of NSNumber back. > > Two things: > (a) PyObjC 1.3a0 doesn't have that behavior, because it currently > converts the result of an operation involving NSNumber subclasses > *back* to a NSNumber, and NSNumber always uses the integer value for > its description if is an integer regardless of what you initialized it > with. > > (b) NSNumber is effectively a "single class". There is NO DIFFERENCE > between NSNumber.numberWithFloat_(10.0) and > NSNumber.numberWithChar_(10). It may use a different internal > representation, but that is not of your concern. > >>> PyObjC generally does not try and contort semantics of Foundation >>> classes because we think it might be "easier to use". PyObjC is a >>> language bridge, not a new set of Foundation classes tailored for >>> Python users. >> >> If that were true then you would have to rip out all the automatic >> conversion of python numbers to NSNumbers, since: >> >> - (void) setValue: (NSNumber *) newValue; >> >> obj.setValue_(1) >> >> Isn't Foundation semantics either, given that the equivalent objc >> code would be: >> >> [obj setValue: [NSNumber numberWithInt:1]]; >> >> the python code should be: >> >> obj.setValue_(NSNumber.numberWithInt_()) instead. >> >> So given all that, I'm sure I'm not going to convince you of the >> utility of the way I'm doing it, but I am going to mock you a little >> bit because you aren't self consistent. >> >> So...is there any way to override the handling of NSNumbers at >> runtime. I see some dead code that used to use a mapping table? > > It is consistent. PyObjC automatically converts several Python types > to id (the Objective-C type system NEVER cares which class name was > declared, only whether it is an id or not). If you write [obj > setValue:1] then you would be completely violating the type system, > and the compiler would complain about that. This is not invalidating > any semantics of Foundation classes because it simply is not possible > to do [obj setValue:1] in Foundation. It's also not possible to say > anObject[someKey] in Foundation either, or nsNumber + anotherNSNumber, > but we allow that too. These are *conveniences* where we allow > behavior that would otherwise simply raise an exception because such > an operation can't even cross the bridge. All conversions we do are > lossless. > > -bob > > -- X|support bv http://www.xsupport.nl/ T: +31 610271479 F: +31 204416173 |
From: Pierce T.W. I. <pi...@ma...> - 2005-02-05 18:03:36
|
On Feb 5, 2005, at 7:45 AM, Ronald Oussoren wrote: > > On 5-feb-05, at 15:22, Bob Ippolito wrote: > >>> >> >> int -> float doesn't lose precision, float -> complex doesn't lose >> precision, int -> decimal doesn't lose precision. float -> decimal >> *does* lose precision. It's already inconsistent with every similar >> type of math in Python. > > The problem is not that float -> decimal would loose precission > (although it sometimes does), but that float is a base-2 floating > point number while decimal is base-10. Because float is base-2 it > cannot exactly represent some numbers, which confuses people at first. > E.g. repr(1.1) == '1.1000000000000001' on most systems. What would > NSDecimalNumber(1.1) return? The same as NSDecimalNumber("1.1") or the > same as NSDecimalNumber("1.1000000000000001")? The first is incorrect, > the seconds is likely to confuse some people. Foundation handles this correctly actually, it rounds as needed when converting to NSDecimalNumber, such that NSDecimalNumber("1.1")==NSDecimalNumber(1.1). NSDecimalNumber(1.1111111111111115) may not be equal to NSDecimalNumber("1.1111111111111115") but that's a pretty standard failure: for (double x=0;x<1.0;x+=0.1) will run eleven times. >> >> What's your point? >> >> -(void)setValue:(NSString *)newValue >> >> obj.setValue_(1) >> >> Can still fail, perhaps not immediately. The same is true of Python. >> PyObjC can not possibly verify all arguments and return values. > > This is not a pyobjc isssue: > > NSObject* foo = [NSNumber numberWithInt:1]; > [obj setValue:foo]; > > This code is likely to compile and will fail at runtime, just like the > python equivalent. You'll also get a type warning at _compile_ time something python never does. I suspect you have the (id) everywhere ObjC coding style. Not me, I declare all my types, and so I get warnings from the compiler as much as possible, not runtime errors. > >> >>> As an aside, the bridge also assumes that an NSNumber of the >>> minimum range is the appropriate conversion, which also may not make >>> sense. >>> >>> So >>> >>> print Foundation.NSNumber.numberWithChar_(126)+1 >>> >>> outputs 127 >>> >>> while >>> >>> print Foundation.NSNumber.numberWithChar_(127)+1 >>> >>> outputs 128.0, because 128 is out of the range of Py_Char, so its >>> now a different type of NSNumber. So if you do math with an >>> NSNumber, you may get a different type of NSNumber back. >> >> Two things: >> (a) PyObjC 1.3a0 doesn't have that behavior, because it currently >> converts the result of an operation involving NSNumber subclasses >> *back* to a NSNumber, and NSNumber always uses the integer value for >> its description if is an integer regardless of what you initialized >> it with. Hmm... I just got: >>> print Foundation.NSNumber.numberWithChar_(126)+1 127.0 >>> print Foundation.NSNumber.numberWithChar_(127)+1 128.0 >> >> (b) NSNumber is effectively a "single class". There is NO DIFFERENCE >> between NSNumber.numberWithFloat_(10.0) and >> NSNumber.numberWithChar_(10). It may use a different internal >> representation, but that is not of your concern. Not strictly true, covered in previous email. The cases are obscure, but there is not "NO DIFFERENCE". >> >>>> PyObjC generally does not try and contort semantics of Foundation >>>> classes because we think it might be "easier to use". PyObjC is a >>>> language bridge, not a new set of Foundation classes tailored for >>>> Python users. >>> >>> If that were true then you would have to rip out all the automatic >>> conversion of python numbers to NSNumbers, since: >>> >>> - (void) setValue: (NSNumber *) newValue; >>> >>> obj.setValue_(1) >>> >>> Isn't Foundation semantics either, given that the equivalent objc >>> code would be: >>> >>> [obj setValue: [NSNumber numberWithInt:1]]; >>> >>> the python code should be: >>> >>> obj.setValue_(NSNumber.numberWithInt_()) instead. >>> >>> So given all that, I'm sure I'm not going to convince you of the >>> utility of the way I'm doing it, but I am going to mock you a little >>> bit because you aren't self consistent. >>> >>> So...is there any way to override the handling of NSNumbers at >>> runtime. I see some dead code that used to use a mapping table? >> >> It is consistent. PyObjC automatically converts several Python types >> to id (the Objective-C type system NEVER cares which class name was >> declared, only whether it is an id or not). If you write [obj >> setValue:1] then you would be completely violating the type system, >> and the compiler would complain about that. This is not invalidating >> any semantics of Foundation classes because it simply is not possible >> to do [obj setValue:1] in Foundation. It's also not possible to say >> anObject[someKey] in Foundation either, or nsNumber + >> anotherNSNumber, but we allow that too. These are *conveniences* >> where we allow behavior that would otherwise simply raise an >> exception because such an operation can't even cross the bridge. Personally, I think the conveniences are a good thing, not a bad thing. I just wished you were a little less attached to excluding those same conveniences from NSDecimalNumber. Your argument is that you have to be self consistent, but the reality is that consistency is an impossible goal because Python and ObjC have slightly different type systems. So perhaps NSDecimalNumbers should not be converted to python decimal types, instead the necessary methods could be added to the bridge from the python bridge side so that they worked much like NSNumbers do, so that: dn2= dn + 1.1 is really a known substitute for dn.decimalNumberByAdding_(NSDecimalNumber.numberWithDouble_(1.1)). I see that you're doing something similar in: Lib/objc/_convenience.py where you add the __add__ methods to NSNumber. I suppose I could do a similar thing for NSDecimalNumber and then remove the bridging to python.decimal. (Which might make sense for the same reason that it turned out not to be a good thing to bridge NSNumber.) Which leads to a Python question: A + B is really A.__add__(B) in python. For NSDecimalNumbers, I could add my own __add__ method so that if A is an NSDecimalNumber, it converts B to a decimal and returns the sum. But what if A is a regular float, and B is an NSDecimalNumber. How can I tell Python it needs to promote A to an NSDecimalNumber before doing the math? Presumably the complex number types had the same problem. This must come up with NSNumbers too, since: 1 + NSNumber(1) and NSNumber(1) + 1 both seem to work, but I can't figure out how you're doing it in _convenience.py. >> All conversions we do are lossless. No, they aren't because not all NSNumbers are created equal, and the python types don't map over the entire set of NSNumber types either. >>> print Foundation.NSNumber.numberWithUnsignedLong_(4000000000) -294967296 >>> print Foundation.NSNumber.numberWithUnsignedLong_(4000000000)+1 -294967295.0 >>> |
From: Bob I. <bo...@re...> - 2005-02-05 19:06:27
|
On Feb 5, 2005, at 1:02 PM, Pierce T.Wetter III wrote: > > On Feb 5, 2005, at 7:45 AM, Ronald Oussoren wrote: > >> >> On 5-feb-05, at 15:22, Bob Ippolito wrote: >> >>>> >>> >>> int -> float doesn't lose precision, float -> complex doesn't lose >>> precision, int -> decimal doesn't lose precision. float -> decimal >>> *does* lose precision. It's already inconsistent with every similar >>> type of math in Python. >> >> The problem is not that float -> decimal would loose precission >> (although it sometimes does), but that float is a base-2 floating >> point number while decimal is base-10. Because float is base-2 it >> cannot exactly represent some numbers, which confuses people at >> first. E.g. repr(1.1) == '1.1000000000000001' on most systems. What >> would NSDecimalNumber(1.1) return? The same as NSDecimalNumber("1.1") >> or the same as NSDecimalNumber("1.1000000000000001")? The first is >> incorrect, the seconds is likely to confuse some people. > > Foundation handles this correctly actually, it rounds as needed when > converting to NSDecimalNumber, such that > NSDecimalNumber("1.1")==NSDecimalNumber(1.1). > > NSDecimalNumber(1.1111111111111115) may not be equal to > NSDecimalNumber("1.1111111111111115") but that's a pretty standard > failure: > > for (double x=0;x<1.0;x+=0.1) will run eleven times. Well, these "standard failures" are part of the major reason why Python's decimal does not support this feature. We could diverge from the Python Way in our implementation of Foundation.NSDecimal, but I am -0 for this change and will defer this decision to Ronald. Good luck convincing him that NSDecimal should behave differently than Python's decimal type. >>> What's your point? >>> >>> -(void)setValue:(NSString *)newValue >>> >>> obj.setValue_(1) >>> >>> Can still fail, perhaps not immediately. The same is true of >>> Python. PyObjC can not possibly verify all arguments and return >>> values. >> >> This is not a pyobjc isssue: >> >> NSObject* foo = [NSNumber numberWithInt:1]; >> [obj setValue:foo]; >> >> This code is likely to compile and will fail at runtime, just like >> the python equivalent. > > You'll also get a type warning at _compile_ time something python > never does. > > I suspect you have the (id) everywhere ObjC coding style. Not me, I > declare all my types, and so I get warnings from the compiler as much > as possible, not runtime errors. I write Objective-C code just like you write Objective-C code. By the time Python knows about these selectors, the specific class information is long gone, so there is no hope of detecting these errors (wrong type of instance, but still an Objective-C instance) at runtime or compile time. >>>> As an aside, the bridge also assumes that an NSNumber of the >>>> minimum range is the appropriate conversion, which also may not >>>> make sense. >>>> >>>> So >>>> >>>> print Foundation.NSNumber.numberWithChar_(126)+1 >>>> >>>> outputs 127 >>>> >>>> while >>>> >>>> print Foundation.NSNumber.numberWithChar_(127)+1 >>>> >>>> outputs 128.0, because 128 is out of the range of Py_Char, so its >>>> now a different type of NSNumber. So if you do math with an >>>> NSNumber, you may get a different type of NSNumber back. >>> >>> Two things: >>> (a) PyObjC 1.3a0 doesn't have that behavior, because it currently >>> converts the result of an operation involving NSNumber subclasses >>> *back* to a NSNumber, and NSNumber always uses the integer value for >>> its description if is an integer regardless of what you initialized >>> it with. > > Hmm... I just got: > > >>> print Foundation.NSNumber.numberWithChar_(126)+1 > 127.0 > >>> print Foundation.NSNumber.numberWithChar_(127)+1 > 128.0 You're not using the same revision that I am, then. >>> (b) NSNumber is effectively a "single class". There is NO >>> DIFFERENCE between NSNumber.numberWithFloat_(10.0) and >>> NSNumber.numberWithChar_(10). It may use a different internal >>> representation, but that is not of your concern. > > Not strictly true, covered in previous email. The cases are obscure, > but there is not "NO DIFFERENCE". Yes, I said it was effectively a single class. I'm fully aware that is is a class cluster. Always class cluster *DOES ALWAYS* return NSCFNumber, at least for the given cases that I stated. The quotes were a way of saying that "this is not strictly true, but is a good enough description for the purposes of this discussion". >>>>> PyObjC generally does not try and contort semantics of >>>>> Foundation classes because we think it might be "easier to use". >>>>> PyObjC is a language bridge, not a new set of Foundation classes >>>>> tailored for Python users. >>>> >>>> If that were true then you would have to rip out all the automatic >>>> conversion of python numbers to NSNumbers, since: >>>> >>>> - (void) setValue: (NSNumber *) newValue; >>>> >>>> obj.setValue_(1) >>>> >>>> Isn't Foundation semantics either, given that the equivalent objc >>>> code would be: >>>> >>>> [obj setValue: [NSNumber numberWithInt:1]]; >>>> >>>> the python code should be: >>>> >>>> obj.setValue_(NSNumber.numberWithInt_()) instead. >>>> >>>> So given all that, I'm sure I'm not going to convince you of the >>>> utility of the way I'm doing it, but I am going to mock you a >>>> little bit because you aren't self consistent. >>>> >>>> So...is there any way to override the handling of NSNumbers at >>>> runtime. I see some dead code that used to use a mapping table? >>> >>> It is consistent. PyObjC automatically converts several Python >>> types to id (the Objective-C type system NEVER cares which class >>> name was declared, only whether it is an id or not). If you write >>> [obj setValue:1] then you would be completely violating the type >>> system, and the compiler would complain about that. This is not >>> invalidating any semantics of Foundation classes because it simply >>> is not possible to do [obj setValue:1] in Foundation. It's also not >>> possible to say anObject[someKey] in Foundation either, or nsNumber >>> + anotherNSNumber, but we allow that too. These are *conveniences* >>> where we allow behavior that would otherwise simply raise an >>> exception because such an operation can't even cross the bridge. > > Personally, I think the conveniences are a good thing, not a bad > thing. > > I just wished you were a little less attached to excluding those > same conveniences from NSDecimalNumber. Your argument is that you have > to be self consistent, but the reality is that consistency is an > impossible goal because Python and ObjC have slightly different type > systems. > > So perhaps NSDecimalNumbers should not be converted to python decimal > types, instead the necessary methods could be added to the bridge from > the python bridge side so that they > worked much like NSNumbers do, so that: > > dn2= dn + 1.1 > > is really a known substitute for > > dn.decimalNumberByAdding_(NSDecimalNumber.numberWithDouble_(1.1)). > > I see that you're doing something similar in: > Lib/objc/_convenience.py where you add the > __add__ methods to NSNumber. I suppose I could do a similar thing for > NSDecimalNumber and then remove the bridging to python.decimal. (Which > might make sense for the same reason that it turned out not to be a > good thing to bridge NSNumber.) It DOES NOT bridge to Python's decimal type (it did for a short while -- I think about 15 minutes -- if it was present, but then I reverted that). It bridges to a Foundation.NSDecimal type, which is the structure behind the implementation behind NSDecimalNumber. However, this wrapper more or less follows Python's decimal semantics. > Which leads to a Python question: > > A + B is really A.__add__(B) in python. Or B.__radd__(A), depending on the behavior of A.__add__. > For NSDecimalNumbers, I could add my own __add__ method so that if A > is an NSDecimalNumber, > it converts B to a decimal and returns the sum. > > But what if A is a regular float, and B is an NSDecimalNumber. How > can I tell Python it needs to promote A to an NSDecimalNumber before > doing the math? Presumably the complex number types had the same > problem. This must come up with NSNumbers too, since: > > 1 + NSNumber(1) > > and > > NSNumber(1) + 1 > > both seem to work, but I can't figure out how you're doing it in > _convenience.py. This is the B.__radd__(A) case. The way that _convenience.py works for numbers (in svn trunk) is the following: (1) If a NSNumber is involved, coerce it into a convenient type. This is either int, long, float, or Foundation.NSDecimal. (2) Perform the operation using the methods that Python normally uses (3) Coerce the result back into a NSNumber subclass (this is done just by making the value cross the bridge). >>> All conversions we do are lossless. > > No, they aren't because not all NSNumbers are created equal, and the > python types don't map over the entire set of NSNumber types either. > > >>> print Foundation.NSNumber.numberWithUnsignedLong_(4000000000) > -294967296 > >>> print Foundation.NSNumber.numberWithUnsignedLong_(4000000000)+1 > -294967295.0 > >>> Yes the Python types to map over the entire set of NSNumber types, and all of our conversions are lossless. However, Apple's implementation of NSNumber does not store whether the input value was signed or unsigned: >>> NSNumber.numberWithUnsignedInt_(1).objCType() 'i' >>> NSNumber.numberWithInt_(1).objCType() 'i' This isn't our problem. If you expect unsigned values, then you need to use Objective-C methods for dealing with them. It could technically be considered a bug in CoreFoundation/Foundation, but it doesn't effect Objective-C in any way so it's unlikely to be fixed. -bob |
From: Bob I. <bo...@re...> - 2005-02-05 20:43:27
|
On Feb 5, 2005, at 3:11 PM, Pierce T.Wetter III wrote: > > On Feb 5, 2005, at 12:05 PM, Bob Ippolito wrote: > >> >> On Feb 5, 2005, at 1:02 PM, Pierce T.Wetter III wrote: >> >>> >>> On Feb 5, 2005, at 7:45 AM, Ronald Oussoren wrote: >>> >>>> >>>> On 5-feb-05, at 15:22, Bob Ippolito wrote: >>>> >>>>>> >>>>> >>>>> int -> float doesn't lose precision, float -> complex doesn't lose >>>>> precision, int -> decimal doesn't lose precision. float -> >>>>> decimal *does* lose precision. It's already inconsistent with >>>>> every similar type of math in Python. >>>> >>>> The problem is not that float -> decimal would loose precission >>>> (although it sometimes does), but that float is a base-2 floating >>>> point number while decimal is base-10. Because float is base-2 it >>>> cannot exactly represent some numbers, which confuses people at >>>> first. E.g. repr(1.1) == '1.1000000000000001' on most systems. What >>>> would NSDecimalNumber(1.1) return? The same as >>>> NSDecimalNumber("1.1") or the same as >>>> NSDecimalNumber("1.1000000000000001")? The first is incorrect, the >>>> seconds is likely to confuse some people. >>> >>> Foundation handles this correctly actually, it rounds as needed >>> when converting to NSDecimalNumber, such that >>> NSDecimalNumber("1.1")==NSDecimalNumber(1.1). >>> >>> NSDecimalNumber(1.1111111111111115) may not be equal to >>> NSDecimalNumber("1.1111111111111115") but that's a pretty standard >>> failure: >>> >>> for (double x=0;x<1.0;x+=0.1) will run eleven times. >> >> Well, these "standard failures" are part of the major reason why >> Python's decimal does not support this feature. We could diverge >> from the Python Way in our implementation of Foundation.NSDecimal, >> but I am -0 for this change and will defer this decision to Ronald. >> Good luck convincing him that NSDecimal should behave differently >> than Python's decimal type. > > > It's not really so much that I want to diverge as much as I want to > write a framework I can load to override the default behavior so I > don't have to patch the release manually each time. > > Anyways, the first step is probably convincing Ronald that > NSDecimalNumber should not be bridged for the same reasons that > NSNumber isn't in 1.3. (If it still is, its hard to tell). > > Then, since Ronald is already thinking about writing OC_Number, > perhaps I can get him to add a hook somewhere. The hooks are already there.. they're just not public. You can import objc._convenience and modify the dictionaries that control what convenience methods are added. The details of how that works for number types is going to change a bit soon because I just noticed that the way it tests is pretty weak... but the technique would still work. >>> both seem to work, but I can't figure out how you're doing it in >>> _convenience.py. >> >> This is the B.__radd__(A) case. >> >> The way that _convenience.py works for numbers (in svn trunk) is the >> following: >> >> (1) If a NSNumber is involved, coerce it into a convenient type. >> This is either int, long, float, or Foundation.NSDecimal. >> (2) Perform the operation using the methods that Python normally uses >> (3) Coerce the result back into a NSNumber subclass (this is done >> just by making the value cross the bridge). > > Ok, so __radd___ is called if __add__ isn't implemented. But __add__ > is implemented for NSNumber, so: > > 1.0 + NSDecimalNumber(1) > > would work, because it would call my implementation of > NSDecimalNumber.__radd__() > > but: > > NSNumber(1.0) + NSDecimalNumber(1) would call NSNumber's > implementation instead because it also has __add__ implemented. Ah, > but this (http://www.python.org/doc/2.4/ref/coercion-rules.html) says: > "if the left operand is an instance of a built-in type or a new-style > class, and the right operand is an instance of a proper subclass of > that type or class, the right operand's __rop__() method is tried > before the left operand's __op__() method." Presumably python knows > that NSNumber is a superclass of NSDecimalNumber, so it will work. > > So the NSDecimalNumber equivalent to _num_to_python(v): in > _convenince.y would be much shorter: > > def _num_to_python(v): > """ > Magic method that converts NSNumber values to Python, see > <CoreFoundation/CFNumber.h> for the magic numbers > """ > if isinstance(v, NSDecimalNumber): > return _numberForDecimal(v) > else > return Decimal(v.__str__()) > > That is, it promotes all the numbers to python.decimal values before > doing the operation. > > So I could write all this directly in Python, such that > > import NSDecimalNumberConvenience > > would add the necessary methods I need to NSDecimalNumber. I suppose > it could be an optional part of PyObjC as well. > > So that would solve the first of my problems, which is that doing math > with NSDecimalNumbers in python was too confusing since sometimes it > worked and sometimes it didn't. > > The second problem, which is that I would like to be able to > conveniently do objc.setValue(1) when setValue() requires an > NSDecimalNumber would require breaking the standard behavior of the > bridge so that it built NSDecimalNumbers instead of NSNumbers, but > I'll have to wait to see what happens with Ronald's OC_NSNumber before > I look at that. you can currently do: import objc._convenience objc._convenience._num_to_python = your_implementation but since it's private we might break your code in a future revision. -bob |
From: Pierce T.W. I. <pi...@ma...> - 2005-02-05 21:40:51
|
>> >> >> It's not really so much that I want to diverge as much as I want to >> write a framework I can load to override the default behavior so I >> don't have to patch the release manually each time. >> >> Anyways, the first step is probably convincing Ronald that >> NSDecimalNumber should not be bridged for the same reasons that >> NSNumber isn't in 1.3. (If it still is, its hard to tell). >> >> Then, since Ronald is already thinking about writing OC_Number, >> perhaps I can get him to add a hook somewhere. > > The hooks are already there.. they're just not public. You can import > objc._convenience and modify the dictionaries that control what > convenience methods are added. The details of how that works for > number types is going to change a bit soon because I just noticed that > the way it tests is pretty weak... but the technique would still work. > > you can currently do: > import objc._convenience > objc._convenience._num_to_python = your_implementation > > but since it's private we might break your code in a future revision. > > -bob > Er, for problem one. And it actually need not be public, since I don't have to replace the NSNumber convenience routines, just add my own for NSDecimalNumber and since NSDecimalNumber is a subclass of NSNumber, python should use its routines instead of NSNumber. So I can crib from the code, but it shouldn't matter if it changes. For problem 2 (my desire to always pass back an NSDecimalNumber instead of an NSNumber), lines 129:144 in OC_PythonObject.m will have to change since I want to build NSDecimalNumbers instead of NSNumbers in wrapPyObject:. Pierce |
From: Bob I. <bo...@re...> - 2005-02-06 01:46:28
|
On Feb 5, 2005, at 4:40 PM, Pierce T.Wetter III wrote: >>> >>> >>> It's not really so much that I want to diverge as much as I want to >>> write a framework I can load to override the default behavior so I >>> don't have to patch the release manually each time. >>> >>> Anyways, the first step is probably convincing Ronald that >>> NSDecimalNumber should not be bridged for the same reasons that >>> NSNumber isn't in 1.3. (If it still is, its hard to tell). >>> >>> Then, since Ronald is already thinking about writing OC_Number, >>> perhaps I can get him to add a hook somewhere. >> >> The hooks are already there.. they're just not public. You can >> import objc._convenience and modify the dictionaries that control >> what convenience methods are added. The details of how that works >> for number types is going to change a bit soon because I just noticed >> that the way it tests is pretty weak... but the technique would still >> work. > >> >> you can currently do: >> import objc._convenience >> objc._convenience._num_to_python = your_implementation >> >> but since it's private we might break your code in a future revision. >> >> -bob >> > > > Er, for problem one. And it actually need not be public, since I > don't have to replace the NSNumber convenience routines, just add my > own for NSDecimalNumber and since NSDecimalNumber is a subclass of > NSNumber, python should use its routines instead of NSNumber. So I can > crib from the code, but it shouldn't matter if it changes. Currently, the convenience method application uses a dictionary. Since you're trying to *replace* existing additions, the result would be undefined if you used a more specific selector. You would need to put the functionality in the existing NSNumber conversion routines. > For problem 2 (my desire to always pass back an NSDecimalNumber > instead of an NSNumber), lines 129:144 in OC_PythonObject.m will have > to change since I want to build NSDecimalNumbers instead of NSNumbers > in wrapPyObject:. You could simply replace the implementation of objc._convenience.number_wrap to change that behavior. number_wrap is called on the result of any operation involving NSNumber. -bob |
From: Pierce T.W. I. <pi...@ma...> - 2005-02-06 02:19:09
|
On Feb 5, 2005, at 6:46 PM, Bob Ippolito wrote: > > On Feb 5, 2005, at 4:40 PM, Pierce T.Wetter III wrote: > >>>> >>>> >>>> It's not really so much that I want to diverge as much as I want >>>> to write a framework I can load to override the default behavior so >>>> I don't have to patch the release manually each time. >>>> >>>> Anyways, the first step is probably convincing Ronald that >>>> NSDecimalNumber should not be bridged for the same reasons that >>>> NSNumber isn't in 1.3. (If it still is, its hard to tell). >>>> >>>> Then, since Ronald is already thinking about writing OC_Number, >>>> perhaps I can get him to add a hook somewhere. >>> >>> The hooks are already there.. they're just not public. You can >>> import objc._convenience and modify the dictionaries that control >>> what convenience methods are added. The details of how that works >>> for number types is going to change a bit soon because I just >>> noticed that the way it tests is pretty weak... but the technique >>> would still work. >> >>> >>> you can currently do: >>> import objc._convenience >>> objc._convenience._num_to_python = your_implementation >>> >>> but since it's private we might break your code in a future revision. >>> >>> -bob >>> >> >> >> Er, for problem one. And it actually need not be public, since I >> don't have to replace the NSNumber convenience routines, just add my >> own for NSDecimalNumber and since NSDecimalNumber is a subclass of >> NSNumber, python should use its routines instead of NSNumber. So I >> can crib from the code, but it shouldn't matter if it changes. > > Currently, the convenience method application uses a dictionary. > Since you're trying to *replace* existing additions, the result would > be undefined if you used a more specific selector. You would need to > put the functionality in the existing NSNumber conversion routines. According to my understanding of how __add__ and __radd__ works, since NSDecimalNumber is a subclass of NSNumber, python will call its implementation of __add__ or __radd__ before any implementation attached to NSNumbers. So any addition N + DN DN + N Will call my version of __add__ or __radd__ first, which will do the necessary type promotion. :-) Or are you saying that won't work? > >> For problem 2 (my desire to always pass back an NSDecimalNumber >> instead of an NSNumber), lines 129:144 in OC_PythonObject.m will have >> to change since I want to build NSDecimalNumbers instead of NSNumbers >> in wrapPyObject:. > > You could simply replace the implementation of > objc._convenience.number_wrap to change that behavior. number_wrap is > called on the result of any operation involving NSNumber. > This is for converting python-typed objects to NSDecimalNumber. The convenience routines won't enter into that, because if I make my above changes, anytime a python or NSNumber object touches an NSDecimalNumber, it will get promoted to an NSDecimalNumber. So its only the case where I want to do: objc.setValue(1) And have the bridge autoconvert the python.int(1) into NSDecimalNumber(1) for me that I need to change. Right now that's in OC_PythonObject.m, but Robert is making noises that he thinks that python.int(1) shouldn't become an NSNumber, but rather a wrapped python object so that the object doesn't change back and forth when it crosses the boundary. So I'll have to see how he does that. Pierce |
From: Bob I. <bo...@re...> - 2005-02-06 02:35:47
|
On Feb 5, 2005, at 9:18 PM, Pierce T.Wetter III wrote: > > On Feb 5, 2005, at 6:46 PM, Bob Ippolito wrote: > >> >> On Feb 5, 2005, at 4:40 PM, Pierce T.Wetter III wrote: >> >>>>> >>>>> >>>>> It's not really so much that I want to diverge as much as I want >>>>> to write a framework I can load to override the default behavior >>>>> so I don't have to patch the release manually each time. >>>>> >>>>> Anyways, the first step is probably convincing Ronald that >>>>> NSDecimalNumber should not be bridged for the same reasons that >>>>> NSNumber isn't in 1.3. (If it still is, its hard to tell). >>>>> >>>>> Then, since Ronald is already thinking about writing OC_Number, >>>>> perhaps I can get him to add a hook somewhere. >>>> >>>> The hooks are already there.. they're just not public. You can >>>> import objc._convenience and modify the dictionaries that control >>>> what convenience methods are added. The details of how that works >>>> for number types is going to change a bit soon because I just >>>> noticed that the way it tests is pretty weak... but the technique >>>> would still work. >>> >>>> >>>> you can currently do: >>>> import objc._convenience >>>> objc._convenience._num_to_python = your_implementation >>>> >>>> but since it's private we might break your code in a future >>>> revision. >>>> >>>> -bob >>>> >>> >>> >>> Er, for problem one. And it actually need not be public, since I >>> don't have to replace the NSNumber convenience routines, just add my >>> own for NSDecimalNumber and since NSDecimalNumber is a subclass of >>> NSNumber, python should use its routines instead of NSNumber. So I >>> can crib from the code, but it shouldn't matter if it changes. >> >> Currently, the convenience method application uses a dictionary. >> Since you're trying to *replace* existing additions, the result would >> be undefined if you used a more specific selector. You would need to >> put the functionality in the existing NSNumber conversion routines. > > According to my understanding of how __add__ and __radd__ works, > since NSDecimalNumber is a subclass of NSNumber, python will call its > implementation of __add__ or __radd__ before any implementation > attached to NSNumbers. So any addition > > N + DN > DN + N > > Will call my version of __add__ or __radd__ first, which will do the > necessary type promotion. :-) > > Or are you saying that won't work? If you modify the NSDecimalNumber class after the fact, then that would work. However, the existing convenience method adding code has no way to specialize a particular Python addition. It's the same problem that you have when you define multiple categories that define the same methods on the same class. >>> For problem 2 (my desire to always pass back an NSDecimalNumber >>> instead of an NSNumber), lines 129:144 in OC_PythonObject.m will >>> have to change since I want to build NSDecimalNumbers instead of >>> NSNumbers in wrapPyObject:. >> >> You could simply replace the implementation of >> objc._convenience.number_wrap to change that behavior. number_wrap >> is called on the result of any operation involving NSNumber. >> > > This is for converting python-typed objects to NSDecimalNumber. The > convenience routines won't enter into that, because if I make my above > changes, anytime a python or NSNumber object touches an > NSDecimalNumber, it will get promoted to an NSDecimalNumber. So its > only the case where I want to do: > > objc.setValue(1) Why would you want to do this? > And have the bridge autoconvert the python.int(1) into > NSDecimalNumber(1) for me that I need to change. Right now that's in > OC_PythonObject.m, but Robert is making noises that he thinks that > python.int(1) shouldn't become an NSNumber, but rather a wrapped > python object so that the object doesn't change back and forth when it > crosses the boundary. > > So I'll have to see how he does that. He would do that by making it part of the NSNumber class cluster, but I'm not so sure it would be a good idea to make it a subclass of NSDecimalNumber. -bob |
From: Pierce T.W. I. <pi...@ma...> - 2005-02-06 03:03:02
|
>> According to my understanding of how __add__ and __radd__ works, >> since NSDecimalNumber is a subclass of NSNumber, python will call its >> implementation of __add__ or __radd__ before any implementation >> attached to NSNumbers. So any addition >> >> N + DN >> DN + N >> >> Will call my version of __add__ or __radd__ first, which will do >> the necessary type promotion. :-) >> >> Or are you saying that won't work? > > If you modify the NSDecimalNumber class after the fact, then that > would work. However, the existing convenience method adding code has > no way to specialize a particular Python addition. It's the same > problem that you have when you define multiple categories that define > the same methods on the same class. Hmmm... I'll have to trace though that code to understand what you just said. > >>>> For problem 2 (my desire to always pass back an NSDecimalNumber >>>> instead of an NSNumber), lines 129:144 in OC_PythonObject.m will >>>> have to change since I want to build NSDecimalNumbers instead of >>>> NSNumbers in wrapPyObject:. >>> >>> You could simply replace the implementation of >>> objc._convenience.number_wrap to change that behavior. number_wrap >>> is called on the result of any operation involving NSNumber. >>> >> >> This is for converting python-typed objects to NSDecimalNumber. The >> convenience routines won't enter into that, because if I make my >> above changes, anytime a python or NSNumber object touches an >> NSDecimalNumber, it will get promoted to an NSDecimalNumber. So its >> only the case where I want to do: >> >> objc.setValue(1) > > Why would you want to do this? Because I use PyObjC for poking around with our object frameworks and writing simple tools, and typing: objc.setValue(Foundation.NSDecimalNumber.numberWithInt(1)) Is really tedious. Even more tedious is something like this: for i in xrange(1,10): objc.setValue_(i*i) That is, if I do math with regular python variables, I have to remember to convert them to NSDecimalNumbers before passing them back. Granted, I have to do this in objc too, but for some reason it comes up less there. > >> And have the bridge autoconvert the python.int(1) into >> NSDecimalNumber(1) for me that I need to change. Right now that's in >> OC_PythonObject.m, but Robert is making noises that he thinks that >> python.int(1) shouldn't become an NSNumber, but rather a wrapped >> python object so that the object doesn't change back and forth when >> it crosses the boundary. >> >> So I'll have to see how he does that. > > He would do that by making it part of the NSNumber class cluster, but > I'm not so sure it would be a good idea to make it a subclass of > NSDecimalNumber. For no one but me. :-) But maybe I can get the wrapped python number to play nice with NSDecimalNumber, which is all I really want. Pierce |
From: Bob I. <bo...@re...> - 2005-02-06 03:18:48
|
On Feb 5, 2005, at 10:02 PM, Pierce T.Wetter III wrote: >>> According to my understanding of how __add__ and __radd__ works, >>> since NSDecimalNumber is a subclass of NSNumber, python will call >>> its implementation of __add__ or __radd__ before any implementation >>> attached to NSNumbers. So any addition >>> >>> N + DN >>> DN + N >>> >>> Will call my version of __add__ or __radd__ first, which will do >>> the necessary type promotion. :-) >>> >>> Or are you saying that won't work? >> >> If you modify the NSDecimalNumber class after the fact, then that >> would work. However, the existing convenience method adding code has >> no way to specialize a particular Python addition. It's the same >> problem that you have when you define multiple categories that define >> the same methods on the same class. > > Hmmm... I'll have to trace though that code to understand what you > just said. This will work: import Foundation def NSDecimal__radd__(self, a, b): .... NSDecimal.__radd__ = NSDecimal__radd__ Messing with objc._convenience will NOT work, unless you replace the convenience functions for *all* NSNumber, somehow before the NSDecimalNumber class gets created.. so it's probably just best to do it in the above way. >>>>> For problem 2 (my desire to always pass back an NSDecimalNumber >>>>> instead of an NSNumber), lines 129:144 in OC_PythonObject.m will >>>>> have to change since I want to build NSDecimalNumbers instead of >>>>> NSNumbers in wrapPyObject:. >>>> >>>> You could simply replace the implementation of >>>> objc._convenience.number_wrap to change that behavior. number_wrap >>>> is called on the result of any operation involving NSNumber. >>>> >>> >>> This is for converting python-typed objects to NSDecimalNumber. >>> The convenience routines won't enter into that, because if I make my >>> above changes, anytime a python or NSNumber object touches an >>> NSDecimalNumber, it will get promoted to an NSDecimalNumber. So its >>> only the case where I want to do: >>> >>> objc.setValue(1) >> >> Why would you want to do this? > > Because I use PyObjC for poking around with our object frameworks and > writing simple tools, and typing: > > objc.setValue(Foundation.NSDecimalNumber.numberWithInt(1)) > > Is really tedious. Even more tedious is something like this: > > for i in xrange(1,10): > objc.setValue_(i*i) > > That is, if I do math with regular python variables, I have to > remember to convert them > to NSDecimalNumbers before passing them back. > > Granted, I have to do this in objc too, but for some reason it comes > up less there. Because there is no tempting easy way to express it :) >>> And have the bridge autoconvert the python.int(1) into >>> NSDecimalNumber(1) for me that I need to change. Right now that's in >>> OC_PythonObject.m, but Robert is making noises that he thinks that >>> python.int(1) shouldn't become an NSNumber, but rather a wrapped >>> python object so that the object doesn't change back and forth when >>> it crosses the boundary. >>> >>> So I'll have to see how he does that. >> >> He would do that by making it part of the NSNumber class cluster, but >> I'm not so sure it would be a good idea to make it a subclass of >> NSDecimalNumber. > > For no one but me. :-) But maybe I can get the wrapped python number > to play nice with NSDecimalNumber, which is all I really want. I think it would probably be good idea to make the OC_PythonObject wrapping more hackable, to the point where you can say int, long, and float instances should be coerced in a particular way. Right now it is actually quite hackable (private API, of course), but only for "default" cases that are not part of that "fast path". -bob |
From: Ronald O. <ron...@ma...> - 2005-02-06 06:38:38
|
On 5-feb-05, at 20:05, Bob Ippolito wrote: > > On Feb 5, 2005, at 1:02 PM, Pierce T.Wetter III wrote: > >> >> On Feb 5, 2005, at 7:45 AM, Ronald Oussoren wrote: >> >>> >>> On 5-feb-05, at 15:22, Bob Ippolito wrote: >>> >>>>> >>>> >>>> int -> float doesn't lose precision, float -> complex doesn't lose >>>> precision, int -> decimal doesn't lose precision. float -> decimal >>>> *does* lose precision. It's already inconsistent with every >>>> similar type of math in Python. >>> >>> The problem is not that float -> decimal would loose precission >>> (although it sometimes does), but that float is a base-2 floating >>> point number while decimal is base-10. Because float is base-2 it >>> cannot exactly represent some numbers, which confuses people at >>> first. E.g. repr(1.1) == '1.1000000000000001' on most systems. What >>> would NSDecimalNumber(1.1) return? The same as >>> NSDecimalNumber("1.1") or the same as >>> NSDecimalNumber("1.1000000000000001")? The first is incorrect, the >>> seconds is likely to confuse some people. >> >> Foundation handles this correctly actually, it rounds as needed >> when converting to NSDecimalNumber, such that >> NSDecimalNumber("1.1")==NSDecimalNumber(1.1). >> >> NSDecimalNumber(1.1111111111111115) may not be equal to >> NSDecimalNumber("1.1111111111111115") but that's a pretty standard >> failure: >> >> for (double x=0;x<1.0;x+=0.1) will run eleven times. > > Well, these "standard failures" are part of the major reason why > Python's decimal does not support this feature. We could diverge from > the Python Way in our implementation of Foundation.NSDecimal, but I am > -0 for this change and will defer this decision to Ronald. Good luck > convincing him that NSDecimal should behave differently than Python's > decimal type. Don't bother trying. I'm against changing this. Ronald |