note
	description: "[
				An arbitrary precision library for decimal numbers. 
				Following the 'General Decimal Arithmetic Specification' of gobo MA_DECIMAL.
				
				Creation
					make_from_string
					make_from_decimal
					make_from_int
					make_from_big_int
					make_from_rational
		
				Queries
					as_big_int: BIG_INTEGER
					as_rational: RATIONAL
					as_real32: REAL_32
					as_real64: REAL_64
					exp alias "^" (power: DECIMAL): DECIMAL
					identity alias "+": DECIMAL
					is_equal (other: DECIMAL): BOOLEAN
					is_greater alias ">" (other: DECIMAL): BOOLEAN
					is_greater_equal alias ">=" (other: DECIMAL): BOOLEAN
					is_integer: BOOLEAN
					is_less alias "<" (other: DECIMAL): BOOLEAN
					is_less_equal alias "<=" (other: DECIMAL): BOOLEAN
					is_natural: BOOLEAN
					is_natural1: BOOLEAN
					is_negative: BOOLEAN
					is_real32: BOOLEAN
					is_real64: BOOLEAN
					max (other: DECIMAL): DECIMAL
					min (other: DECIMAL): DECIMAL
					minus alias "-" (other: DECIMAL): DECIMAL
					one: DECIMAL
					opposite alias "-": DECIMAL
					out: STRING
					plus alias "+" (other: DECIMAL): DECIMAL
					precise_out: STRING
					precise_out_to (digits: INTEGER): STRING
					product alias "*" (other: DECIMAL): DECIMAL
					quotient alias "/" (other: DECIMAL): DECIMAL
					sqrt: DECIMAL
					round_to (digits: INTEGER): DECIMAL
					zero: DECIMAL
		
				Commands
					round (digits: INTEGER)
					set_precision (precision: INTEGER)
	]"
	author: "JSO, DC, and CD"
	date: "$Date$"
	revision: "$Revision$"

class 
	DECIMAL

inherit
	COMPARABLE
		redefine
			is_equal,
			default_create,
			out
		end

	DEBUG_OUTPUT
		redefine
			is_equal,
			default_create,
			out
		end

create 
	make_from_string,
	make_from_decimal,
	make_from_int,
	default_create,
	make_from_string_ctx,
	make_with_precision,
	make_from_big_int,
	make_from_rational

convert
	make_from_string ({STRING_8}),
	make_from_big_int ({BIG_INTEGER}),
	make_from_rational ({RATIONAL}),
	as_real64: {REAL_64},
	as_big_int: {BIG_INTEGER},
	as_rational: {RATIONAL},
	out: {STRING_8}

feature {NONE} -- constructors

	make_with_precision (a_value: STRING_8; a_precision: INTEGER_32)
			-- Make a new decimal from a_value with `a_precision
		require
			positive_precision: a_precision > 0
		local
			l_ma_decimal_ctx: MA_DECIMAL_CONTEXT
		do
			create l_ma_decimal_ctx.make (a_precision, 4)
			create ma_decimal.make_from_string_ctx (a_value, l_ma_decimal_ctx)
			ma_decimal_ctx := l_ma_decimal_ctx
			item := decimal_2_value_string
		end

	default_create
			-- create an empty object (it's equivalent to 0)
		local
			empty: STRING_8
		do
			empty := "0"
			item := empty.twin
			ma_decimal := decimal_setup (item, Default_precision + 1)
		ensure then
				item.is_equal ("0")
		end

	make_from_string (s: STRING_8)
			-- create an object from string 's'
		require
			non_void: s /= Void
			non_empty: not s.is_empty
			has_correct_format: ensurevalid (s)
		local
			precision: INTEGER_32
			value: LIST [STRING_8]
		do
			value := s.split (Exponent_char.to_character_32)
			precision := value [1].count
			if precision < (Default_precision + value [1].split (Decimal_char.to_character_32) [1].count) then
				precision := Default_precision + value [1].split (Decimal_char.to_character_32) [1].count
			end
			ma_decimal := decimal_setup (s.twin, precision)
			item := decimal_2_value_string
		end

	make_from_int (int: INTEGER_32)
			-- create a DECIMAL object from integer m
		do
			item := int.out
			ma_decimal_ctx := decimal_ctx_for_value
			ma_decimal_ctx.set_digits (item.count + Default_precision)
			create ma_decimal.make_from_integer (int)
		end

	make_from_big_int (big_int: BIG_INTEGER)
			-- Create from BIG_INTEGER
		do
			make_from_string (big_int.out)
		end

	make_from_rational (rational: RATIONAL)
			-- Create from RATIONAL
		do
			make_from_string (rational.out)
		end
	
feature -- comparison

	is_equal (other: DECIMAL): BOOLEAN
			-- check whether other is equal to current or not
		do
			Result := ma_decimal.is_equal (other.ma_decimal)
		end

	is_less alias "<" (other: DECIMAL): BOOLEAN
			-- whether current is less than other
		do
			Result := ma_decimal < other.ma_decimal
		end
	
feature -- operations

	plus alias "+" (other: DECIMAL): DECIMAL
			-- adds current to other and returns a new object
			-- The new DECIMAL will have a precision based on the criteria in calculate_new_precision
			-- Was declared in DECIMAL as synonym of add.
		local
			l_decimal: MA_DECIMAL
			new_context: MA_DECIMAL_CONTEXT
			digits: INTEGER_32
		do
			digits := calculate_new_precision (other)
			create new_context.make (digits, Default_rounding)
			create l_decimal.make_zero
			l_decimal.set_shared_decimal_context (new_context)
			l_decimal := Current.ma_decimal.add (other.ma_decimal, l_decimal.shared_decimal_context)
			create Result.make_from_decimal (l_decimal)
		end

	add (other: DECIMAL): DECIMAL
			-- adds current to other and returns a new object
			-- The new DECIMAL will have a precision based on the criteria in calculate_new_precision
			-- Was declared in DECIMAL as synonym of plus.
		local
			l_decimal: MA_DECIMAL
			new_context: MA_DECIMAL_CONTEXT
			digits: INTEGER_32
		do
			digits := calculate_new_precision (other)
			create new_context.make (digits, Default_rounding)
			create l_decimal.make_zero
			l_decimal.set_shared_decimal_context (new_context)
			l_decimal := Current.ma_decimal.add (other.ma_decimal, l_decimal.shared_decimal_context)
			create Result.make_from_decimal (l_decimal)
		end

	minus alias "-" (other: DECIMAL): DECIMAL
			-- subtracts other from current and returns a new object
			-- The new DECIMAL will have a precision based on the criteria in calculate_new_precision
			-- Was declared in DECIMAL as synonym of subtract.
		local
			l_decimal: MA_DECIMAL
			new_context: MA_DECIMAL_CONTEXT
			digits: INTEGER_32
		do
			digits := calculate_new_precision (other)
			create new_context.make (digits, Default_rounding)
			create l_decimal.make_zero
			l_decimal.set_shared_decimal_context (new_context)
			l_decimal := Current.ma_decimal.subtract (other.ma_decimal, l_decimal.shared_decimal_context)
			create Result.make_from_decimal (l_decimal)
		end

	subtract (other: DECIMAL): DECIMAL
			-- subtracts other from current and returns a new object
			-- The new DECIMAL will have a precision based on the criteria in calculate_new_precision
			-- Was declared in DECIMAL as synonym of minus.
		local
			l_decimal: MA_DECIMAL
			new_context: MA_DECIMAL_CONTEXT
			digits: INTEGER_32
		do
			digits := calculate_new_precision (other)
			create new_context.make (digits, Default_rounding)
			create l_decimal.make_zero
			l_decimal.set_shared_decimal_context (new_context)
			l_decimal := Current.ma_decimal.subtract (other.ma_decimal, l_decimal.shared_decimal_context)
			create Result.make_from_decimal (l_decimal)
		end

	product alias "*" (other: DECIMAL): DECIMAL
			-- multiplies current by other and returns a new object
			-- The new DECIMAL will have a precision based on the criteria in calculate_new_precision
			-- Was declared in DECIMAL as synonym of multiply.
		local
			l_decimal: MA_DECIMAL
			new_context: MA_DECIMAL_CONTEXT
			digits: INTEGER_32
		do
			digits := calculate_new_precision (other)
			create new_context.make (digits, Default_rounding)
			create l_decimal.make_zero
			l_decimal.set_shared_decimal_context (new_context)
			l_decimal := Current.ma_decimal.multiply (other.ma_decimal, l_decimal.shared_decimal_context)
			create Result.make_from_decimal (l_decimal)
		end

	multiply (other: DECIMAL): DECIMAL
			-- multiplies current by other and returns a new object
			-- The new DECIMAL will have a precision based on the criteria in calculate_new_precision
			-- Was declared in DECIMAL as synonym of product.
		local
			l_decimal: MA_DECIMAL
			new_context: MA_DECIMAL_CONTEXT
			digits: INTEGER_32
		do
			digits := calculate_new_precision (other)
			create new_context.make (digits, Default_rounding)
			create l_decimal.make_zero
			l_decimal.set_shared_decimal_context (new_context)
			l_decimal := Current.ma_decimal.multiply (other.ma_decimal, l_decimal.shared_decimal_context)
			create Result.make_from_decimal (l_decimal)
		end

	quotient alias "/" (other: DECIMAL): DECIMAL
			-- divides current by other and returns a new object
			-- The new DECIMAL will have a precision based on the criteria in calculate_new_precision
			-- Was declared in DECIMAL as synonym of divide.
		require
			other_non_zero: other /~ zero
		local
			l_decimal: MA_DECIMAL
			new_context: MA_DECIMAL_CONTEXT
			digits: INTEGER_32
		do
			digits := calculate_new_precision (other)
			create new_context.make (digits, Default_rounding)
			create l_decimal.make_zero
			l_decimal.set_shared_decimal_context (new_context)
			l_decimal := Current.ma_decimal.divide (other.ma_decimal, l_decimal.shared_decimal_context)
			create Result.make_from_decimal (l_decimal)
		end

	divide (other: DECIMAL): DECIMAL
			-- divides current by other and returns a new object
			-- The new DECIMAL will have a precision based on the criteria in calculate_new_precision
			-- Was declared in DECIMAL as synonym of quotient.
		require
			other_non_zero: other /~ zero
		local
			l_decimal: MA_DECIMAL
			new_context: MA_DECIMAL_CONTEXT
			digits: INTEGER_32
		do
			digits := calculate_new_precision (other)
			create new_context.make (digits, Default_rounding)
			create l_decimal.make_zero
			l_decimal.set_shared_decimal_context (new_context)
			l_decimal := Current.ma_decimal.divide (other.ma_decimal, l_decimal.shared_decimal_context)
			create Result.make_from_decimal (l_decimal)
		end

	sqrt: DECIMAL
			-- calculates square root for this instance
		require
			non_negative: not is_negative
		local
			l_epsilon: DECIMAL
			l_root: DECIMAL
		do
			l_epsilon := create {attached DECIMAL}.make_from_string (Epsilon)
			if out ~ zero.out then
				Result := zero
			else
				from
					l_root := clone_me
				until
					((l_root - (Current / l_root)).absolute <= create {DECIMAL}.make_from_string (Epsilon) * l_root)
				loop
					l_root := ((Current / l_root) + l_root) / create {DECIMAL}.make_from_string ("2.0")
				end
				Result := l_root
			end
		end

	exp alias "^" (p: DECIMAL): DECIMAL
			-- calculates the value of current instance raised to 'power' (capped to 500 digits of precision)
			-- Was declared in DECIMAL as synonym of exponent and power.
		require
			is_exponentiable: exponentiable (p)
		do
			if p.is_integer then
				Result := integer_exp (Current, p.ma_decimal.to_integer.abs)
			else
				Result := real_exp (Current, p.absolute)
			end
			if p.is_negative then
				Result := one / Result
			end
		end

	exponent (p: DECIMAL): DECIMAL
			-- calculates the value of current instance raised to 'power' (capped to 500 digits of precision)
			-- Was declared in DECIMAL as synonym of exp and power.
		require
			is_exponentiable: exponentiable (p)
		do
			if p.is_integer then
				Result := integer_exp (Current, p.ma_decimal.to_integer.abs)
			else
				Result := real_exp (Current, p.absolute)
			end
			if p.is_negative then
				Result := one / Result
			end
		end

	power (p: DECIMAL): DECIMAL
			-- calculates the value of current instance raised to 'power' (capped to 500 digits of precision)
			-- Was declared in DECIMAL as synonym of exp and exponent.
		require
			is_exponentiable: exponentiable (p)
		do
			if p.is_integer then
				Result := integer_exp (Current, p.ma_decimal.to_integer.abs)
			else
				Result := real_exp (Current, p.absolute)
			end
			if p.is_negative then
				Result := one / Result
			end
		end

	negate
			-- negates Current
		local
			l_decimal: MA_DECIMAL
		do
			l_decimal := ma_decimal.opposite
			Current.make_from_decimal (l_decimal)
		end

	absolute: DECIMAL
			-- returns the absoulte value of Current
		do
			create Result.make_from_decimal (ma_decimal.abs)
		end

	floor: DECIMAL
			-- Returns the greatest integer less than or equal to Current
		local
			curr_abs: DECIMAL
			floored_absolute: DECIMAL
		do
			curr_abs := Current.absolute
			create floored_absolute.make_from_string (curr_abs.value_precise_out.split (Decimal_char.to_character_32) [1])
			if is_integer then
				Result := Current.twin
			else
				if is_negative then
					floored_absolute.negate
					Result := floored_absolute - create {DECIMAL}.make_from_string ("1")
				else
					Result := floored_absolute
				end
			end
		end

	ceiling: DECIMAL
			-- Returns the smallest integer greater than or equal to Current
		local
			curr_abs: DECIMAL
			floored_absolute: DECIMAL
		do
			curr_abs := Current.absolute
			create floored_absolute.make_from_string (curr_abs.value_precise_out.split (Decimal_char.to_character_32) [1])
			if is_integer then
				Result := Current.twin
			else
				if is_negative then
					floored_absolute.negate
					Result := floored_absolute
				else
					Result := floored_absolute + create {DECIMAL}.make_from_string ("1")
				end
			end
		end
	
feature -- conversion

	as_real32: REAL_32
			-- represent as a REAL_32
		require
			valid_real32: is_real32
		do
			Result := item.to_real_32
		end

	as_real64: REAL_64
			-- represent as a REAL_64
		require
			valid_real64: is_real64
		do
			Result := ma_decimal.to_double
		end

	as_big_int: BIG_INTEGER
			-- Represent as BIG_INTEGER
		require
			is_integer: is_integer
		do
			create Result.make_from_string (item)
		end

	as_rational: RATIONAL
			-- Represent as RATIONAL
		do
			create Result.make_from_string (item)
		end
	
feature -- queries

	opposite alias "-": DECIMAL
			-- unary minus
		do
			create Result.make_from_decimal (ma_decimal.opposite)
		end

	identity alias "+": DECIMAL
			-- unary plus
		do
			create Result.make_from_decimal (ma_decimal)
		end

	is_natural: BOOLEAN
			-- Is Current an Natural number?
		do
			Result := is_integer and Current >= zero
		end

	is_natural1: BOOLEAN
			-- Is Current an Natural1 number?
		do
			Result := is_integer and Current >= one
		end

	is_integer: BOOLEAN
			-- Is Current an integer number?
		do
			Result := ma_decimal.is_integer
		end

	is_real32: BOOLEAN
		local
			correct_length, in_range: BOOLEAN
			upper, lower: DECIMAL
		do
			create upper.make_from_string ("-3.4")
			create lower.make_from_string ("3.4")
			upper := upper * (create {DECIMAL}.make_from_string ("10")).exp (create {DECIMAL}.make_from_string ("38"))
			lower := lower * (create {DECIMAL}.make_from_string ("10")).exp (create {DECIMAL}.make_from_string ("38"))
			correct_length := item.count <= 7
			in_range := Current > create {DECIMAL}.make_from_string ("-3.4")
		end

	is_real64: BOOLEAN
			-- Is Current a double?
		do
			Result := ma_decimal.is_double
		end

	is_negative: BOOLEAN
			-- Is Current less than or equal to zero?
		do
			Result := ma_decimal.is_negative and Current /~ zero
		end

	debug_output: STRING_8
			-- String that should be displayed in debugger to represent Current.
		do
			Result := out
		end

	out: STRING_8
			-- return a representation of the number rounded to two decimal places
		do
			Result := value_out
		end

	precise_out: STRING_8
			-- return precise representation of the number
		do
			Result := value_precise_out
		end

	zero: DECIMAL
			-- neutral element for "+" and "-"
		do
			create Result.make_from_string ("0")
		end

	one: DECIMAL
			-- neutral element for "*" and "/"
		do
			create Result.make_from_string ("1")
		end

	divisible (other: DECIMAL): BOOLEAN
			-- may current object be divided by other?
		do
			Result := not other.is_equal (zero)
		end

	exponentiable (other: DECIMAL): BOOLEAN
			-- may current object be elevated to the power other?
		do
			Result := True
		end
	
feature -- rounding

	round_to (digits: INTEGER_32): DECIMAL
			-- Returns a decimal that is the current value rounded to the specified 'digits' number of decimal places
			-- Uses default strategy as set in ma_decimal_ctx (half up)
		require
			positive_digits: digits >= 0
		local
			l_decimal_ctx: MA_DECIMAL_CONTEXT
			decimal_left: INTEGER_32
			decimal_left_int: BIG_INTEGER
		do
			decimal_left := value_precise_out.split (Decimal_char.to_character_32) [1].count
			if is_negative then
				decimal_left := decimal_left - 1
			end
			decimal_left_int := create {attached BIG_INTEGER}.make_from_string (value_precise_out.split (Decimal_char.to_character_32) [1])
			if decimal_left_int ~ create {BIG_INTEGER}.make_from_string ("0") then
				decimal_left := 0
			end
			if digits + decimal_left <= 0 then
				decimal_left := 1
			end
			create l_decimal_ctx.make (decimal_left + digits, ma_decimal_ctx.rounding_mode)
			create Result.make_from_decimal (create {MA_DECIMAL}.make_from_string_ctx (item, l_decimal_ctx))
		end

	round (digits: INTEGER_32)
			-- rounds this instance to the specified digits number of decimal places
		require
			positive_digits: digits >= 0
		local
			l_decimal_ctx: MA_DECIMAL_CONTEXT
			decimal_left: INTEGER_32
		do
			decimal_left := value_precise_out.split (Decimal_char.to_character_32) [1].count
			if is_negative then
				decimal_left := decimal_left - 1
			end
			if (value_precise_out.split (Decimal_char.to_character_32) [1] [1]).out ~ "0" or (is_negative and then (value_precise_out.split (Decimal_char.to_character_32) [1] [2]).out ~ "0") then
				decimal_left := decimal_left - 1
			end
			create l_decimal_ctx.make (decimal_left + digits, ma_decimal_ctx.rounding_mode)
			Current.make_from_decimal (create {MA_DECIMAL}.make_from_string_ctx (item, l_decimal_ctx))
		end

	precise_out_to (digits: INTEGER_32): STRING_8
			-- returns the precise string representation to rounded to the specified 'digits' number of decimal places
		require
			positive_digits: digits >= 0
		local
			digit_list: LIST [STRING_8]
			decimal_left: STRING_8
			decimal_right: STRING_8
			rounded_up: INTEGER_32
			l_decimal, unrounded_dec, rounded_dec, carry_dec: DECIMAL
			zero_pad: STRING_8
		do
			create Result.make_empty
			l_decimal := round_to (digits)
			digit_list := l_decimal.precise_out.split (Decimal_char.to_character_32)
			decimal_left := digit_list [1]
			Result.append (decimal_left)
			Result.append (create {STRING_8}.make_filled (Decimal_char, 1))
			if digit_list.count > 1 then
				decimal_right := digit_list [2]
				if decimal_right.count < digits then
					create zero_pad.make_filled ('0', digits - decimal_right.count)
					Result.append (decimal_right)
					Result.append (zero_pad)
				else
					Result.append (decimal_right.substring (1, digits))
					if decimal_right.count > digits and then decimal_right.substring (digits + 1, digits + 1).to_integer >= 5 then
						rounded_up := decimal_right.substring (digits, digits).to_integer + 1
						if rounded_up < 10 then
							Result.remove_tail (1)
							Result.append_integer (rounded_up)
						else
							unrounded_dec := create {attached DECIMAL}.make_from_string (Result)
							create zero_pad.make_filled ('0', digits - 1)
							zero_pad.prepend ("0.")
							zero_pad.append ("1")
							carry_dec := create {attached DECIMAL}.make_from_string (zero_pad)
							rounded_dec := unrounded_dec + carry_dec
							create Result.make_from_string (rounded_dec.out)
						end
					end
				end
			else
				create zero_pad.make_filled ('0', digits)
				Result.append (zero_pad)
			end
		end

	set_precision (precision: INTEGER_32)
			-- sets the precision of Current for future operations
		require
			precision_positive: precision > 0
		local
			l_decimal_ctx: MA_DECIMAL_CONTEXT
		do
			create l_decimal_ctx.make (precision, ma_decimal_ctx.rounding_mode)
			create ma_decimal.make_from_string_ctx (item, l_decimal_ctx)
			ma_decimal_ctx := l_decimal_ctx
			ma_decimal.set_shared_decimal_context (l_decimal_ctx)
			item := decimal_2_value_string
		end

	reset_precision
			-- resets the precision of Current for future operations to Default_precision
		local
			l_decimal_ctx: MA_DECIMAL_CONTEXT
		do
			create l_decimal_ctx.make (Default_precision, ma_decimal_ctx.rounding_mode)
			create ma_decimal.make_from_string_ctx (item, l_decimal_ctx)
			ma_decimal_ctx := l_decimal_ctx
			ma_decimal.set_shared_decimal_context (l_decimal_ctx)
			item := decimal_2_value_string
		end

	get_precision: INTEGER_32
			-- returns the current number of digits being kept by the number
		do
			Result := ma_decimal_ctx.digits.twin
		end
	
feature {DECIMAL} -- private fields

	item: STRING_8
			-- String representation of the number

	Default_precision: INTEGER_32 = 36
			-- the minimum accuracy of division results;
			-- division results will be accurate to AT LEAST this many digits.

	Default_rounding: INTEGER_32 = 4
			-- Use half-round-up rounding

	Epsilon: STRING_8 = "0.000000000000001"
			-- for root computation

	Decimal_char: CHARACTER_8 = '.'

	Exponent_char: CHARACTER_8 = 'E'

	ma_decimal_ctx: MA_DECIMAL_CONTEXT
			-- underlying implementation

	ma_decimal: MA_DECIMAL
			-- ma_decimal is the underlying implementation

	make_from_decimal (decimal: MA_DECIMAL)
			-- create DECIMAL_WRAPPER from MA_DECIMAL
		do
			create ma_decimal.make_copy (decimal)
			ma_decimal_ctx := ma_decimal.shared_decimal_context
			item := decimal_2_value_string
		end

	decimal_ctx_for_value: MA_DECIMAL_CONTEXT
			-- create MA_DECIMAL_CONTEXT configured according to VALUE
		do
			create Result.make (Default_precision, Default_rounding)
		end

	decimal_setup (s: STRING_8; dv: INTEGER_32): MA_DECIMAL
			-- create new MA_DECIMAL object from value "s" with precision "dv"
		require
			non_void: s /= Void
			non_empty: not s.is_empty
			has_correct_format: ensurevalid (s)
		do
			ma_decimal_ctx := decimal_ctx_for_value
			ma_decimal_ctx.set_digits (dv)
			create Result.make_from_string_ctx (s, ma_decimal_ctx)
			Result.set_shared_decimal_context (ma_decimal_ctx)
		end

	clone_me: DECIMAL
		local
			l_s: STRING_8
		do
			create l_s.make_from_string (item)
			create Result.make_from_decimal (ma_decimal)
		end

	decimal_2_value_string: STRING_8
			-- converts scientific_notation to value.precise_out
			-- *should be deprecated once MA_DECIMAL allows precise string representation
		local
			str_list: LIST [STRING_8]
			coefficient_list: LIST [STRING_8]
			decimal_left: STRING_8
			decimal_right: STRING_8
			coefficients: STRING_8
			zero_pad: STRING_8
			l_exponent: STRING_8
			pad_count: INTEGER_32
			int_exponent: INTEGER_32
			decimal_count: INTEGER_32
			index: INTEGER_32
		do
			if ma_decimal.is_zero then
				Result := "0"
			else
				str_list := ma_decimal.to_scientific_string.split (Exponent_char.to_character_32)
				coefficients := str_list [1]
				create Result.make_empty
				if str_list.count > 1 then
					l_exponent := str_list [2]
					int_exponent := l_exponent.to_integer
					coefficient_list := coefficients.split (Decimal_char.to_character_32)
					if coefficient_list.count > 1 then
						decimal_count := coefficient_list [2].count
					else
						decimal_count := 0
					end
					if int_exponent.abs >= decimal_count then
						Result.append (coefficient_list [1])
						if decimal_count > 0 then
							Result.append (coefficient_list [2])
						end
						create zero_pad.make_empty
						if int_exponent > 0 then
							zero_pad.make_filled ('0', int_exponent - decimal_count)
							Result.append (zero_pad)
						else
							zero_pad.make_filled ('0', int_exponent.abs - 1)
							Result.prepend (zero_pad)
							Result.prepend ("0.")
						end
					elseif int_exponent < 0 then
						Result.append ("0")
						Result.append (create {STRING_8}.make_filled (Decimal_char, 1))
						pad_count := int_exponent.abs - coefficient_list [1].count
						create zero_pad.make_filled ('0', pad_count)
						Result.append (zero_pad)
						Result.append (coefficient_list [1])
						Result.append (coefficient_list [2])
					else
						if int_exponent = 0 then
							Result := coefficients
						else
							decimal_left := coefficient_list [2].substring (1, int_exponent)
							decimal_right := coefficient_list [2].substring (int_exponent, decimal_count)
							Result.append (coefficient_list [1])
							Result.append (decimal_left)
							Result.append (create {STRING_8}.make_filled (Decimal_char, 1))
							Result.append (decimal_right)
						end
					end
				else
					coefficient_list := coefficients.split (Decimal_char.to_character_32)
					if coefficient_list.count > 1 and then zero.is_equal (create {DECIMAL}.make_from_string (coefficient_list [2])) then
						Result := coefficient_list [1]
					else
						Result := coefficients
					end
				end
				if Result.has (Decimal_char) then
					from
						index := Result.count
					until
						not (Result.at (index) ~ '0')
					loop
						index := index - 1
						Result := Result.substring (1, index)
					end
				end
			end
		end

	value_precise_out: STRING_8
			-- Precise string representation of object
		do
			Result := item.twin
		end

	value_out: STRING_8
			-- String representation of object upto 2 decimal places
		do
			Result := precise_out_to (2)
		end

	ensurevalid (s: STRING_8): BOOLEAN
			-- check if the given string is of the correct format
		require
			non_void: s /= Void
			non_empty: not s.is_empty
		local
			seendot: BOOLEAN
			i: INTEGER_32
		do
			seendot := False
			Result := True
			from
				i := 1
			until
				i > s.count
			loop
				if s.item (i) = '-' then
					if i > 1 then
						Result := False
						i := s.count + 1
					end
				elseif s.item (i) = Decimal_char then
					if i = s.count or seendot then
						Result := False
						i := s.count + 1
					end
					seendot := True
				elseif not s.item (i).is_digit then
					Result := False
					i := s.count + 1
				end
				i := i + 1
			end
		end

	integer_exp (val: DECIMAL; p: INTEGER_32): DECIMAL
			-- recursively calculate 'val' to positive integer power 'power'
			-- Fast exponentiation, capped to 500 digits of precision
		local
			square: DECIMAL
		do
			if p = 0 then
				Result := create {attached DECIMAL}.make_from_string ("1")
			else
				square := val * val
				square.set_precision (500)
				Result := integer_exp (square, p // 2)
				if p \\ 2 = 1 then
					Result := Result * val
				end
			end
			Result.set_precision (500)
		end

	real_exp (val: DECIMAL; p: DECIMAL): DECIMAL
			-- recursively calculate 'val' to real power 'power'
			-- Fast exponentiation until power < 1. Then binary search on fractional exponent
			-- Capped to 500 decimals of precision
		local
			temp: DECIMAL
			low: DECIMAL
			high: DECIMAL
			mid: DECIMAL
			l_root: DECIMAL
			acc: DECIMAL
			two: DECIMAL
		do
			two := create {DECIMAL}.make_from_string ("2.0")
			if p >= one then
				temp := real_exp (val, p / two)
				temp.set_precision (500)
				Result := temp * temp
			else
				from
					low := zero
					high := one
					l_root := val.sqrt
					acc := l_root
					mid := high / two
				until
					((mid - p).absolute <= create {DECIMAL}.make_from_string (Epsilon))
				loop
					l_root := l_root.sqrt
					if mid <= p then
						low := mid
						acc := acc * l_root
					else
						high := mid
						acc := acc * (one / l_root)
					end
					mid := (low + high) / two
				end
				Result := acc
			end
			Result.set_precision (500)
		end

	make_from_string_ctx (value_string: STRING_8; ctx: MA_DECIMAL_CONTEXT)
			-- Make a new decimal from value_string relative to ctx
		do
			create ma_decimal.make_from_string_ctx (value_string, ctx)
			ma_decimal_ctx := ctx
			item := decimal_2_value_string
		end

	calculate_new_precision (other: DECIMAL): INTEGER_32
			-- Used in primary operations to calculate new precision
			-- The new precision is the largest value of: Current, other, Default_precision,
			-- or the sum of Current's precision + other's precision.
		local
			curr_dig, other_dig, str_digs, digits: INTEGER_32
			curr_string, other_string: STRING_8
		do
			curr_string := Current.precise_out
			other_string := other.precise_out
			curr_dig := curr_string.count
			other_dig := other_string.count
			str_digs := curr_dig + other_dig
			digits := Default_precision
			if digits < ma_decimal_ctx.digits then
				digits := ma_decimal_ctx.digits
			end
			if digits < other.ma_decimal_ctx.digits then
				digits := other.ma_decimal_ctx.digits
			end
			if digits < str_digs then
				digits := str_digs
			end
			Result := digits
		end
	
invariant
	consistent_rounding: Default_rounding = {MA_DECIMAL_CONTEXT}.round_half_up

note
	info: "[
		 For an oracle see http://delaneyrm.com/MPCalcRB.html.
		 
		 test: BOOLEAN
			local
				d1, d2, d3, d4, d5, d6: DECIMAL
				r64: REAL_64
				i: INTEGER
				r: RATIONAL
			do
				comment ("test: Mortgage Calculation in REAL_64 vs DECIMAL")
				-- floating point arithmetic cannot distinguish
				-- between 0.4 and 0.40000000000000002
				r64 := 0.1 + 0.3
				Result := r64 = 0.4 and r64 = 0.40000000000000002
				check Result end
				-- decimal arithmetic can
				d1 := "0.1" ; d2 := "0.3"
				d3 := d1 + d2
				Result := d3 ~ "0.4"and d3 /~ "0.40000000000000002"
				check Result end
				assert_equal ("precise_out test", "0.4", d3.precise_out)
				assert_equal ("out test1", "0.40", d3.out)
				r64 := 100000*(1+0.05/12)^360
				Result := r64 = 446774.43140061089
				check Result end
				d1 := "100000" d2 := "0.05"; d3 := "12"; d4 := "360"
				d2.set_precision(120)
				d5 := d1 * (d1.one + d2/d3)^d4
				Result := d5.precise_out.starts_with ("446774.431400613221242807011041301589597772674487270698002439224547330881614835011308578390042190134313238565062087470576")
				check Result end
				assert_equal ("out test2", "446774.43", d5.out)
				d6 := d5 - "446774.43140061089"
				Result := d6.precise_out.starts_with ("0.000000002331242807011041301589597772674487270698002439224547330881614835011308578390042190134313238565062087470576")
				check
					 Result
				end
			end
	]"

end -- class DECIMAL

Generated by ISE EiffelStudio