quaternion_c2 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +43 -0
- data/lib/quaternion_c2/arithmetic.rb +50 -6
- data/lib/quaternion_c2/attributes.rb +11 -0
- data/lib/quaternion_c2/base.rb +16 -0
- data/lib/quaternion_c2/classification.rb +23 -11
- data/lib/quaternion_c2/conversion.rb +164 -24
- data/lib/quaternion_c2/equality.rb +18 -1
- data/lib/quaternion_c2/to_type.rb +119 -11
- data/lib/quaternion_c2/unary.rb +33 -4
- data/lib/quaternion_c2/units.rb +33 -9
- data/lib/quaternion_c2/utils.rb +71 -31
- data/quaternion_c2.gemspec +1 -1
- metadata +14 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6990b36ad863bcfeb0d147530f06461f634b13cc
|
4
|
+
data.tar.gz: 04be92b14b2149067953ac61c73151aafa42edea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d62049c41e69f64f427932b91499399e122890472d963e2a2185959c197533f9fdac296301a8dd277866a74f710f6020b019fce30d584ae2f5c874b77c82593
|
7
|
+
data.tar.gz: b3dfa3a4691263ad104b5d9fc5669bd39708b1ad55ef680205f305f2012abc6512a6c93ebf93e9bb6ba660e1dc14b70a74b0e7f258da1aee3f02969ecfd4e9b2
|
data/README.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# Quaternion
|
2
2
|
|
3
|
+
This gem provides a `Quaternion` class highly compatible with other numeric classes.
|
4
|
+
|
5
|
+
The [quaternions](https://en.wikipedia.org/wiki/Quaternion) are an extension of complex numbers.
|
6
|
+
They have an important application to calculations of spatial rotations, such as in 3DCG.
|
7
|
+
|
8
|
+
A quaternion can be represented in several forms:
|
9
|
+
|
10
|
+
* Four real numbers: `$w + xi + yj + zk$`
|
11
|
+
* Two complex numbers: `$a + bj$`
|
12
|
+
* Scalar and 3-D vector: `$s + \vec{v}$`
|
13
|
+
* Polar form: `$r \exp{(\vec{n} \theta)}$`
|
14
|
+
|
15
|
+
where i, j, and k are imaginary units which satisfy $i^2 = j^2 = k^2 = ijk = -1$.
|
16
|
+
A scalar is a real quaternion and a vector is a pure imaginary quaternion.
|
17
|
+
|
18
|
+
The multiplication of quaternions is noncommutative unlike complex numbers.
|
19
|
+
|
3
20
|
## Installation
|
4
21
|
|
5
22
|
Add this line to your application's Gemfile:
|
@@ -45,6 +62,32 @@ Complex::I * Quaternion::J #=> (0+0i+0j+1k)
|
|
45
62
|
Quaternion(1, 1, 1, 1) ** 6 #=> (64+0i+0j+0k)
|
46
63
|
```
|
47
64
|
|
65
|
+
### Spatial rotations
|
66
|
+
|
67
|
+
Unit quaternions are often used to calculate 3-D rotations around arbitrary axes.
|
68
|
+
They have a compact form, avoid gimbal lock, and easily interpolate between themselves (see [Slerp](https://en.wikipedia.org/wiki/Slerp)).
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# theta = 120 * (Math::PI / 180)
|
72
|
+
# axis = Vector[1, 1, 1].normalize
|
73
|
+
# q = Quaternion.polar(1, theta / 2, axis)
|
74
|
+
q = Quaternion(0.5, 0.5, 0.5, 0.5)
|
75
|
+
r = Quaternion('2i+3j+4k')
|
76
|
+
|
77
|
+
r_new = q * r / q #=> (0.0+4.0i+2.0j+3.0k)
|
78
|
+
r_new = q * r * q.conj #=> (0.0+4.0i+2.0j+3.0k) # q.abs must be 1
|
79
|
+
```
|
80
|
+
|
81
|
+
More generally, a pair of unit quaternions represents a [4-D rotation](https://en.wikipedia.org/wiki/Rotations_in_4-dimensional_Euclidean_space).
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
ql = Quaternion(0.5, 0.5, -0.5, 0.5)
|
85
|
+
qr = Quaternion(0.5, -0.5, 0.5, 0.5)
|
86
|
+
r = Quaternion('1+2i+3j+4k')
|
87
|
+
|
88
|
+
r_new = ql * r * qr #=> (-4.0-3.0i-2.0j+1.0k)
|
89
|
+
```
|
90
|
+
|
48
91
|
## Development
|
49
92
|
|
50
93
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -2,12 +2,18 @@ require_relative 'base'
|
|
2
2
|
require_relative 'classification'
|
3
3
|
require_relative 'unary'
|
4
4
|
|
5
|
-
#
|
6
|
-
# Basic arithmetic operators (#+, #-, #*, #/)
|
7
|
-
# and related methods
|
8
|
-
#
|
9
|
-
|
10
5
|
class Quaternion
|
6
|
+
##
|
7
|
+
# Basic arithmetic operators (#+, #-, #*, #/)
|
8
|
+
# and related methods
|
9
|
+
#
|
10
|
+
|
11
|
+
##
|
12
|
+
# Performs addition.
|
13
|
+
#
|
14
|
+
# @param other [Numeric]
|
15
|
+
# @return [Quaternion]
|
16
|
+
#
|
11
17
|
def +(other)
|
12
18
|
if other.kind_of?(Quaternion)
|
13
19
|
__new__(@a + other.a, @b + other.b)
|
@@ -19,6 +25,12 @@ class Quaternion
|
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
28
|
+
##
|
29
|
+
# Performs subtraction.
|
30
|
+
#
|
31
|
+
# @param other [Numeric]
|
32
|
+
# @return [Quaternion]
|
33
|
+
#
|
22
34
|
def -(other)
|
23
35
|
if other.kind_of?(Quaternion)
|
24
36
|
__new__(@a - other.a, @b - other.b)
|
@@ -30,6 +42,12 @@ class Quaternion
|
|
30
42
|
end
|
31
43
|
end
|
32
44
|
|
45
|
+
##
|
46
|
+
# Performs multiplication.
|
47
|
+
#
|
48
|
+
# @param other [Numeric]
|
49
|
+
# @return [Quaternion]
|
50
|
+
#
|
33
51
|
def *(other)
|
34
52
|
if other.kind_of?(Quaternion)
|
35
53
|
_a = other.a
|
@@ -43,6 +61,25 @@ class Quaternion
|
|
43
61
|
end
|
44
62
|
end
|
45
63
|
|
64
|
+
##
|
65
|
+
# @!method quo(other)
|
66
|
+
#
|
67
|
+
# Performs division.
|
68
|
+
#
|
69
|
+
# @param other [Numeric]
|
70
|
+
# @return [Quaternion]
|
71
|
+
# @raise [ZeroDivisionError] if +other+ is exactly zero.
|
72
|
+
#
|
73
|
+
|
74
|
+
##
|
75
|
+
# @!method fdiv(other)
|
76
|
+
#
|
77
|
+
# Performs division as each part is a float, never returns a float.
|
78
|
+
#
|
79
|
+
# @param other [Numeric]
|
80
|
+
# @return [Quaternion]
|
81
|
+
#
|
82
|
+
|
46
83
|
[:quo, :fdiv].each do |sym|
|
47
84
|
define_method(sym) do |other|
|
48
85
|
if other.kind_of?(Quaternion)
|
@@ -58,10 +95,17 @@ class Quaternion
|
|
58
95
|
alias / quo
|
59
96
|
undef div, %, modulo, remainder, divmod
|
60
97
|
|
61
|
-
|
98
|
+
##
|
62
99
|
# Conversion
|
63
100
|
#
|
64
101
|
|
102
|
+
##
|
103
|
+
# Performs type conversion.
|
104
|
+
#
|
105
|
+
# @param other [Numeric]
|
106
|
+
# @return [[Quaternion, self]]
|
107
|
+
# @raise [TypeError]
|
108
|
+
#
|
65
109
|
def coerce(other)
|
66
110
|
if other.kind_of?(Quaternion)
|
67
111
|
[other, self]
|
@@ -9,12 +9,23 @@ class Quaternion
|
|
9
9
|
defined_methods = public_instance_methods
|
10
10
|
|
11
11
|
if defined_methods.include?(:finite?)
|
12
|
+
##
|
13
|
+
# Returns true if its magnitude is finite, oterwise returns false.
|
14
|
+
#
|
15
|
+
# @return [Boolean]
|
16
|
+
#
|
12
17
|
def finite?
|
13
18
|
abs.finite?
|
14
19
|
end
|
15
20
|
end
|
16
21
|
|
17
22
|
if defined_methods.include?(:infinite?)
|
23
|
+
##
|
24
|
+
# Returns values corresponding to the value of its magnitude.
|
25
|
+
#
|
26
|
+
# @return [nil] if finite.
|
27
|
+
# @return [+1] if positive infinity.
|
28
|
+
#
|
18
29
|
def infinite?
|
19
30
|
abs.infinite?
|
20
31
|
end
|
data/lib/quaternion_c2/base.rb
CHANGED
@@ -1,7 +1,23 @@
|
|
1
|
+
##
|
2
|
+
# This class +Quaternion+ is an analogue of +Complex+.
|
3
|
+
# * A subclass of +Numeric+
|
4
|
+
# * Immutable instances
|
5
|
+
# * Common / extended methods
|
6
|
+
# Please +require 'quaternion_c2'+ to load all functions.
|
7
|
+
#
|
8
|
+
# +Quaternion+ has many constructors to accept
|
9
|
+
# the various representations of a quaternion.
|
10
|
+
# It is recommended to use +Kernel.#Quaternion+ and +Quaternion.polar+.
|
11
|
+
#
|
12
|
+
# @see https://ruby-doc.org/core/Complex.html
|
13
|
+
#
|
1
14
|
class Quaternion < Numeric
|
2
15
|
attr_reader :a, :b
|
3
16
|
protected :a, :b
|
4
17
|
|
18
|
+
##
|
19
|
+
# @!visibility private
|
20
|
+
#
|
5
21
|
def initialize(a, b)
|
6
22
|
@a = a.to_c
|
7
23
|
@b = b.to_c
|
@@ -1,31 +1,43 @@
|
|
1
1
|
require_relative 'base'
|
2
2
|
|
3
|
-
#
|
4
|
-
# Number system classification
|
5
|
-
# (N <) Z < Q < R < C < H (< ...)
|
6
|
-
#
|
7
|
-
# * quaternion? : [H] Quaternion (*undefined*)
|
8
|
-
# * && complex? : [C] Complex
|
9
|
-
# * && real? : [R] Float, BigDecimal, Numeric
|
10
|
-
# * && rational? : [Q] Rational (*undefined*)
|
11
|
-
# * && integer? : [Z] Fixnum, Bignum, Integer
|
12
|
-
#
|
13
|
-
|
14
3
|
class Numeric
|
4
|
+
##
|
5
|
+
# Number system classification
|
6
|
+
# (N <) Z < Q < R < C < H (< ...)
|
7
|
+
#
|
8
|
+
# * quaternion? : [H] Quaternion (*undefined*)
|
9
|
+
# * && complex? : [C] Complex
|
10
|
+
# * && real? : [R] Float, BigDecimal, Numeric
|
11
|
+
# * && rational? : [Q] Rational (*undefined*)
|
12
|
+
# * && integer? : [Z] Fixnum, Bignum, Integer
|
13
|
+
#
|
14
|
+
|
15
15
|
# defined:
|
16
16
|
# * integer? #=> false
|
17
17
|
# * real? #=> true
|
18
18
|
|
19
|
+
##
|
20
|
+
# Returns true.
|
21
|
+
#
|
19
22
|
def complex?
|
20
23
|
true
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
24
27
|
class Quaternion
|
28
|
+
# defined by Numeric:
|
29
|
+
# * integer? #=> false
|
30
|
+
|
31
|
+
##
|
32
|
+
# Returns false.
|
33
|
+
#
|
25
34
|
def real?
|
26
35
|
false
|
27
36
|
end
|
28
37
|
|
38
|
+
##
|
39
|
+
# Returns false.
|
40
|
+
#
|
29
41
|
def complex?
|
30
42
|
false
|
31
43
|
end
|
@@ -8,11 +8,22 @@ require_relative 'to_type'
|
|
8
8
|
require 'matrix'
|
9
9
|
|
10
10
|
class Quaternion
|
11
|
-
|
11
|
+
##
|
12
12
|
# Constructors
|
13
13
|
#
|
14
14
|
|
15
15
|
class << self
|
16
|
+
##
|
17
|
+
# Returns a quaternion object a+bj, where both a and b are complex (or real).
|
18
|
+
#
|
19
|
+
# @param a [Complex]
|
20
|
+
# @param b [Complex]
|
21
|
+
# @return [Quaternion]
|
22
|
+
# @raise [TypeError]
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# Quaternion.rect(1, Complex::I) #=> (1+0i+0j+1k)
|
26
|
+
#
|
16
27
|
def rect(a, b = 0)
|
17
28
|
unless [a, b].all? { |c| c.kind_of?(Numeric) && c.complex? }
|
18
29
|
raise TypeError, 'not a complex'
|
@@ -21,6 +32,19 @@ class Quaternion
|
|
21
32
|
end
|
22
33
|
alias rectangular rect
|
23
34
|
|
35
|
+
##
|
36
|
+
# Returns a quaternion object w+xi+yj+zk, where all of w, x, y, and z are real.
|
37
|
+
#
|
38
|
+
# @param w [Real]
|
39
|
+
# @param x [Real]
|
40
|
+
# @param y [Real]
|
41
|
+
# @param z [Real]
|
42
|
+
# @return [Quaternion]
|
43
|
+
# @raise [TypeError]
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# Quaternion.hrect(1, 2, 3, 4) #=> (1+2i+3j+4k)
|
47
|
+
#
|
24
48
|
def hrect(w, x = 0, y = 0, z = 0)
|
25
49
|
a = Complex.rect(w, x)
|
26
50
|
b = Complex.rect(y, z)
|
@@ -28,8 +52,26 @@ class Quaternion
|
|
28
52
|
end
|
29
53
|
alias hyperrectangular hrect
|
30
54
|
|
31
|
-
|
32
|
-
#
|
55
|
+
##
|
56
|
+
# Returns a quaternion object which denotes the given polar form.
|
57
|
+
# The actual angle is recognized as +theta * vector.norm+.
|
58
|
+
#
|
59
|
+
# @param r [Real] absolute value
|
60
|
+
# @param theta [Real] angle in radians
|
61
|
+
# @param vector [Enumerable] 3-D vector
|
62
|
+
# @return [Quaternion]
|
63
|
+
# @raise [TypeError]
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# Quaternion.polar(1, Math::PI/3, Vector[1,1,1].normalize)
|
67
|
+
# #=> (0.5000000000000001+0.5i+0.5j+0.5k)
|
68
|
+
# Quaternion.polar(1, 1, Math::PI/3 * Vector[1,1,1].normalize)
|
69
|
+
# #=> (0.4999999999999999+0.5i+0.5j+0.5k)
|
70
|
+
#
|
71
|
+
# r, theta = -3, -1
|
72
|
+
# Quaternion.polar(r) == r #=> true
|
73
|
+
# Quaternion.polar(r, theta) == Complex.polar(r, theta) #=> true
|
74
|
+
#
|
33
75
|
def polar(r, theta = 0, vector = Vector[1, 0, 0])
|
34
76
|
unless vector.kind_of?(Enumerable) && vector.size == 3
|
35
77
|
raise TypeError, 'not a 3-D vector'
|
@@ -49,25 +91,57 @@ class Quaternion
|
|
49
91
|
end
|
50
92
|
end
|
51
93
|
|
52
|
-
|
94
|
+
##
|
53
95
|
# Accessors
|
54
96
|
#
|
55
97
|
|
98
|
+
##
|
99
|
+
# Returns an array of two complex numbers.
|
100
|
+
#
|
101
|
+
# @return [[Complex, Complex]]
|
102
|
+
#
|
103
|
+
# @example
|
104
|
+
# Quaternion('1+2i+3j+4k').rect #=> [(1+2i), (3+4i)]
|
105
|
+
#
|
56
106
|
def rect
|
57
107
|
[@a, @b]
|
58
108
|
end
|
59
109
|
alias rectangular rect
|
60
110
|
|
111
|
+
##
|
112
|
+
# Returns an array of four real numbers.
|
113
|
+
#
|
114
|
+
# @return [[Real, Real, Real, Real]]
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# Quaternion('1+2i+3j+4k').hrect #=> [1, 2, 3, 4]
|
118
|
+
#
|
61
119
|
def hrect
|
62
120
|
rect.flat_map(&:rect)
|
63
121
|
end
|
64
122
|
alias hyperrectangular hrect
|
65
123
|
|
124
|
+
##
|
125
|
+
# Returns the real part.
|
126
|
+
#
|
127
|
+
# @return [Real]
|
128
|
+
#
|
129
|
+
# @example
|
130
|
+
# Quaternion('1+2i+3j+4k').real #=> 1
|
131
|
+
#
|
66
132
|
def real
|
67
133
|
@a.real
|
68
134
|
end
|
69
135
|
alias scalar real
|
70
136
|
|
137
|
+
##
|
138
|
+
# Returns the imaginary part as a 3-D vector.
|
139
|
+
#
|
140
|
+
# @return [Vector] 3-D vector
|
141
|
+
#
|
142
|
+
# @example
|
143
|
+
# Quaternion('1+2i+3j+4k').imag #=> Vector[2, 3, 4]
|
144
|
+
#
|
71
145
|
def imag
|
72
146
|
Vector[*hrect.drop(1)]
|
73
147
|
end
|
@@ -78,6 +152,14 @@ class Quaternion
|
|
78
152
|
# * abs
|
79
153
|
# * magnitude
|
80
154
|
|
155
|
+
##
|
156
|
+
# Returns the angle part of its polar form.
|
157
|
+
#
|
158
|
+
# @return [Real]
|
159
|
+
#
|
160
|
+
# @example
|
161
|
+
# Quaternion('1+i+j+k').arg #=> Math::PI/3
|
162
|
+
#
|
81
163
|
def arg
|
82
164
|
r_cos = real
|
83
165
|
r_sin = imag.norm
|
@@ -86,6 +168,14 @@ class Quaternion
|
|
86
168
|
alias angle arg
|
87
169
|
alias phase arg
|
88
170
|
|
171
|
+
##
|
172
|
+
# Returns the axis part of its polar form.
|
173
|
+
#
|
174
|
+
# @return [Vector] normalized 3-D vector
|
175
|
+
#
|
176
|
+
# @example
|
177
|
+
# Quaternion('1+i+j+k').axis #=> Vector[1,1,1].normalize
|
178
|
+
#
|
89
179
|
def axis
|
90
180
|
v = imag
|
91
181
|
norm = v.norm
|
@@ -100,24 +190,55 @@ class Quaternion
|
|
100
190
|
end
|
101
191
|
end
|
102
192
|
|
193
|
+
##
|
194
|
+
# Returns an array; +[q.abs, q.arg, q.axis]+.
|
195
|
+
#
|
196
|
+
# @return [[Real, Real, Vector]]
|
197
|
+
#
|
198
|
+
# @example
|
199
|
+
# Quaternion('1+i+j+k').polar
|
200
|
+
# #=> [2.0, Math::PI/3, Vector[1,1,1].normalize]
|
201
|
+
#
|
103
202
|
def polar
|
104
203
|
[abs, arg, axis]
|
105
204
|
end
|
106
205
|
end
|
107
206
|
|
108
|
-
#
|
109
|
-
# Generic constructor
|
110
|
-
#
|
111
|
-
# This accepts various arguments:
|
112
|
-
# * (Numeric) -> real quaternion
|
113
|
-
# * (Numeric, Numeric) -> a+bj
|
114
|
-
# * (Numeric, Vector) -> scalar and 3-D vector
|
115
|
-
# * (Numeric, Numeric, Numeric[, Numeric]) -> w+xi+yj+zk
|
116
|
-
# and String instead of Numeric.
|
117
|
-
#
|
118
207
|
module Kernel
|
119
208
|
module_function
|
120
209
|
|
210
|
+
##
|
211
|
+
# Returns a quaternion.
|
212
|
+
#
|
213
|
+
# This function accepts various arguments except polar form.
|
214
|
+
# Strings are parsed to +Numeric+.
|
215
|
+
#
|
216
|
+
# @overload Quaternion(w, x, y, z)
|
217
|
+
# @param w [Numeric]
|
218
|
+
# @param x [Numeric]
|
219
|
+
# @param y [Numeric]
|
220
|
+
# @param z [Numeric]
|
221
|
+
# @return [Quaternion] w+xi+yj+zk
|
222
|
+
# @overload Quaternion(a, b)
|
223
|
+
# @param a [Numeric]
|
224
|
+
# @param b [Numeric]
|
225
|
+
# @return [Quaternion] a+bj
|
226
|
+
# @overload Quaternion(s, v)
|
227
|
+
# @param s [Numeric] scalar part
|
228
|
+
# @param v [Enumerable] vector part
|
229
|
+
# @return [Quaternion] $s+\\vec!{v}$
|
230
|
+
# @overload Quaternion(str)
|
231
|
+
# @param str [String]
|
232
|
+
# @return [Quaternion] +str.to_q+
|
233
|
+
# @raise [ArgumentError] if its format is inexact.
|
234
|
+
#
|
235
|
+
# @example
|
236
|
+
# Quaternion(1) #=> (1+0i+0j+0k)
|
237
|
+
# Quaternion(1, 2) #=> (1+0i+2j+0k) # not (1+2i+0j+0k)
|
238
|
+
# Quaternion(1, 2, 3, 4) #=> (1+2i+3j+4k)
|
239
|
+
# Quaternion(1, [2, 3, 4]) #=> (1+2i+3j+4k)
|
240
|
+
# Quaternion('1+2i+3j+4k') #=> (1+2i+3j+4k)
|
241
|
+
#
|
121
242
|
def Quaternion(*args)
|
122
243
|
argc = args.size
|
123
244
|
|
@@ -141,26 +262,45 @@ module Kernel
|
|
141
262
|
end
|
142
263
|
|
143
264
|
case argc
|
265
|
+
when 1
|
266
|
+
arg = args[0]
|
267
|
+
if arg.kind_of?(Numeric)
|
268
|
+
# a quaternion (or an octonion, etc.)
|
269
|
+
return arg.complex? ? arg.to_q : arg
|
270
|
+
else
|
271
|
+
raise TypeError,
|
272
|
+
"can't convert #{arg.class} into Quaternion"
|
273
|
+
end
|
144
274
|
when 2
|
145
275
|
if args[1].kind_of?(Enumerable)
|
146
|
-
# scalar and 3-D vector
|
276
|
+
# scalar and 3-D vector -> expand
|
147
277
|
if args[1].size != 3
|
148
278
|
raise TypeError, "not a 3-D vector"
|
149
279
|
end
|
150
|
-
args
|
151
|
-
|
280
|
+
args.flatten!(1)
|
281
|
+
elsif args.all? { |x| x.kind_of?(Numeric) && x.complex? }
|
152
282
|
# a pair of complex numbers
|
283
|
+
return Quaternion.send(:new, *args)
|
284
|
+
else
|
153
285
|
return args[0] + args[1] * Quaternion::J
|
154
286
|
end
|
155
287
|
end
|
156
288
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
289
|
+
w, x, y, z = args
|
290
|
+
z ||= 0
|
291
|
+
if args.all? { |x| x.kind_of?(Numeric) && x.real? }
|
292
|
+
# 3 or 4 real numbers
|
293
|
+
a = Complex.rect(w, x)
|
294
|
+
b = Complex.rect(y, z)
|
295
|
+
Quaternion.send(:new, a, b)
|
296
|
+
else
|
297
|
+
a = Complex(w, x)
|
298
|
+
b = Complex(y, z)
|
299
|
+
if [a, b].all? { |x| x.kind_of?(Numeric) && x.complex? }
|
300
|
+
Quaternion.send(:new, a, b)
|
301
|
+
else
|
302
|
+
a + b * Quaternion::J
|
303
|
+
end
|
164
304
|
end
|
165
305
|
end
|
166
306
|
end
|
@@ -5,6 +5,12 @@ class Quaternion
|
|
5
5
|
undef <=>
|
6
6
|
undef_method(*Comparable.instance_methods)
|
7
7
|
|
8
|
+
##
|
9
|
+
# Returns true if it equals to the other numerically.
|
10
|
+
#
|
11
|
+
# @param other [Object]
|
12
|
+
# @return [Boolean]
|
13
|
+
#
|
8
14
|
def ==(other)
|
9
15
|
if other.kind_of?(Quaternion)
|
10
16
|
@a == other.a && @b == other.b
|
@@ -15,6 +21,12 @@ class Quaternion
|
|
15
21
|
end
|
16
22
|
end
|
17
23
|
|
24
|
+
##
|
25
|
+
# Returns true if two quaternions have same reals.
|
26
|
+
#
|
27
|
+
# @param other [Object]
|
28
|
+
# @return [Boolean]
|
29
|
+
#
|
18
30
|
def eql?(other)
|
19
31
|
if other.kind_of?(Quaternion)
|
20
32
|
@a.eql?(other.a) && @b.eql?(other.b)
|
@@ -23,8 +35,13 @@ class Quaternion
|
|
23
35
|
end
|
24
36
|
end
|
25
37
|
|
26
|
-
|
38
|
+
##
|
39
|
+
# Returns a hash.
|
40
|
+
#
|
41
|
+
# @return [Integer]
|
42
|
+
#
|
27
43
|
def hash
|
44
|
+
# q1.eql?(q2) -> q1.hash == q2.hash
|
28
45
|
[@a, @b].hash
|
29
46
|
end
|
30
47
|
end
|
@@ -1,18 +1,29 @@
|
|
1
1
|
require_relative 'base'
|
2
2
|
require_relative 'unary'
|
3
3
|
|
4
|
-
#
|
5
|
-
# to_xxx
|
6
|
-
#
|
7
|
-
|
8
4
|
class Quaternion
|
5
|
+
##
|
6
|
+
# to_xxx
|
7
|
+
#
|
8
|
+
|
9
9
|
# defined by Numeric:
|
10
10
|
# * to_int #=> to_i
|
11
11
|
|
12
|
+
##
|
13
|
+
# Returns self.
|
14
|
+
#
|
15
|
+
# @return [self]
|
16
|
+
#
|
12
17
|
def to_q
|
13
18
|
self
|
14
19
|
end
|
15
20
|
|
21
|
+
##
|
22
|
+
# Returns the value as a complex if possible (the discarded part should be exactly zero).
|
23
|
+
#
|
24
|
+
# @return [Complex]
|
25
|
+
# @raise [RangeError] if its yj+zk part is not exactly zero.
|
26
|
+
#
|
16
27
|
def to_c
|
17
28
|
unless __exact_zero__(@b)
|
18
29
|
raise RangeError, "can't convert #{self} into Complex"
|
@@ -20,14 +31,71 @@ class Quaternion
|
|
20
31
|
@a
|
21
32
|
end
|
22
33
|
|
34
|
+
##
|
35
|
+
# @!method to_f
|
36
|
+
#
|
37
|
+
# Returns the value as a float if possible (the imaginary part should be exactly zero).
|
38
|
+
#
|
39
|
+
# @return [Float]
|
40
|
+
# @raise [RangeError] if its imaginary part is not exactly zero.
|
41
|
+
#
|
42
|
+
|
43
|
+
##
|
44
|
+
# @!method to_r
|
45
|
+
#
|
46
|
+
# Returns the value as a rational if possible (the imaginary part should be exactly zero).
|
47
|
+
#
|
48
|
+
# @return [Rational]
|
49
|
+
# @raise [RangeError] if its imaginary part is not exactly zero.
|
50
|
+
#
|
51
|
+
|
52
|
+
##
|
53
|
+
# @!method to_i
|
54
|
+
#
|
55
|
+
# Returns the value as an integer if possible (the imaginary part should be exactly zero).
|
56
|
+
#
|
57
|
+
# @return [Integer]
|
58
|
+
# @raise [RangeError] if its imaginary part is not exactly zero.
|
59
|
+
#
|
60
|
+
|
61
|
+
##
|
62
|
+
# @!method rationalize(eps = 0)
|
63
|
+
#
|
64
|
+
# Returns the value as a rational if possible (the imaginary part should be exactly zero).
|
65
|
+
#
|
66
|
+
# @param eps [Real]
|
67
|
+
# @return [Rational]
|
68
|
+
# @raise [RangeError] if its imaginary part is not exactly zero.
|
69
|
+
#
|
70
|
+
|
23
71
|
[:to_f, :to_r, :to_i, :rationalize].each do |sym|
|
24
72
|
define_method(sym) { |*args| to_c.send(sym, *args) }
|
25
73
|
end
|
26
74
|
|
75
|
+
##
|
76
|
+
# Returns the value as a string.
|
77
|
+
#
|
78
|
+
# @return [String]
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# str = '1-2i-3/4j+0.56k'
|
82
|
+
# q = str.to_q #=> (1-2i-(3/4)*j+0.56k)
|
83
|
+
# q.to_s #=> "1-2i-3/4j+0.56k"
|
84
|
+
#
|
27
85
|
def to_s
|
28
86
|
__format__(:to_s)
|
29
87
|
end
|
30
88
|
|
89
|
+
##
|
90
|
+
# Returns the value as a string for inspection.
|
91
|
+
#
|
92
|
+
# @return [String]
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# str = '1-2i-3/4j+0.56k'
|
96
|
+
# q = str.to_q #=> (1-2i-(3/4)*j+0.56k)
|
97
|
+
# q.inspect #=> "(1-2i-(3/4)*j+0.56k)"
|
98
|
+
#
|
31
99
|
def inspect
|
32
100
|
"(#{__format__(:inspect)})"
|
33
101
|
end
|
@@ -53,6 +121,9 @@ class Quaternion
|
|
53
121
|
end
|
54
122
|
|
55
123
|
class << self
|
124
|
+
##
|
125
|
+
# @!visibility private
|
126
|
+
#
|
56
127
|
def parse(str, strict = false)
|
57
128
|
regexp = %r{
|
58
129
|
\A
|
@@ -65,16 +136,20 @@ class Quaternion
|
|
65
136
|
|\z
|
66
137
|
(?'head_or_sign' (?<=^|\s) [+-]?+ | [+-])
|
67
138
|
(?'digits' \d++ (?:#{strict ? "_" : "_++"} \d++)*+)
|
68
|
-
(?'int_or_float' (?:\g'digits')?+ (?:\. \g'digits')?+
|
139
|
+
(?'int_or_float' (?:\g'digits')?+ (?:\. \g'digits')?+
|
140
|
+
(?<=\d) (?:e [+-]?+ \g'digits')?+)
|
69
141
|
(?'rational' \g'int_or_float' (?:/ \g'digits')?+)
|
70
142
|
}xi
|
71
143
|
|
72
|
-
|
73
|
-
|
74
|
-
|
144
|
+
# regexp.match(str) always succeeds (even if str is empty)
|
145
|
+
components = regexp.match(str).captures[0,4]
|
146
|
+
components = components.reverse.drop_while(&:nil?).reverse
|
147
|
+
if strict && (!$'.empty? || components.empty?)
|
148
|
+
raise ArgumentError,
|
149
|
+
"invalid value for convert(): #{str.inspect}"
|
75
150
|
end
|
76
151
|
|
77
|
-
|
152
|
+
components.collect! do |s|
|
78
153
|
case s
|
79
154
|
when %r{/} then s.to_r
|
80
155
|
when %r{[.eE]} then s.to_f
|
@@ -84,24 +159,57 @@ class Quaternion
|
|
84
159
|
end
|
85
160
|
end
|
86
161
|
|
87
|
-
|
162
|
+
# returns the parsed number as a preferred type
|
163
|
+
case components.size
|
164
|
+
when 0
|
165
|
+
0
|
166
|
+
when 1
|
167
|
+
components[0]
|
168
|
+
when 2
|
169
|
+
Complex.rect(*components)
|
170
|
+
else # 3 or 4
|
171
|
+
w, x, y, z = components
|
172
|
+
new(Complex.rect(w, x), Complex.rect(y, z || 0))
|
173
|
+
end
|
88
174
|
end
|
89
175
|
end
|
90
176
|
end
|
91
177
|
|
92
178
|
class Numeric
|
179
|
+
##
|
180
|
+
# Returns the value as a quaternion.
|
181
|
+
#
|
182
|
+
# @return [Quaternion]
|
183
|
+
#
|
93
184
|
def to_q
|
94
185
|
Quaternion.send(:new, self, 0)
|
95
186
|
end
|
96
187
|
end
|
97
188
|
|
98
189
|
class String
|
190
|
+
##
|
191
|
+
# Returns a quaternion which denotes the string form.
|
192
|
+
# The parser ignores leading whitespaces and trailing garbage.
|
193
|
+
# Any digit sequences can be separated by an underscore.
|
194
|
+
# Returns zero for null or garbage string.
|
195
|
+
#
|
196
|
+
# @return [Quaternion]
|
197
|
+
#
|
198
|
+
# @example
|
199
|
+
# "1-2i-3/4j+0.56k".to_q #=> (1-2i-(3/4)*j+0.56k)
|
200
|
+
# "foobarbaz".to_q #=> (0+0i+0j+0k)
|
201
|
+
#
|
99
202
|
def to_q
|
100
|
-
Quaternion.send(:parse, self, false)
|
203
|
+
Quaternion.send(:parse, self, false).to_q
|
101
204
|
end
|
102
205
|
end
|
103
206
|
|
104
207
|
class NilClass
|
208
|
+
##
|
209
|
+
# Returns zero as a quaternion.
|
210
|
+
#
|
211
|
+
# @return [Quaternion]
|
212
|
+
#
|
105
213
|
def to_q
|
106
214
|
Quaternion.send(:new, 0, 0)
|
107
215
|
end
|
data/lib/quaternion_c2/unary.rb
CHANGED
@@ -1,23 +1,47 @@
|
|
1
1
|
require_relative 'base'
|
2
2
|
|
3
|
-
#
|
4
|
-
# Unary operations
|
5
|
-
#
|
6
|
-
|
7
3
|
class Quaternion
|
4
|
+
##
|
5
|
+
# Unary operations
|
6
|
+
#
|
7
|
+
|
8
8
|
# defined by Numeric:
|
9
9
|
# * +@ #=> self
|
10
10
|
# * -@ #=> 0 - self
|
11
11
|
|
12
|
+
##
|
13
|
+
# Returns its conjugate.
|
14
|
+
#
|
15
|
+
# @return [Quaternion]
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# Quaternion(1, 2, 3, 4).conj #=> (1-2i-3j-4k)
|
19
|
+
#
|
12
20
|
def conj
|
13
21
|
__new__(@a.conj, -@b)
|
14
22
|
end
|
15
23
|
alias conjugate conj
|
16
24
|
|
25
|
+
##
|
26
|
+
# Returns square of the absolute value.
|
27
|
+
#
|
28
|
+
# @return [Real]
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# Quaternion(-1, 1, -1, 1).abs2 #=> 4
|
32
|
+
#
|
17
33
|
def abs2
|
18
34
|
@a.abs2 + @b.abs2
|
19
35
|
end
|
20
36
|
|
37
|
+
##
|
38
|
+
# Returns the absolute part of its polar form.
|
39
|
+
#
|
40
|
+
# @return [Real]
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# Quaternion(-1, 1, -1, 1).abs #=> 2.0
|
44
|
+
#
|
21
45
|
def abs
|
22
46
|
a_abs = @a.abs
|
23
47
|
b_abs = @b.abs
|
@@ -40,4 +64,9 @@ class Quaternion
|
|
40
64
|
else
|
41
65
|
false
|
42
66
|
end
|
67
|
+
|
68
|
+
def __reciprocal__
|
69
|
+
d2 = abs2
|
70
|
+
__new__(@a.conj.quo(d2), @b.quo(-d2))
|
71
|
+
end
|
43
72
|
end
|
data/lib/quaternion_c2/units.rb
CHANGED
@@ -1,33 +1,57 @@
|
|
1
1
|
require_relative 'base'
|
2
2
|
|
3
|
-
#
|
4
|
-
# Fundamental quaternion units
|
5
|
-
#
|
6
|
-
|
7
3
|
class Quaternion
|
4
|
+
##
|
5
|
+
# Fundamental quaternion units
|
6
|
+
#
|
7
|
+
|
8
8
|
i = Complex::I
|
9
9
|
I = new(i, 0)
|
10
10
|
J = new(0, 1)
|
11
11
|
K = new(0, i)
|
12
12
|
end
|
13
13
|
|
14
|
-
#
|
15
|
-
# Convert to an imaginary part
|
16
|
-
#
|
17
|
-
|
18
14
|
class Numeric
|
15
|
+
##
|
16
|
+
# Convert to an imaginary part
|
17
|
+
#
|
18
|
+
|
19
19
|
# defined:
|
20
|
-
# *
|
20
|
+
# * i #=> Complex.rect(0, self)
|
21
21
|
|
22
|
+
##
|
23
|
+
# Returns the corresponding imaginary number.
|
24
|
+
# Not available for quaternions.
|
25
|
+
#
|
26
|
+
# @return [Quaternion] +self * Quaternion::J+
|
27
|
+
# @raise [NoMethodError] if +self+ is not a complex.
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# 3.j #=> (0+0i+3j+0k)
|
31
|
+
# Complex(3, 4).j #=> (0+0i+3j+4k)
|
32
|
+
#
|
22
33
|
def j
|
23
34
|
Quaternion.send(:new, 0, self)
|
24
35
|
end
|
25
36
|
|
37
|
+
##
|
38
|
+
# Returns the corresponding imaginary number.
|
39
|
+
# Not available for complex numbers.
|
40
|
+
#
|
41
|
+
# @return [Quaternion] +self * Quaternion::K+
|
42
|
+
# @raise [NoMethodError] if +self+ is not a real.
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# 4.k #=> (0+0i+0j+4k)
|
46
|
+
#
|
26
47
|
def k
|
27
48
|
Quaternion.send(:new, 0, self.i)
|
28
49
|
end
|
29
50
|
end
|
30
51
|
|
52
|
+
##
|
53
|
+
# @!parse class Complex < Numeric; end
|
54
|
+
#
|
31
55
|
class Complex
|
32
56
|
# undefined:
|
33
57
|
# * #i
|
data/lib/quaternion_c2/utils.rb
CHANGED
@@ -5,58 +5,98 @@ require_relative 'arithmetic'
|
|
5
5
|
require_relative 'conversion'
|
6
6
|
|
7
7
|
class Quaternion
|
8
|
+
##
|
9
|
+
# Performs exponentiation.
|
10
|
+
#
|
11
|
+
# @param index [Numeric]
|
12
|
+
# @return [Quaternion]
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# Quaternion(1, 1, 1, 1) ** 6 #=> (64+0i+0j+0k)
|
16
|
+
# Math::E.to_q ** Quaternion(Math.log(2), [Math::PI/3 / Math.sqrt(3)] * 3)
|
17
|
+
# #=> (1.0000000000000002+1.0i+1.0j+1.0k)
|
18
|
+
#
|
8
19
|
def **(index)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
20
|
+
unless index.kind_of?(Numeric)
|
21
|
+
num1, num2 = index.coerce(self)
|
22
|
+
return num1 ** num2
|
23
|
+
end
|
24
|
+
|
25
|
+
if __exact_zero__(index)
|
26
|
+
return __new__(1, 0)
|
27
|
+
end
|
13
28
|
|
14
|
-
|
29
|
+
# complex -> real
|
30
|
+
# (only if the imaginary part is exactly zero)
|
31
|
+
unless index.real?
|
15
32
|
begin
|
16
33
|
index.to_f
|
17
34
|
rescue
|
18
35
|
else
|
19
36
|
index = index.real
|
20
37
|
end
|
38
|
+
end
|
21
39
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
40
|
+
# rational -> integer
|
41
|
+
if index.kind_of?(Rational) && index.denominator == 1
|
42
|
+
index = index.numerator
|
43
|
+
end
|
26
44
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
45
|
+
# calc and return
|
46
|
+
if index.integer?
|
47
|
+
# exponentiation by squaring
|
48
|
+
x = (index >= 0) ? self : __reciprocal__
|
49
|
+
n = index.abs
|
31
50
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
elsif index.real?
|
40
|
-
r, theta, vector = polar
|
41
|
-
return Quaternion.polar(r ** index, theta * index, vector)
|
42
|
-
elsif index.complex? || index.kind_of?(Quaternion)
|
43
|
-
r, theta, vector = polar
|
44
|
-
q = Math.log(r) + Quaternion.hrect(0, *(theta * vector))
|
45
|
-
q *= index
|
46
|
-
return Quaternion.polar(Math.exp(q.real), 1, q.imag)
|
51
|
+
z = __new__(1, 0)
|
52
|
+
while true
|
53
|
+
z *= x if n.odd?
|
54
|
+
n >>= 1
|
55
|
+
return z if n.zero?
|
56
|
+
x *= x
|
47
57
|
end
|
58
|
+
elsif index.real?
|
59
|
+
r, theta, vector = polar
|
60
|
+
Quaternion.polar(r ** index, theta * index, vector)
|
61
|
+
elsif index.complex? || index.kind_of?(Quaternion)
|
62
|
+
# assume that log(self) commutes with index under multiplication
|
63
|
+
r, theta, vector = polar
|
64
|
+
q = Quaternion.hrect(Math.log(r), *(theta * vector))
|
65
|
+
q *= index
|
66
|
+
Quaternion.polar(Math.exp(q.real), 1, q.imag)
|
67
|
+
else
|
68
|
+
num1, num2 = index.coerce(self)
|
69
|
+
num1 ** num2
|
48
70
|
end
|
49
|
-
|
50
|
-
num1, num2 = other.coerce(self)
|
51
|
-
num1 ** num2
|
52
71
|
end
|
53
72
|
|
73
|
+
##
|
74
|
+
# Returns the denominator (lcm of all components' denominators).
|
75
|
+
# @see numerator
|
76
|
+
#
|
77
|
+
# @return [Integer]
|
78
|
+
#
|
54
79
|
def denominator
|
55
80
|
ad = @a.denominator
|
56
81
|
bd = @b.denominator
|
57
82
|
ad.lcm(bd)
|
58
83
|
end
|
59
84
|
|
85
|
+
##
|
86
|
+
# Returns the numerator.
|
87
|
+
#
|
88
|
+
# 1 1 1 3 4-6i-12j+9k <- numerator
|
89
|
+
# - - -i - -j + -k -> -----------
|
90
|
+
# 3 2 1 4 12 <- denominator
|
91
|
+
#
|
92
|
+
# @return [Quaternion]
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# q = Quaternion('1/3-1/2i-j+3/4k') #=> ((1/3)-(1/2)*i-1j+(3/4)*k)
|
96
|
+
# n = q.numerator #=> (4-6i-12j+9k)
|
97
|
+
# d = q.denominator #=> 12
|
98
|
+
# n / d == q #=> true
|
99
|
+
#
|
60
100
|
def numerator
|
61
101
|
an = @a.numerator
|
62
102
|
bn = @b.numerator
|
data/quaternion_c2.gemspec
CHANGED
metadata
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quaternion_c2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masahiro Nomoto
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.14'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.14'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '10.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '3.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.0'
|
55
55
|
description: This provides a numeric class Quaternion which is similar to a built-in
|
@@ -60,9 +60,9 @@ executables: []
|
|
60
60
|
extensions: []
|
61
61
|
extra_rdoc_files: []
|
62
62
|
files:
|
63
|
-
- .gitignore
|
64
|
-
- .rspec
|
65
|
-
- .travis.yml
|
63
|
+
- ".gitignore"
|
64
|
+
- ".rspec"
|
65
|
+
- ".travis.yml"
|
66
66
|
- Gemfile
|
67
67
|
- LICENSE.txt
|
68
68
|
- README.md
|
@@ -91,17 +91,17 @@ require_paths:
|
|
91
91
|
- lib
|
92
92
|
required_ruby_version: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
98
|
requirements:
|
99
|
-
- -
|
99
|
+
- - ">="
|
100
100
|
- !ruby/object:Gem::Version
|
101
101
|
version: '0'
|
102
102
|
requirements: []
|
103
103
|
rubyforge_project:
|
104
|
-
rubygems_version: 2.
|
104
|
+
rubygems_version: 2.6.8
|
105
105
|
signing_key:
|
106
106
|
specification_version: 4
|
107
107
|
summary: Quaternion class
|