vector_number 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/vector_number/comparing.rb +110 -107
- data/lib/vector_number/converting.rb +152 -147
- data/lib/vector_number/enumerating.rb +107 -108
- data/lib/vector_number/math_converting.rb +113 -109
- data/lib/vector_number/mathing.rb +305 -312
- data/lib/vector_number/numeric_refinements.rb +22 -3
- data/lib/vector_number/querying.rb +143 -136
- data/lib/vector_number/stringifying.rb +79 -82
- data/lib/vector_number/version.rb +1 -1
- data/lib/vector_number.rb +125 -55
- data/sig/vector_number.rbs +128 -161
- metadata +6 -6
|
@@ -8,12 +8,18 @@ class VectorNumber
|
|
|
8
8
|
# - refinement for +Complex#<=>+ to work with classes implementing +<=>+;
|
|
9
9
|
# - refinement for +Kernel#BigDecimal+ to work with classes implementing +to_d+.
|
|
10
10
|
#
|
|
11
|
+
# @note Refinements won't work on Ruby 3.0.
|
|
12
|
+
#
|
|
11
13
|
# @example activating refinements
|
|
12
14
|
# require "vector_number/numeric_refinements"
|
|
13
15
|
# using VectorNumber::NumericRefinements
|
|
14
16
|
#
|
|
15
17
|
# @since 0.2.0
|
|
16
18
|
module NumericRefinements
|
|
19
|
+
# CommutativeShuttle refinement works only on 3.1, so it almost never actually runs.
|
|
20
|
+
# There are tests for the correct behavior, however, so it's fine.
|
|
21
|
+
# :nocov:
|
|
22
|
+
|
|
17
23
|
# Refinement module to provide a +#<=>+ method that can work backwards.
|
|
18
24
|
#
|
|
19
25
|
# @note Currently only applies to Complex on *3.1*,
|
|
@@ -41,8 +47,13 @@ class VectorNumber
|
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
if (Complex(1, 0) <=> VectorNumber[1]).nil?
|
|
44
|
-
refine(Complex)
|
|
50
|
+
refine(Complex) do
|
|
51
|
+
import_methods CommutativeShuttle
|
|
52
|
+
rescue
|
|
53
|
+
warn "Numeric refinements are not available on Ruby < 3.1"
|
|
54
|
+
end
|
|
45
55
|
end
|
|
56
|
+
# :nocov:
|
|
46
57
|
|
|
47
58
|
# Refinement module to change Kernel#BigDecimal so it works with +#to_d+.
|
|
48
59
|
#
|
|
@@ -68,11 +79,19 @@ class VectorNumber
|
|
|
68
79
|
if value.respond_to?(:to_d)
|
|
69
80
|
ndigits.nil? ? value.to_d : value.to_d(ndigits)
|
|
70
81
|
else
|
|
71
|
-
ndigits.nil? ? super(value, exception:) : super
|
|
82
|
+
ndigits.nil? ? super(value, exception: exception) : super
|
|
72
83
|
end
|
|
73
84
|
end
|
|
74
85
|
end
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
# :nocov:
|
|
88
|
+
if defined?(BigDecimal)
|
|
89
|
+
refine(Kernel) do
|
|
90
|
+
import_methods BigDecimalToD
|
|
91
|
+
rescue
|
|
92
|
+
warn "Numeric refinements are not available on Ruby < 3.1"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
# :nocov:
|
|
77
96
|
end
|
|
78
97
|
end
|
|
@@ -1,150 +1,157 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class VectorNumber
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
# Whether this VectorNumber can be considered strictly numeric, e.g. real or complex.
|
|
8
|
-
#
|
|
9
|
-
# @example
|
|
10
|
-
# VectorNumber[2].numeric? # => true
|
|
11
|
-
# VectorNumber[2, 3i].numeric? # => true
|
|
12
|
-
# VectorNumber[2, "a"].numeric? # => false
|
|
13
|
-
# VectorNumber[2, 3i].numeric?(1) # => false
|
|
14
|
-
#
|
|
15
|
-
# @param dimensions [Integer] number of dimensions to consider "numeric"
|
|
16
|
-
# - 0 — zero
|
|
17
|
-
# - 1 — real number
|
|
18
|
-
# - 2 — complex number, etc.
|
|
19
|
-
# @return [Boolean]
|
|
20
|
-
# @raise [ArgumentError] if +dimensions+ is negative
|
|
21
|
-
#
|
|
22
|
-
# @since 0.2.0
|
|
23
|
-
def numeric?(dimensions = 2)
|
|
24
|
-
raise ArgumentError, "`dimensions` must be non-negative" unless dimensions >= 0
|
|
4
|
+
# @group Querying
|
|
5
|
+
#
|
|
6
|
+
# Mostly modelled after {::Complex}.
|
|
25
7
|
|
|
26
|
-
|
|
27
|
-
|
|
8
|
+
# Whether this VectorNumber can be considered strictly numeric — real or complex.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# VectorNumber[2].numeric? # => true
|
|
12
|
+
# VectorNumber[2, 3i].numeric? # => true
|
|
13
|
+
# VectorNumber[2, "a"].numeric? # => false
|
|
14
|
+
# VectorNumber[2, 3i].numeric?(1) # => false
|
|
15
|
+
#
|
|
16
|
+
# @param dimensions [Integer] number of dimensions to consider "numeric"
|
|
17
|
+
# - 0 — zero
|
|
18
|
+
# - 1 — real number
|
|
19
|
+
# - 2 — complex number, etc.
|
|
20
|
+
# @return [Boolean]
|
|
21
|
+
# @raise [ArgumentError] if +dimensions+ is negative
|
|
22
|
+
#
|
|
23
|
+
# @since 0.2.0
|
|
24
|
+
def numeric?(dimensions = 2)
|
|
25
|
+
raise ArgumentError, "`dimensions` must be non-negative" unless dimensions >= 0
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
# @example
|
|
32
|
-
# VectorNumber[2].nonnumeric? # => false
|
|
33
|
-
# VectorNumber[2, 3i].nonnumeric? # => false
|
|
34
|
-
# VectorNumber[2, "a"].nonnumeric? # => true
|
|
35
|
-
# VectorNumber[2, 3i].nonnumeric?(1) # => true
|
|
36
|
-
#
|
|
37
|
-
# @param (see #numeric?)
|
|
38
|
-
# @return (see #numeric?)
|
|
39
|
-
# @raise (see #numeric?)
|
|
40
|
-
#
|
|
41
|
-
# @since 0.2.1
|
|
42
|
-
def nonnumeric?(dimensions = 2) = !numeric?(dimensions)
|
|
43
|
-
|
|
44
|
-
# Returns +true+ if all coefficients are finite, +false+ otherwise.
|
|
45
|
-
#
|
|
46
|
-
# @example
|
|
47
|
-
# VectorNumber[2].finite? # => true
|
|
48
|
-
# VectorNumber[Float::NAN].finite? # => false
|
|
49
|
-
# VectorNumber["a"].mult(Float::INFINITY).finite? # => false
|
|
50
|
-
#
|
|
51
|
-
# @return [Boolean]
|
|
52
|
-
#
|
|
53
|
-
# @since 0.1.0
|
|
54
|
-
def finite?
|
|
55
|
-
all? { |_u, v| v.finite? }
|
|
56
|
-
end
|
|
27
|
+
size <= dimensions && (1..dimensions).count { @data[UNIT[_1]].nonzero? } == size
|
|
28
|
+
end
|
|
57
29
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
end
|
|
30
|
+
# Whether this VectorNumber contains any non-numeric parts.
|
|
31
|
+
#
|
|
32
|
+
# @example
|
|
33
|
+
# VectorNumber[2].nonnumeric? # => false
|
|
34
|
+
# VectorNumber[2, 3i].nonnumeric? # => false
|
|
35
|
+
# VectorNumber[2, "a"].nonnumeric? # => true
|
|
36
|
+
# VectorNumber[2, 3i].nonnumeric?(1) # => true
|
|
37
|
+
#
|
|
38
|
+
# @param (see #numeric?)
|
|
39
|
+
# @return (see #numeric?)
|
|
40
|
+
# @raise (see #numeric?)
|
|
41
|
+
#
|
|
42
|
+
# @since 0.2.1
|
|
43
|
+
def nonnumeric?(dimensions = 2) = !numeric?(dimensions)
|
|
73
44
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
45
|
+
# Returns +true+ if all coefficients are finite, +false+ otherwise.
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# VectorNumber[2].finite? # => true
|
|
49
|
+
# VectorNumber[Float::NAN].finite? # => false
|
|
50
|
+
# VectorNumber["a"].mult(Float::INFINITY).finite? # => false
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
#
|
|
54
|
+
# @since 0.1.0
|
|
55
|
+
def finite?
|
|
56
|
+
all? { |_u, v| v.finite? }
|
|
57
|
+
end
|
|
84
58
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
59
|
+
# Returns +1+ if any coefficients are non-finite, +nil+ otherwise.
|
|
60
|
+
#
|
|
61
|
+
# This behavior is the same as +Complex+'s.
|
|
62
|
+
#
|
|
63
|
+
# @example
|
|
64
|
+
# VectorNumber[2].infinite? # => nil
|
|
65
|
+
# VectorNumber[Float::NAN].infinite? # => 1
|
|
66
|
+
# VectorNumber["a"].mult(-Float::INFINITY).infinite? # => 1
|
|
67
|
+
#
|
|
68
|
+
# @return [1, nil]
|
|
69
|
+
#
|
|
70
|
+
# @since 0.1.0
|
|
71
|
+
def infinite?
|
|
72
|
+
finite? ? nil : 1 # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
|
|
73
|
+
end
|
|
99
74
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
end
|
|
75
|
+
# Returns +true+ if there are no non-zero coefficients, and +false+ otherwise.
|
|
76
|
+
#
|
|
77
|
+
# This is synonymous with +size+ being 0.
|
|
78
|
+
#
|
|
79
|
+
# @example
|
|
80
|
+
# VectorNumber["c"].zero? # => false
|
|
81
|
+
# VectorNumber[].zero? # => true
|
|
82
|
+
#
|
|
83
|
+
# @see #size
|
|
84
|
+
#
|
|
85
|
+
# @return [Boolean]
|
|
86
|
+
#
|
|
87
|
+
# @since 0.1.0
|
|
88
|
+
def zero? = size.zero?
|
|
115
89
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
90
|
+
# Returns +self+ if there are any non-zero coefficients, +nil+ otherwise.
|
|
91
|
+
#
|
|
92
|
+
# This is synonymous with +size+ not being equal to 0.
|
|
93
|
+
# Behavior of returning self or +nil+ is the same as +Numeric+'s.
|
|
94
|
+
#
|
|
95
|
+
# @example
|
|
96
|
+
# VectorNumber["ab", "cd"].nonzero? # => (1⋅'ab' + 1⋅'cd')
|
|
97
|
+
# VectorNumber[].nonzero? # => nil
|
|
98
|
+
#
|
|
99
|
+
# @see #size
|
|
100
|
+
#
|
|
101
|
+
# @return [VectorNumber, nil]
|
|
102
|
+
#
|
|
103
|
+
# @since 0.1.0
|
|
104
|
+
def nonzero?
|
|
105
|
+
zero? ? nil : self # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
|
|
106
|
+
end
|
|
131
107
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
108
|
+
# Returns +true+ if number is non-zero and all non-zero coefficients are positive,
|
|
109
|
+
# and +false+ otherwise.
|
|
110
|
+
#
|
|
111
|
+
# @example
|
|
112
|
+
# VectorNumber["a"].positive? # => true
|
|
113
|
+
# VectorNumber[2].neg.positive? # => false
|
|
114
|
+
# (VectorNumber["1"] - VectorNumber[1]).positive? # => false
|
|
115
|
+
# VectorNumber[0].positive? # => false
|
|
116
|
+
#
|
|
117
|
+
# @return [Boolean]
|
|
118
|
+
#
|
|
119
|
+
# @since 0.1.0
|
|
120
|
+
def positive?
|
|
121
|
+
!zero? && all? { |_u, c| c.positive? }
|
|
122
|
+
end
|
|
142
123
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
124
|
+
# Returns +true+ if number is non-zero and all non-zero coefficients are negative,
|
|
125
|
+
# and +false+ otherwise.
|
|
126
|
+
#
|
|
127
|
+
# @example
|
|
128
|
+
# VectorNumber["a"].neg.negative? # => true
|
|
129
|
+
# VectorNumber[-2].neg.negative? # => false
|
|
130
|
+
# (VectorNumber["1"] - VectorNumber[1]).negative? # => false
|
|
131
|
+
# VectorNumber[0].negative? # => false
|
|
132
|
+
#
|
|
133
|
+
# @return [Boolean]
|
|
134
|
+
#
|
|
135
|
+
# @since 0.1.0
|
|
136
|
+
def negative?
|
|
137
|
+
!zero? && all? { |_u, c| c.negative? }
|
|
149
138
|
end
|
|
139
|
+
|
|
140
|
+
# Always returns +false+, as vectors are not real numbers.
|
|
141
|
+
#
|
|
142
|
+
# This behavior is the same as +Complex+'s.
|
|
143
|
+
#
|
|
144
|
+
# @see #numeric?
|
|
145
|
+
#
|
|
146
|
+
# @return [false]
|
|
147
|
+
#
|
|
148
|
+
# @since 0.1.0
|
|
149
|
+
def real? = false
|
|
150
|
+
|
|
151
|
+
# Always returns +false+, as vectors are not +Integer+s.
|
|
152
|
+
#
|
|
153
|
+
# @return [false]
|
|
154
|
+
#
|
|
155
|
+
# @since 0.2.1
|
|
156
|
+
def integer? = false
|
|
150
157
|
end
|
|
@@ -1,96 +1,93 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class VectorNumber
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
none: "",
|
|
18
|
-
}.freeze
|
|
4
|
+
# Predefined symbols for multiplication to display between unit and coefficient.
|
|
5
|
+
#
|
|
6
|
+
# @return [Hash{Symbol => String}]
|
|
7
|
+
#
|
|
8
|
+
# @since 0.1.0
|
|
9
|
+
MULT_STRINGS = {
|
|
10
|
+
asterisk: "*", # U+002A
|
|
11
|
+
cross: "×", # U+00D7
|
|
12
|
+
dot: "⋅", # U+22C5
|
|
13
|
+
invisible: "", # U+2062, zero-width multiplication operator
|
|
14
|
+
space: " ",
|
|
15
|
+
none: "",
|
|
16
|
+
}.freeze
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
# @example
|
|
23
|
-
# VectorNumber[5, "s"].to_s # => "5 + 1⋅'s'"
|
|
24
|
-
# VectorNumber["s", 5].to_s # => "1⋅'s' + 5"
|
|
25
|
-
# @example with :mult argument
|
|
26
|
-
# VectorNumber[5, "s"].to_s(mult: :asterisk) # => "5 + 1*'s'"
|
|
27
|
-
# @example :mult option specified for the vector
|
|
28
|
-
# VectorNumber[5, "s", mult: :none].to_s # => "5 + 1's'"
|
|
29
|
-
#
|
|
30
|
-
# @param mult [Symbol, String]
|
|
31
|
-
# text to use between coefficient and unit,
|
|
32
|
-
# can be one of the keys in {MULT_STRINGS} or an arbitrary string
|
|
33
|
-
# @return [String]
|
|
34
|
-
# @raise [ArgumentError]
|
|
35
|
-
# if +mult+ is not a String and is not in {MULT_STRINGS}'s keys
|
|
36
|
-
#
|
|
37
|
-
# @since 0.1.0
|
|
38
|
-
def to_s(mult: options[:mult])
|
|
39
|
-
return "0" if zero?
|
|
18
|
+
# @group Miscellaneous methods
|
|
40
19
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
20
|
+
# Return string representation of the vector.
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# VectorNumber[5, "s"].to_s # => "5 + 1⋅'s'"
|
|
24
|
+
# VectorNumber["s", 5].to_s # => "1⋅'s' + 5"
|
|
25
|
+
# @example with :mult argument
|
|
26
|
+
# VectorNumber[5, "s"].to_s(mult: :asterisk) # => "5 + 1*'s'"
|
|
27
|
+
# @example :mult option specified for the vector
|
|
28
|
+
# VectorNumber[5, "s", mult: :none].to_s # => "5 + 1's'"
|
|
29
|
+
#
|
|
30
|
+
# @param mult [Symbol, String]
|
|
31
|
+
# text to use between coefficient and unit,
|
|
32
|
+
# can be one of the keys in {MULT_STRINGS} or an arbitrary string
|
|
33
|
+
# @return [String]
|
|
34
|
+
# @raise [ArgumentError]
|
|
35
|
+
# if +mult+ is not a String and is not in {MULT_STRINGS}'s keys
|
|
36
|
+
#
|
|
37
|
+
# @since 0.1.0
|
|
38
|
+
def to_s(mult: options[:mult])
|
|
39
|
+
return "0" if zero?
|
|
40
|
+
|
|
41
|
+
result = +""
|
|
42
|
+
each_with_index do |(unit, coefficient), index|
|
|
43
|
+
if index.zero?
|
|
44
|
+
result << "-" if coefficient.negative?
|
|
45
|
+
else
|
|
46
|
+
result << (coefficient.positive? ? " + " : " - ")
|
|
49
47
|
end
|
|
50
|
-
result
|
|
48
|
+
result << value_to_s(unit, coefficient.abs, mult: mult)
|
|
51
49
|
end
|
|
50
|
+
result
|
|
51
|
+
end
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
53
|
+
# Return string representation of the vector.
|
|
54
|
+
#
|
|
55
|
+
# This is similar to +Complex#inspect+: it returns result of {#to_s} in round brackets.
|
|
56
|
+
#
|
|
57
|
+
# @example
|
|
58
|
+
# VectorNumber[5, "s"].inspect # => "(5 + 1⋅'s')"
|
|
59
|
+
#
|
|
60
|
+
# @return [String]
|
|
61
|
+
#
|
|
62
|
+
# @see to_s
|
|
63
|
+
#
|
|
64
|
+
# @since 0.1.0
|
|
65
|
+
def inspect
|
|
66
|
+
# TODO: Probably make this independent of options.
|
|
67
|
+
"(#{self})"
|
|
68
|
+
end
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
private
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
raise ArgumentError, "unknown key #{mult.inspect}", caller
|
|
82
|
-
end
|
|
72
|
+
# @param unit [Object]
|
|
73
|
+
# @param coefficient [Numeric]
|
|
74
|
+
# @param mult [Symbol, String]
|
|
75
|
+
# @return [String]
|
|
76
|
+
# @raise [ArgumentError] if +mult+ is not in {MULT_STRINGS}'s keys
|
|
77
|
+
def value_to_s(unit, coefficient, mult:)
|
|
78
|
+
if !mult.is_a?(String) && !MULT_STRINGS.key?(mult)
|
|
79
|
+
raise ArgumentError, "unknown key #{mult.inspect}", caller
|
|
80
|
+
end
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
end
|
|
82
|
+
case unit
|
|
83
|
+
when R
|
|
84
|
+
coefficient.to_s
|
|
85
|
+
when I
|
|
86
|
+
"#{coefficient}i"
|
|
87
|
+
else
|
|
88
|
+
unit = "'#{unit}'" if unit.is_a?(String)
|
|
89
|
+
operator = mult.is_a?(String) ? mult : MULT_STRINGS[mult]
|
|
90
|
+
"#{coefficient}#{operator}#{unit}"
|
|
94
91
|
end
|
|
95
92
|
end
|
|
96
93
|
end
|