note
	description: "[
		Efficient value type for handling money objects
			in dollars and cents, with precision 
			and safety in plus and minus operators.
		See end of class for specification.
	]"
	author: "JSO"
	date: "$Date$"
	revision: "$Revision$"

expanded class 
	MONEY

inherit
	COMPARABLE
		redefine
			out,
			is_equal
		end

	INTEGER_OVERFLOW
		redefine
			is_equal,
			out
		end

	DEBUG_OUTPUT
		rename
			debug_output as out
		undefine
			is_equal
		redefine
			out
		end

create 
	make,
	make_with_float,
	make_from_int,
	default_create

feature -- public

	cents: INTEGER_64

	amount: FLOAT
			-- float value of cents
		do
			Result := create {FLOAT}.make_from_real (cents / 100)
		ensure
				Result = create {FLOAT}.make_from_real (cents / 100)
		end
	
feature {NONE} -- Initialization

	make (a_dollars: INTEGER_64; a_cents: INTEGER_64)
			-- make a money object with dollars and cents
		do
			cents := a_dollars * 100 + a_cents
		ensure
				cents = a_dollars * 100 + a_cents
		end

	make_with_float (a_amount: FLOAT)
			-- make money from a real amount of dollars
			-- rounded to two decimal places
		local
			r: FLOAT
		do
			r := a_amount * create {FLOAT}.make_from_integer_32 (100)
			cents := r.rounded.to_integer_64
		end

	make_from_int (a_amount: INTEGER_64)
			-- make from an integer amount of cents
		do
			cents := a_amount
		end
	
feature --compare

	is_equal (other: like Current): BOOLEAN
			-- Is other attached to an object of the same type
			-- as current object and identical to it?
		do
			Result := cents = other.cents
		end

	is_less alias "<" (other: MONEY): BOOLEAN
			-- Is current money less than other?
		do
			Result := cents < other.cents
		end
	
feature -- effect numeric

	plus alias "+" (other: MONEY): MONEY
			-- Sum with other safely
		require
			safe_to_do_plus: safe_plus (cents, other.cents)
		local
			l_amount_imp: INTEGER_64
		do
			l_amount_imp := cents + other.cents
			create Result.make_from_int (l_amount_imp)
		ensure
			correct: Result = (create {MONEY}.make_from_int (cents + other.cents))
		end

	minus alias "-" (other: MONEY): MONEY
			-- minus with other safely
		require
			safe_to_do_minus: safe_minus (cents, other.cents)
		do
			create Result.make_from_int (- other.cents)
			Result := Current + Result
		ensure
			correct: Result = (create {MONEY}.make_from_int (cents - other.cents))
		end

	product alias "*" (arg: FLOAT): MONEY
			-- Product by arg
		do
			create Result.make_with_float (amount * arg)
		ensure then
				Result = (create {MONEY}.make_with_float ((amount * arg)))
		end

	one: like Current
			-- Neutral element for "*" and "/"
		do
			create Result.make_from_int (1)
		end

	zero: like Current
			-- Neutral element for "+" and "-"
		do
			create Result.make_from_int (0)
		end

	divisible (other: FLOAT): BOOLEAN
			-- May current object be divided by other?
		do
			Result := other /= create {FLOAT}.make_from_real (0.0)
		end

	quotient alias "/" (other: FLOAT): MONEY
			-- Division by other
		require else
				other /= create {FLOAT}.make_from_real (0.0)
		do
			create Result.make_with_float (amount / other)
		end

	identity alias "+": MONEY
			-- Unary plus
		do
			create Result.make_from_int (cents)
		end

	opposite alias "-": MONEY
			-- Unary minus
		do
			create Result.make_from_int (- cents)
		end
	
feature -- allocation

	allocated (arg: INTEGER_32): MONEY_ARRAY
			-- Divide by arg into equal amounts
		require
				comment ("Avoid division by zero and within range")
			arg_positive: arg > 0 and arg.to_integer_64 <= cents
		local
			base_result: INTEGER_64
			base_money: MONEY
			remainder: INTEGER_64
			one_cent: MONEY
			i: INTEGER_32
		do
			base_result := cents // arg.to_integer_64
			create base_money.make_from_int (base_result)
			create Result.make_filled (base_money, 1, arg)
			remainder := cents - base_result * arg.to_integer_64
			from
				i := 1
				create one_cent.make_from_int (1)
			until
				i.to_integer_64 > remainder
			loop
				Result [i] := Result [i] + one_cent
				i := i + 1
			end
		ensure
				Result.count = arg
				Result.sum = Current
			result_correct: across
					1 |..| arg as j
				all
					(j.item.to_integer_64 <= cents \\ arg.to_integer_64 implies Result [j.item].cents = (cents // arg.to_integer_64) + 1) and (j.item.to_integer_64 > cents \\ arg.to_integer_64 implies Result [j.item].cents = (cents // arg.to_integer_64))
				end
		end

	allocated_by_ratios (ratios: NUM_ARRAY [INTEGER_32]): MONEY_ARRAY
			-- Divide according to ratios
		require
				ratios /= Void and then ratios.sum /= ratios.sum.zero
		local
			total: INTEGER_32
			i: INTEGER_32
			remainder: INTEGER_64
			m: MONEY
		do
			total := ratios.sum
			from
				remainder := cents
				create Result.make_filled (m.zero, 1, ratios.count)
				i := ratios.lower
			until
				i > ratios.upper
			loop
				create m.make_from_int (((cents * ratios [i].to_integer_64) / total.to_integer_64).floor.to_integer_64)
				Result [i] := m
				remainder := remainder - m.cents
				i := i + 1
			end
			from
				i := 1
			until
				i.to_integer_64 > remainder
			loop
				Result [i] := Result [i] + m.one
				i := i + 1
			end
			Result.compare_objects
		ensure
				Result.count = ratios.count
				Result.sum = Current
			result_correct: across
					1 |..| ratios.count as j
				all
					Result [j.item].cents = ((cents * ratios [j.item].to_integer_64) / ratios.sum.to_integer_64).floor.to_integer_64 or Result [j.item].cents = ((cents * ratios [j.item].to_integer_64) / ratios.sum.to_integer_64).floor + 1.to_integer_64
				end
		end
	
feature -- out

	out: STRING_8
			-- New string containing terse printable representation
			-- of current object
		do
			Result := amount.formatted (2)
		end

	comment (s: STRING_8): BOOLEAN
		do
			Result := True
		end
	
invariant
	min: cents.Min_value = -9223372036854775808
	max: cents.Max_value = 9223372036854775807
	consistent_amount: amount = create {FLOAT}.make_from_real (cents / 100)

note
	specification: "[
			Efficient value type for handling money in dollars and cents.
			The class stores money as an INTEGER_64.
				MONEY values are within about 92,000 trillion dollars:
				$92,233,720,368,547,758.07
				Min_value: INTEGER_64 = -9223372036854775808
		    	Max_value: INTEGER_64 = 9223372036854775807
		    But the API works in terms of dollars and cents.
		    Thus create {MONEY}.make(10,966)creates $10.966.
		    Addition and subtraction are meaningful with type MONEY x MONEY -> MONEY.
		    Addition and subtraction are precise within the stated range,
		    	and are protected with preconditions to stay 
		    	safely within range without overflows.
		    For product, we have only one choice: MONEY x SCALAR -> MONEY
				where scalar can be float or int; we have float.
			For quotient we have MONEY x SCALAR -> MONEY
		    	which tells us amount / number_of_people
		    	i.e. approximately the amount that each person gets 
		    	dividing equally.Protected so that there is no division by zero.
		    These signatures satisfy dimensional analysis constraints.
		    As an alternative to the imprecise quotient (which can loose pennies),
		    	there are pecise allocation queries to divide money
		    	into equal amounts without loss.
		    Class MONEY uses a new type FLOAT
		    	with approximately equal operators.
		    	Division and multiplication using float is imprecise.
		    Feature 'out' displays money rounded to two decimal points.
	]"

end -- class MONEY

Generated by ISE EiffelStudio