!=========================================================================
! Copyright (C) VMware, Inc. 1986-2011.  All Rights Reserved.
!
! $Id: scaleddecimal.gs,v 1.12 2008-01-09 22:50:14 stever Exp $
!
! Superclass Hierarchy:
!   ScaledDecimal, Number, Magnitude, Object.
!
!=========================================================================
expectvalue %String
run
^ Number _newKernelSubclass: 'ScaledDecimal'
        instVarNames: #( 'numerator' 'denominator' 'scale' )
        classVars: #()
        classInstVars: #()
        poolDictionaries: #[]
        inDictionary: Globals
        constraints: #[ #[ #numerator, Integer],
		        #[ #denominator, Integer],
			#[ #scale, SmallInteger] ]
        instancesInvariant: false
        isModifiable: false
        reservedOop: 805
%

removeallmethods ScaledDecimal
removeallclassmethods ScaledDecimal

category: 'For Documentation Installation only'
classmethod: ScaledDecimal
installDocumentation

| doc txt |
doc := GsClassDocumentation newForClass: self.

txt := (GsDocText new) details:
'ScaledDecimal stores numerical values as a rational number, represented by a
 numerator and denominator that are Integers.  Since the numerator and
 denominator can be carried to arbitrary precision, ScaledDecimal can represent
 any rational number without loss of precision.  It also calculates based upon
 fractional arithmetic, and thus produces numerical results without loss of
 precision.

 ScaledDecimal also provides for automatic rounding to a fixed precision after
 the decimal point when converting to and from other types, such as String.

 One useful application of this kind of number is for financial instruments,
 which are always rounded off, but usually need more digits than a floating
 number can accurately express in order not to lose precision during
 computation.' .
doc documentClassWith: txt.

txt := (GsDocText new) details:
'An Integer that represents the numerator of the rational value of the
 instance.' .
doc documentInstVar: #numerator with: txt.

txt := (GsDocText new) details:
'A positive Integer that represents the denominator of the rational value 
 of the instance.' .
doc documentInstVar: #denominator with: txt.

txt := (GsDocText new) details:
'A non-negative Integer that represents the number of decimal places of
 precision to the right of the decimal point.' .
doc documentInstVar: #scale with: txt.

self description: doc.
%

! gemstone64, corrected comments about allowed argument kinds.
category: 'Instance Creation'
classmethod: ScaledDecimal
numerator: numerator denominator: denominator scale: scale

"Returns an instance of ScaledDecimal with the given numerator and denominator.
 If that ScaledDecimal can be reduced, this method returns the corresponding
 Integer instead. 

 The arguments numerator and denominator must be Integers.
 The argument  scale  must be a SmallInteger . " 

(denominator = 0) ifTrue: [ ^ numerator _errorDivideByZero ].

^ self basicNew _numerator: numerator denominator: denominator scale: scale
%

category: 'Private'
method: ScaledDecimal
_numerator: num denominator: den scale: sc

"Private.  Assigns the receiver's instance variables, reduces it, and makes it
 invariant."

num _isInteger ifTrue:[   "gemstone64, explicit constraint enforcement"
  den _isInteger ifTrue:[
    sc _isSmallInteger ifTrue:[
      numerator := num.
      denominator := den.
      sc < 0 ifTrue:[ scale := 0 ]
      	    ifFalse:[ scale := sc ].
      ^ self _reduce immediateInvariant
    ] ifFalse:[
      System signal: 2107
       args: #[ self , sc, SmallInteger, sc class]
       signalDictionary: (Globals at: #GemStoneError) .
    ].
  ] ifFalse: [
    System signal: 2107
       args: #[ self , den, Integer, den class]
       signalDictionary: (Globals at: #GemStoneError) .
  ].
] ifFalse:[ 
  System signal: 2107
       args: #[ self , num, Integer, num class]
       signalDictionary: (Globals at: #GemStoneError) .
].
self _uncontinuableError .
%

category: 'Private'
method: ScaledDecimal
_reduce

"Private.  Reduces the receiver."

| gcd |
"now reduce it"
numerator = 0 ifTrue:[ 
  denominator := 1. 
  ^ self 
  ].
denominator < 0 ifTrue:[  "denominator is always positive "
  numerator := numerator negated .
  denominator := denominator negated
  ].
gcd := numerator gcd: denominator.
numerator := numerator // gcd.
denominator := denominator // gcd.
^ self 
%

category: 'Accessing'
method: ScaledDecimal
at: anIndex

"Disallowed."

self shouldNotImplement: #at:
%

category: 'Accessing'
method: ScaledDecimal
at: anIndex put: aNumber

"Disallowed.  You may not change the value of a ScaledDecimal."

self shouldNotImplement: #at:put:
%

category: 'Accessing'
method: ScaledDecimal
denominator

"Returns the denominator of the receiver."

^denominator
%

category: 'Testing'
method: ScaledDecimal
isZero

"Returns true if the receiver is zero."

^ numerator = 0 .
%

category: 'Accessing'
method: ScaledDecimal
instVarAt: anIndex put: aValue

"Disallowed.  You may not change the value of a ScaledDecimal."

self shouldNotImplement: #instVarAt:put:
%

category: 'Accessing'
method: ScaledDecimal
numerator

"Returns the numerator of the receiver."

^numerator
%

category: 'Accessing'
method: ScaledDecimal
scale

"Returns the scale of the receiver."

^scale
%

category: 'Accessing'
method: ScaledDecimal
size: anInteger

"Disallowed.  You may not change the size of a ScaledDecimal."

self shouldNotImplement: #size:
%

category: 'Updating'
method: ScaledDecimal
reduced

"Returns a ScaledDecimal determined by finding the greatest common
 divisor of the numerator and denominator of the receiver."

"Reduce a fraction to its smallest terms."

| gcd numer denom |

(numerator = 0) ifTrue:[ 
  denominator == 1 ifFalse:[
    denominator := 1 .
    ].
  ^ self
  ].

gcd := numerator gcd: denominator.
numer := numerator // gcd.
denom := denominator // gcd.
(numer = numerator _and:[ denom = denominator]) ifTrue:[ ^ self ].

^ ScaledDecimal numerator: numer denominator: denom scale: scale.
%

category: 'Private'
method: ScaledDecimal
_scale: aScale

"Private."

scale := aScale
%

category: 'Formatting'
method: ScaledDecimal
withScale: newScale

"Returns the receiver with the new scale."

scale == newScale ifTrue:[ ^ self ].
^ (self copy _scale: newScale) immediateInvariant
%

category: 'Formatting'
method: ScaledDecimal
asString

"Returns a String of the form '123.56 for a number with scale = 2."

| x num aString wholePart fraction |

aString := String new.
scale == nil ifTrue:[ ^ '(uninitialized ScaledDecimal)' "fix bug 13190"].
x := 10 raisedToInteger: scale .
num := ((numerator * x) + (denominator quo: 2)) // denominator.
(numerator < 0) ifTrue: [
    aString add: $-.
    num := num negated.
    ].

wholePart := num // x.
fraction := num \\ x.

aString add: (wholePart asString).
aString add: (Locale decimalPoint). "fix 36666"
scale timesRepeat: [
  fraction := fraction * 10.
  aString add: (fraction // x) asString.
  fraction := fraction \\ x.
  ].

^ aString.
%

category: 'Private'
classmethod: ScaledDecimal
_fromString: aString

"Private.  Given aString such as '34.23', returns a non-reduced ScaledDecimal.
 Returns nil if there is a format error in aString"

<primitive: 465>
self _primitiveFailed: #_fromString: .
%

category: 'Instance Creation'
classmethod: ScaledDecimal
fromString: aString

"Given aString such as '34.23', returns an instance of ScaledDecimal with
 appropriate numerator and denominator, and with scale equal to the number 
 of digits to the right of the decimal point.  Characters in aString after 
 the first character which is neither a digit or decimal point are ignored."

| result | 
result := self _fromString: aString .
result == nil ifTrue:[ self _errIncorrectFormat: aString ].
^ result _reduce immediateInvariant
%

category: 'Converting'
method: ScaledDecimal
asFloat

"Returns an instance of SmallDouble or Float that has the value of the receiver."

^ numerator asFloat / denominator asFloat
%

category: 'Converting'
method: ScaledDecimal
asDecimalFloat

"Returns an instance of DecimalFloat that has the value of the receiver."

^ numerator asDecimalFloat / denominator asDecimalFloat
%

category: 'Converting'
method: ScaledDecimal
asScaledDecimal

"Returns a ScaledDecimal representing the receiver."

^self
%

category: 'Converting'
method: ScaledDecimal
_coerce: aNumber

"Reimplemented from Number."

^aNumber asScaledDecimal: scale
%

category: 'Comparing'
method: ScaledDecimal
< aScaledDecimal

"Returns true if the receiver is less than aScaledDecimal; returns false
 otherwise."

(aScaledDecimal _getKind > 4) 
  ifTrue: [ 
    "NaN"
    ^ false 
  ].
(aScaledDecimal _class == ScaledDecimal)
  ifTrue: [
    (aScaledDecimal numerator = 0)
      ifTrue: [^numerator < 0]
      ifFalse: [^self - aScaledDecimal < 0]
  ]
  ifFalse: [
    (aScaledDecimal isKindOf: Fraction) 
      ifTrue: [
        ((aScaledDecimal numerator == nil) _or: [aScaledDecimal denominator == nil])
           ifTrue: [ ^false ].
      ].
    ^self _retry: #< coercing: aScaledDecimal
   ]
%

category: 'Comparing'
method: ScaledDecimal
>= aMagnitude

"Returns true if the receiver is greater than or equal to aMagnitude;
 returns false otherwise."

"Reimplemented from Magnitude to handle NaNs correctly."

^ aMagnitude <= self
%

category: 'Comparing'
method: ScaledDecimal
<= aScaledDecimal

"Returns true if the receiver is less than or equal to aScaledDecimal;
 returns false otherwise."

(aScaledDecimal _getKind > 4) 
  ifTrue: [ 
    "NaN"
    ^ false 
  ].
(aScaledDecimal isKindOf: Fraction) 
  ifTrue: [
    ((aScaledDecimal numerator == nil) _or: [aScaledDecimal denominator == nil])
       ifTrue: [ ^false ].
  ].
^(self > aScaledDecimal) not
%

category: 'Comparing'
method: ScaledDecimal
= aScaledDecimal

"Returns true if the receiver is equal to aScaledDecimal; returns false
 otherwise."

self == aScaledDecimal ifTrue:[ ^ true ].
(aScaledDecimal _class == ScaledDecimal)
  ifTrue: [
    (aScaledDecimal numerator = 0)
      ifTrue: [^numerator = 0]
      ifFalse: [
        ^aScaledDecimal numerator = numerator
        _and: [aScaledDecimal denominator = denominator]
      ]
  ]
  ifFalse: [^self _retry: #= coercing: aScaledDecimal]
%

! hash inherited from Number

category: 'Truncation and Rounding'
method: ScaledDecimal
truncated

"Returns the integer that is closest to the receiver, on the same side
 of the receiver as zero is located."

^numerator quo: denominator
%

category: 'Arithmetic'
method: ScaledDecimal
* aScaledDecimal

"Returns the result of multiplying the receiver by aScaledDecimal."

(aScaledDecimal _class == ScaledDecimal)
  ifTrue: [^(ScaledDecimal
           numerator: numerator * aScaledDecimal numerator
           denominator: denominator * aScaledDecimal denominator
           scale: scale)]
  ifFalse: [^self _retry: #* coercing: aScaledDecimal]
%

category: 'Arithmetic'
method: ScaledDecimal
+ aScaledDecimal

"Returns the sum of the receiver and aScaledDecimal."

| commonDenominator newNumerator |
(aScaledDecimal _class == ScaledDecimal)
   ifTrue:
      [(denominator = aScaledDecimal denominator)
         ifTrue: [^(ScaledDecimal
                  numerator: numerator + aScaledDecimal numerator
                  denominator: denominator
                  scale: scale)].
      commonDenominator := denominator lcm: aScaledDecimal denominator.
      newNumerator := numerator
                     * (commonDenominator quo: denominator)
                     + (aScaledDecimal numerator *
                        (commonDenominator quo: aScaledDecimal denominator)).
      ^(ScaledDecimal
         numerator: newNumerator
         denominator: commonDenominator
         scale: scale)]
   ifFalse: [^self _retry: #+ coercing: aScaledDecimal]
%

category: 'Arithmetic'
method: ScaledDecimal
- aScaledDecimal

"Returns the difference between the receiver and aScaledDecimal."

(aScaledDecimal _class == ScaledDecimal)
  ifTrue: [^self + aScaledDecimal negated]
  ifFalse: [^self _retry: #- coercing: aScaledDecimal]
%

category: 'Arithmetic'
method: ScaledDecimal
/ aScaledDecimal

"Returns the result of dividing the receiver by aScaledDecimal."

(aScaledDecimal _class == ScaledDecimal)
  ifTrue: [^self * aScaledDecimal reciprocal]
  ifFalse: [^self _retry: #/ coercing: aScaledDecimal]
%

category: 'Arithmetic'
method: ScaledDecimal
negated

"Returns a Number that is the negation of the receiver."

^ScaledDecimal numerator: numerator negated denominator: denominator 
		scale: scale
%

category: 'Arithmetic'
method: ScaledDecimal
reciprocal

"Returns the reciprocal of the receiver."

(numerator = 0) ifTrue: [ ^ self _errorDivideByZero].
^ScaledDecimal numerator: denominator denominator: numerator scale: scale
%

category: 'Storing and Loading'
classmethod: ScaledDecimal
loadFrom: passiveObj

"Reads from passiveObj the passive form of an object.  Converts the object to
 its active form by loading the information into a new instance of the receiver.
 Returns the new instance."

| inst num den scale |

passiveObj readNamedIV.
num := passiveObj ivValue.
passiveObj readNamedIV.
den := passiveObj ivValue.
passiveObj readNamedIV.
scale := passiveObj ivValue.
  
passiveObj skipNamedInstVars.

inst := self numerator: num denominator: den scale: scale.
passiveObj hasRead: inst.
^inst.
%

category: 'Storing and Loading'
method: ScaledDecimal
writeTo: passiveObj

"Converts the receiver to its passive form and writes that information on
 passiveObj."

"Reimplemented from Number since the receiver has a non-literal representation."

^super basicWriteTo: passiveObj
%

! deleted classmethod _generality

category: 'Private'
method: ScaledDecimal
_generality

"Returns an Integer representing the ordering of the receiver in
 the generality hierarchy."

^ 60
%

category: 'Testing'
method: ScaledDecimal
even

"(R) Returns true if the receiver is an even integer, false otherwise."

 denominator = 1 ifFalse: [ ^ false ].
 ^ numerator even
%

category: 'Testing'
method: ScaledDecimal
odd

"(R) Returns true if the receiver is an odd integer, false otherwise."

 denominator = 1 ifFalse: [ ^ false ].
 ^ numerator odd
%

category: 'Converting'
method: ScaledDecimal
asFraction

"(R) Returns a Fraction that represents the receiver."

^ Fraction numerator: numerator denominator: denominator.
%

