Re: [Pyobjc-dev] NSNumbers and NSDecimalNumbers
Brought to you by:
ronaldoussoren
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 |