Had some financial calculations fail on me the other day because I didn't
realize the following is true: (ScaledDecimal fromString: '3.123') class = Float I sort of think that if I ask for a ScaledDecimal I should get one, or an error. Opinions? Regards, --Peter Goodall |
Peter Goodall wrote:
> (ScaledDecimal fromString: '3.123') class = Float > > I sort of think that if I ask for a ScaledDecimal I should get one, or an > error. I agree. I think the underlying problem is that the Dolphin facilities for converting Strings to numbers are implemented in terms of Number class>>readSmalltalkSyntaxFrom:, and that is just wrong for any application that happens not to be a Smalltalk compiler. I don't think that either the API that's provided, or the implementation thereof, are suitable for general purpose applications. This whole area is overdue for an overhaul, IMO. (Although I should say that I, at least, haven't often been affected by the problems -- and where I have been I've just switched to doing the number parsing myself) -- chris |
In reply to this post by Peter Goodall-4
Peter,
> I sort of think that if I ask for a ScaledDecimal I should get one, > or an error. > > Opinions? Personally I think it should be an error. You've asked for an instance of a given class and then passed it a String which doesn't contain a valid sequence of characters for an instance of that class. It's a bit like the discussion the other day about what "Float fromString: '.5'" should answer. Dolphin does seem a bit, umm, undecided about what to do in these situations though.... Integer fromString: '3.123' Throws an error (because . is not a valid character) Fraction fromString: '3.123' Answers an Fraction which is an approximation of the argument (fair enough) Fraction fromString: '3' Fraction fromString: '3.0' Both answer a SmallInteger (rather than the result of "Fraction numerator: 3(.0) denominator: 1(.0)") Float fromString: '3' Answers a Float (and not a SmallInteger as Fraction does) I'm not sure what the best solution is, especially as changing any of the existing behaviour may break existing code. One possibility might be making the #fromString: conversion in each class strict (if it isn't the correct format, or a permissible variant, then an error is raised) and a generic Number>>fromString: method that, as now, answers an instance of the appropriate subclass. You could then provide reasonable (?) #asXxxx methods in all Number subclasses that always answers (via double dispatch?) an instance of the required class. You could then use ANumberSubclass fromString: 'whatever' if you were sure of (or wanted to ensure) the correct format and (Number fromString: 'whatever') asANumberSubclass if you weren't The first would always give as good a conversion as possible but the second might be less exact e.g. "(Number fromString: '3.123') asScaledDecimal" will probably not equal "ScaledDecimal fromString: '3.123s'" because of the formers Float->Fraction->ScaledDecimal conversions. -- Ian Use the Reply-To address to contact me. Mail sent to the From address is ignored. |
In reply to this post by Peter Goodall-4
"Peter Goodall" <[hidden email]> wrote in message news:<3ff4cdab$0$18691$[hidden email]>...
> Had some financial calculations fail on me the other day because I didn't > realize the following is true: > > (ScaledDecimal fromString: '3.123') class = Float > > I sort of think that if I ask for a ScaledDecimal I should get one, or an > error. > > Opinions? Look at the comment in Number>>fromString:. It says "Should be separate fromDisplayString: version which converts from locale specific format (this method from Smalltalk format)" So it looks like you should use #fromDisplayString: here instead of #fromString: to get the results you want. Sadly, #fromDisplayString: is not implemented. :-( Happily, #fromDisplayString: shouldn't be too tough to implement. A starting point might be something like Number>>fromDisplayString: aString | num num2 | num := self fromString: aString. num2 := num. [ num2 := num perform: ('as', self name) asSymbol ] on: Error do: [ :e | ]. ^num2 This won't work for ScaledDecimals because there is no #asScaledDecimal method in Number (there is an asScaledDecimal: method), but this could be worked around (i.e. implement ScaledDecimal>>fromDisplayString:). Just a thought. |
In reply to this post by Ian Bartholomew-18
Thanks Ian,
"Ian Bartholomew" <[hidden email]> wrote in message news:bt3u36$31n51$[hidden email]... > Peter, > > > I sort of think that if I ask for a ScaledDecimal I should get one, > > or an error. > > > > Opinions? > > Personally I think it should be an error. You've asked for an instance > of a given class and then passed it a String which doesn't contain a > valid sequence of characters for an instance of that class. It's a bit > like the discussion the other day about what "Float fromString: '.5'" > should answer. Which is why I considered an error as a result. Though much like the '.5' and Float problem - the input String can be perfectly well represented as a ScaledDecimal. Noting that the 'scale' part of the ScaledDecimal does not affect the arithmetic operarations of the number, just its display. So a default scale for ScaledDecimal may be reasonable. I reckon a reasonable return from a reasonable request is a good thing. > > Dolphin does seem a bit, umm, undecided about what to do in these > situations though.... > > Integer fromString: '3.123' > Throws an error (because . is not a valid character) Its a valid number representation though. In a workspace 3.123 asInteger = 3 > > Fraction fromString: '3.123' > Answers an Fraction which is an approximation of the argument (fair > enough) A fraction can represent an terminating decimal perfectly. In fact, the Scaled Decimal is represented internally as a Fraction. (I always wanted a Fractional Arithmetic Coprocessor - but could never find one) > > Fraction fromString: '3' 3 asFraction --> 3 Fraction fromString: '3.0' 3.0 asFraction --> 3 ( 3/1) class = SmallInteger As I was going through this - I was thinking that the solution was for #fromString to do the same as the Compiler - may not be a complete answer though. > Both answer a SmallInteger (rather than the result of "Fraction > numerator: 3(.0) denominator: 1(.0)") > > Float fromString: '3' > Answers a Float (and not a SmallInteger as Fraction does) > > I'm not sure what the best solution is, especially as changing any of > the existing behaviour may break existing code. One possibility might > be making the #fromString: conversion in each class strict (if it isn't > the correct format, or a permissible variant, then an error is raised) > and a generic Number>>fromString: method that, as now, answers an > instance of the appropriate subclass. You could then provide reasonable > (?) #asXxxx methods in all Number subclasses that always answers (via > double dispatch?) an instance of the required class. You could then use > > ANumberSubclass fromString: 'whatever' > if you were sure of (or wanted to ensure) the correct format and > > (Number fromString: 'whatever') asANumberSubclass > if you weren't > > The first would always give as good a conversion as possible but the > second might be less exact e.g. "(Number fromString: '3.123') > asScaledDecimal" will probably not equal "ScaledDecimal fromString: > '3.123s'" because of the formers Float->Fraction->ScaledDecimal > conversions. > > -- > Ian > > Use the Reply-To address to contact me. > Mail sent to the From address is ignored. > |
Peter,
> Which is why I considered an error as a result. Though much like the > '.5' and Float problem - the input String can be perfectly well > represented as a ScaledDecimal. Noting that the 'scale' part of the > ScaledDecimal > does not affect the arithmetic operarations of the number, just its > display. So a default scale for ScaledDecimal may be reasonable. But if you are not going to worry about the scale factor (until you need to display the current value anyway) then you might as well use a Float or Fraction and avoid the extra complications of a SD. > I reckon a reasonable return from a reasonable request is a good > thing. Who is going to define "reasonable" though :-) is "(Fraction fromString: '3') class = SmallInteger" a reasonable return?. How about "Integer fromString: 'three'" being a reasonable request :-) I'm not just trying to be argumentative here, honestly, but I do think a slightly more structured approach for the various #fromString: methods in the _base_ image would have been better. Probably too late now though.... >> Integer fromString: '3.123' >> Throws an error (because . is not a valid character) > Its a valid number representation though. In a workspace > 3.123 asInteger = 3 Yes, but there are two conversions involved it that expression. The compiler does the initial (and completely valid) "Float fromString: '3.123'" and you then send asInteger to the resulting Float. It's equivalent to (Float fromString: '3.123') asInteger which would still be possible in the scheme I outlined yesterday. > A fraction can represent an terminating decimal perfectly. In fact, > the Scaled Decimal is represented internally as a Fraction. Yes, but if you do anything other than a String->Fraction conversion then you will run into trouble. For example 3.123 asFraction = (216085600644019/69191674878008) rather than the (rather more obvious) 3123/1000. Should you expect (3.123 asScaledDecimal: 3) = (ScaledDecimal fromString: '3.123s3') to answer true? > As I was going through this - I was thinking that the solution > was for #fromString to do the same as the Compiler - may not be a > complete answer though. I don't think there is a complete answer. I have to say though that, as Chris mentioned yesterday, I haven't really had any major (i.e. unsolvable) problems in this area - if the existing conversions aren't what I want then I just add a method that is.... -- Ian Use the Reply-To address to contact me. Mail sent to the From address is ignored. |
Free forum by Nabble | Edit this page |