Class: Quadratic

Inherits:
Numeric
  • Object
show all
Defined in:
lib/quadratic_number.rb

Overview

Base class.

Users must specify a square-free integer to get a concrete class (see class method .[]).

Direct Known Subclasses

Imag, Real

Defined Under Namespace

Classes: Imag, Real

Constant Summary collapse

@@classes =
{}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(a, b = 0) ⇒ Quadratic

Returns (a+b√d).

Examples:

phi   = Quadratic[5].new(1, 1) / 2     #=> ((1/2)+(1/2)*√5)
omega = Quadratic[-3].new(-1, 1) / 2   #=> ((-1/2)+(1/2)*√-3)


73
74
75
76
77
78
79
# File 'lib/quadratic_number.rb', line 73

def initialize(a, b = 0)
	unless [a, b].all? { |x| __rational__(x) }
		raise TypeError, "not a rational"
	end
	@a = a
	@b = b
end

Class Method Details

.[](d) ⇒ Class

Provides a concrete class.

Examples:

Quadratic[2].ancestors.take(4)
#=> [Quadratic[2], Quadratic::Real, Quadratic, Numeric]
Quadratic[-1].ancestors.take(4)
#=> [Quadratic[-1], Quadratic::Imag, Quadratic, Numeric]

Parameters:

  • d (Integer)

Returns:

  • (Class)

Raises:

  • (TypeError)

    if d is not an integer.

  • (RangeError)

    if d is not square-free.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/quadratic_number.rb', line 29

def self.[](d)
	# return a memoized subclass if exists
	return @@classes[d] if @@classes[d]

	unless d.kind_of?(Integer)
		raise TypeError, 'not an integer'
	end

	if d == 0 || d == 1 ||
	   Prime.prime_division(d).any? { |p,k| k > 1 }
		raise RangeError, 'd must be square-free other than 0 or 1'
	end

	# memoize a new subclass and return it
	base = (d >= 0) ? Real : Imag
	@@classes[d] = Class.new(base) do
		# In this scope, `self` indicates a concrete subclass.
		self.const_set(:D, d)

		class << self
			def name
				"Quadratic[#{self::D}]"
			end
			alias to_s name
			alias inspect name

			public :new
		end
	end
end

Instance Method Details

#*(other) ⇒ Quadratic[d]

Performs multiplication.

Parameters:

  • other (Numeric)

Returns:



159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/quadratic_number.rb', line 159

def *(other)
	my_class = self.class
	if other.kind_of?(my_class)
		_a = other.a
		_b = other.b
		my_class.new(@a * _a + @b * _b * my_class::D, @a * _b + @b * _a)
	elsif __rational__(other)
		my_class.new(@a * other, @b * other)
	else
		__coerce_exec__(:*, other)
	end
end

#**(index) ⇒ Quadratic[d]/Float/Complex

Performs exponentiation.

Parameters:

  • index (Numeric)

Returns:



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/quadratic_number.rb', line 216

def **(index)
	unless index.kind_of?(Numeric)
		num1, num2 = index.coerce(self)
		return num1 ** num2
	end

	# return 1 if index is exactly zero
	begin
		1 / index
	rescue ZeroDivisionError
		return self.class.new(1, 0)
	end

	# complex -> real
	begin
		index.to_f
	rescue
	else
		index = index.real
	end

	# quadratic -> rational or integer / float or complex
	if index.kind_of?(Quadratic)
		if index.b == 0
			index = index.a
		else
			index = index.to_builtin
		end
	end

	# rational -> integer
	if index.kind_of?(Rational) && index.denominator == 1
		index = index.numerator
	end

	if index.integer?
		# binary method
		x = (index >= 0) ? self : 1 / self
		n = index.abs

		z = self.class.new(1, 0)
		while true
			n, i = n.divmod(2)
			z *= x if i == 1
			return z if n == 0
			x *= x
		end
	else
		return self.to_builtin ** index
	end
end

#+(other) ⇒ Quadratic[d]

Performs addition.

Parameters:

  • other (Numeric)

Returns:



125
126
127
128
129
130
131
132
133
134
# File 'lib/quadratic_number.rb', line 125

def +(other)
	my_class = self.class
	if other.kind_of?(my_class)
		my_class.new(@a + other.a, @b + other.b)
	elsif __rational__(other)
		my_class.new(@a + other, @b)
	else
		__coerce_exec__(:+, other)
	end
end

#-(other) ⇒ Quadratic[d]

Performs subtraction.

Parameters:

  • other (Numeric)

Returns:



142
143
144
145
146
147
148
149
150
151
# File 'lib/quadratic_number.rb', line 142

def -(other)
	my_class = self.class
	if other.kind_of?(my_class)
		my_class.new(@a - other.a, @b - other.b)
	elsif __rational__(other)
		my_class.new(@a - other, @b)
	else
		__coerce_exec__(:-, other)
	end
end

#-@Quadratic[d]

Returns negation of the value.

Returns:



273
274
275
# File 'lib/quadratic_number.rb', line 273

def -@
	self.class.new(-@a, -@b)
end

#coerce(other) ⇒ [Numeric, Numeric]

Performs type conversion.

Parameters:

  • other (Numeric)

Returns:

  • ([Numeric, Numeric])

    other and self

Raises:

  • (TypeError)


91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/quadratic_number.rb', line 91

def coerce(other)
	my_class = self.class
	if other.kind_of?(my_class)
		# my_class
		[other, self]
	elsif __rational__(other)
		# Integer and Rational
		[my_class.new(other, 0), self]
	elsif other.kind_of?(Quadratic)
		# Quadratic::Real and Quadratic::Imag
		if self.real? && other.real?
			[other.to_f, self.to_f]
		else
			[other.to_c, self.to_c]
		end
	elsif __real__(other)
		# Float and BigDecimal
		[other, self.to_builtin]
	elsif __complex__(other)
		# Complex
		[other, self.to_c]
	else
		# others
		raise TypeError,
		      "#{other.class} can't be coerced into #{self.class}"
	end
end

#denominatorInteger

Returns its denominator.

Returns:

  • (Integer)


339
340
341
342
343
# File 'lib/quadratic_number.rb', line 339

def denominator
	ad = @a.denominator
	bd = @b.denominator
	ad.lcm(bd)
end

#discriminantInteger/Rational

Returns its discriminant.

Returns:

  • (Integer/Rational)


398
399
400
401
# File 'lib/quadratic_number.rb', line 398

def discriminant
	# trace ** 2 - 4 * norm
	@b * @b * (self.class::D * 4)
end

#eql?(other) ⇒ Boolean

Returns true if the two numbers are equal including their types.

Parameters:

  • other (Object)

Returns:

  • (Boolean)


285
286
287
288
289
290
291
# File 'lib/quadratic_number.rb', line 285

def eql?(other)
	if other.kind_of?(self.class)
		@a.eql?(other.a) && @b.eql?(other.b)
	else
		false
	end
end

#fdiv(other) ⇒ Float/Complex

Performs division.

Parameters:

  • other (Numeric)

Returns:

  • (Float/Complex)


199
200
201
202
203
204
205
206
207
208
# File 'lib/quadratic_number.rb', line 199

def fdiv(other)
	if other.kind_of?(Quadratic)
		self.to_builtin.fdiv(other.to_builtin)
	elsif other.kind_of?(Numeric)
		self.to_builtin.fdiv(other)
	else
		n1, n2 = other.coerce(self)
		n1.fdiv(other)
	end
end

#hashInteger

Returns a hash value.

Returns:

  • (Integer)


298
299
300
# File 'lib/quadratic_number.rb', line 298

def hash
	[@a, @b, self.class::D].hash
end

#inspectString Also known as: to_s

Returns a string.

Returns:

  • (String)


327
328
329
# File 'lib/quadratic_number.rb', line 327

def inspect
	'(' << __format__(:inspect) << ')'
end

#normInteger/Rational Also known as: qabs2

Returns its norm.

Returns:

  • (Integer/Rational)


386
387
388
389
# File 'lib/quadratic_number.rb', line 386

def norm
	# self * self.qconj
	@a * @a - @b * @b * self.class::D
end

#numeratorQuadratic[d]

Returns its numerator.

Returns:



350
351
352
353
354
355
356
357
# File 'lib/quadratic_number.rb', line 350

def numerator
	an = @a.numerator
	ad = @a.denominator
	bn = @b.numerator
	bd = @b.denominator
	abd = ad.lcm(bd)
	self.class.new(an * (abd / ad), bn * (abd / bd))
end

#qconjQuadratic[d] Also known as: quadratic_conjugate

Returns its quadratic conjugate.

Returns:



366
367
368
# File 'lib/quadratic_number.rb', line 366

def qconj
	self.class.new(@a, -@b)
end

#quo(other) ⇒ Quadratic[d] Also known as: /

Performs division.

Parameters:

  • other (Numeric)

Returns:



178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/quadratic_number.rb', line 178

def quo(other)
	my_class = self.class
	if other.kind_of?(my_class)
		_a = other.a
		_b = other.b
		d = _a * _a - _b * _b * my_class::D
		self * my_class.new(_a.quo(d), -_b.quo(d))
	elsif __rational__(other)
		my_class.new(@a.quo(other), @b.quo(other))
	else
		__coerce_exec__(:quo, other)
	end
end

#traceInteger/Rational

Returns its trace.

Returns:

  • (Integer/Rational)


376
377
378
379
# File 'lib/quadratic_number.rb', line 376

def trace
	# self + self.qconj
	@a * 2
end