David T. Lewis uploaded a new version of Kernel to project The Inbox:
http://source.squeak.org/inbox/Kernel-dtl.1166.mcz ==================== Summary ==================== Name: Kernel-dtl.1166 Author: dtl Time: 25 April 2018, 10:48:38.572237 pm UUID: 43d80609-fbc5-40a1-9d49-37d2c4f0b306 Ancestors: Kernel-dtl.1165 In calculation of asApproximateFraction, the digit count for floatPrecision should be an integer, so do not convert it with asFlloat. Allows the following to be resolved without infinite loop on comparison to Float infinity: ((FloatArray new: 1) at: 1 put: 1 / 3; at: 1) asApproximateFractionFloatPrecision: 10000 =============== Diff against Kernel-dtl.1165 =============== Item was changed: ----- Method: Float>>asApproximateFractionAtOrder:floatPrecision: (in category 'converting') ----- asApproximateFractionAtOrder: maxOrder floatPrecision: decimalPlaces "Answer a Fraction approximating the receiver. This conversion uses the continued fraction method to approximate a floating point number. If maxOrder is zero, use maximum order. Assume that I am a float with decimalPlaces of meaningful accuracy, regardless of the precision of my internal representation." | num1 denom1 num2 denom2 int frac newD temp order floatPrecision | num1 := self asInteger. "The first of two alternating numerators" denom1 := 1. "The first of two alternating denominators" num2 := 1. "The second numerator" denom2 := 0. "The second denominator--will update" int := num1. "The integer part of self" frac := self fractionPart. "The fractional part of self" order := maxOrder = 0 ifTrue: [-1] ifFalse: [maxOrder]. + floatPrecision := 10 raisedTo: decimalPlaces. - floatPrecision := (10 raisedTo: decimalPlaces) asFloat. [frac = 0 or: [order = 0] ] whileFalse: ["repeat while the fractional part is not zero and max order is not reached" order := order - 1. newD := 1.0 / frac. "Take reciprocal of the fractional part" int := newD asInteger. "get the integer part of this" frac := newD fractionPart. "and save the fractional part for next time" temp := num2. "Get old numerator and save it" num2 := num1. "Set second numerator to first" num1 := num1 * int + temp. "Update first numerator" temp := denom2. "Get old denominator and save it" denom2 := denom1. "Set second denominator to first" denom1 := int * denom1 + temp. "Update first denominator" floatPrecision < denom1 ifTrue: ["Is ratio past float precision? If so, pick which of the two ratios to use" num2 = 0.0 ifTrue: ["Is second denominator 0?" ^ Fraction numerator: num1 denominator: denom1]. ^ Fraction numerator: num2 denominator: denom2]]. "If fractional part is zero, return the first ratio" denom1 = 1 ifTrue: ["Am I really an Integer?" ^ num1 "Yes, return Integer result"] ifFalse: ["Otherwise return Fraction result" ^ Fraction numerator: num1 denominator: denom1]! |
Here is the version that i use in VW (SYSBUG-FloatConversion in cincom public store) Note that I use self asFraction (asTrueFraction) and then perform all operations in Integer arithmetic.asApproximateFraction "Answer a Rational number--Integer or Fraction--representing the receiver. This conversion uses the continued fraction method to approximate a floating point number." | num1 denom1 num2 denom2 int frac newD temp limit | num1 := self asFraction. "use exact arithmetic" frac := num1 fractionPart. "The fractional part of self" int := num1 truncated. "The integer part of self" num1 := int. "The first of two alternating numerators" denom1 := 1. "The first of two alternating denominators" num2 := 1. "The second numerator" denom2 := 0. "The second denominator--will update" limit := self class precision + 7 // 8 + 1. [frac = 0 or: [denom1 digitLength > limit or: [(self coerce: (Fraction numerator: num1 denominator: denom1)) = self]]] whileFalse: ["repeat while the fractional part is not zero" newD := frac reciprocal. "Take reciprocal of the fractional part" int := newD truncated. "get the integer part of this" frac := newD fractionPart. "and save the fractional part for next time" temp := num2. "Get old numerator and save it" num2 := num1. "Set second numerator to first" num1 := num1 * int + temp. "Update first numerator" temp := denom2. "Get old denominator and save it" denom2 := denom1. "Set second denominator to first" denom1 := int * denom1 + temp. "Update first denominator"]. "If fractional part is zero, return the first ratio" denom1 = 1 ifTrue: ["Am i really an Integer?" ^num1"Yes, return Integer result"] ifFalse: ["Otherwise return Fraction result" ^Fraction numerator: num1 denominator: denom1] For user-defined controlled precision like aimed by Eliot, we could use #decimalDigitLength and seed it by default with something like #decimalPrecision in Squeak. #decimalPrecision does not exist yet but could be (self class precision * 2 ln / 10 ln) rounded - or simply 15 or 16. Note that it's an absolute precision, not a relative precision which is used. den1 := den2 + ((den2 := den1) * int). 2018-04-26 4:48 GMT+02:00 <[hidden email]>: David T. Lewis uploaded a new version of Kernel to project The Inbox: |
Also note that in base 2, we know the unit of least precision So we could stop the loop when precision reaches half ulp, orbitLimit := 1 - self ulp exponent. 2018-04-26 9:45 GMT+02:00 Nicolas Cellier <[hidden email]>:
|
2018-04-26 9:53 GMT+02:00 Nicolas Cellier <[hidden email]>:
Err, I forgot to tell the aim: get a relative precision
|
Hmm, after thoughts, denominator length is a bit indirect... For a relative precision, the limit could be something like this: asApproximateFractionPrecision: limit
[frac = 0 or: [(Fraction numerator: num1 denominator: denom1)) - self) abs <= limit]]... asApproximateFractionRelativeDecimalPlaces: decimalPlaces asApproximateFraction 2018-04-26 9:54 GMT+02:00 Nicolas Cellier <[hidden email]>:
|
Free forum by Nabble | Edit this page |