Class: VectorNumber

Inherits:
Object
  • Object
show all
Includes:
Comparable, Enumerable
Defined in:
lib/vector_number.rb,
lib/vector_number/mathing.rb,
lib/vector_number/version.rb,
lib/vector_number/querying.rb,
lib/vector_number/comparing.rb,
lib/vector_number/converting.rb,
lib/vector_number/enumerating.rb,
lib/vector_number/special_unit.rb,
lib/vector_number/stringifying.rb,
lib/vector_number/math_converting.rb,
lib/vector_number/numeric_refinements.rb

Overview

VectorNumber provides a Numeric-like experience for doing arithmetics on heterogeneous objects, with more advanced operations based on real vector spaces available when needed.

VectorNumber inherits from Object and includes Enumerable and Comparable. It implements mostly the same interface as Numeric classes, but can #coerce any value. Its behavior follows Complex when possible.

All instances are frozen after creation.

Creation

Comparing

  • #==: test for equal value

  • #eql?: test for equal value and type

  • #<=>: compare real values

  • #hash: calculate hash value for use in Hashes

Querying

Unary math operations

Arithmetic operations

  • #coerce: convert any object to a VectorNumber

  • #+/#add: add object

  • #-/#sub: subtract object

  • #*/#mult: multiply (scale) by a real number

  • #//#quo: divide (scale) by a real number

  • #fdiv: divide (scale) by a real number, using fdiv

  • #div: perform integer division

  • #%/#modulo: return modulus from integer division

  • #divmod: combination of #div and #modulo

  • #remainder: return remainder from integer division

Rounding

  • #round: round each coefficient

  • #ceil: round each coefficient up towards +∞

  • #floor: round each coefficient down towards -∞

  • #truncate: truncate each coefficient towards 0

Type conversion

  • #real: return real part

  • #imag/#imaginary: return imaginary part

  • #to_i/#to_int: convert to Integer if possible

  • #to_f: convert to Float if possible

  • #to_r: convert to Rational if possible

  • #to_d: convert to BigDecimal if possible

  • #to_c: convert to Complex if possible

Hash-like operations

Miscellaneous methods

Since:

  • 0.1.0

Defined Under Namespace

Modules: NumericRefinements Classes: SpecialUnit

Constant Summary collapse

NUMERIC_UNITS =

List of special numeric unit constants.

Since:

  • 0.6.0

[SpecialUnit.new("1", "").freeze, SpecialUnit.new("i", "i").freeze].freeze
R =

Constant for real unit (1).

Its string representation is an empty string.

Since:

  • 0.6.0

I =

Constant for imaginary unit (i).

Its string representation is “i”.

Since:

  • 0.6.0

VERSION =

Returns:

  • (String)

Since:

  • 0.1.0

"0.6.0"
MULT_STRINGS =

Predefined symbols for multiplication to display between unit and coefficient.

Returns:

  • (Hash{Symbol => String})

Since:

  • 0.1.0

{
  asterisk: "*", # U+002A
  cross: "×", # U+00D7
  dot: "", # U+22C5
  invisible: "", # U+2062, zero-width multiplication operator
  space: " ",
  none: "",
}.freeze

Instance Attribute Summary collapse

Creation collapse

Miscellaneous methods collapse

Mathematical operations collapse

Querying collapse

Comparing collapse

Converting to different numeric classes collapse

Hash-like operations collapse

Class Method Summary collapse

Constructor Details

#initialize(values = nil) {|coefficient| ... } ⇒ VectorNumber

Create new VectorNumber from an array of values, possibly modifying coefficients with a block.

Using VectorNumber.new() directly is more efficient than VectorNumber[values...].

values can be:

  • an array of values (see []);

  • a VectorNumber to copy;

  • a hash in the format returned by #to_h;

  • nil to specify a 0-sized vector (same as an empty array or hash).

Using a hash as values is an advanced technique which allows to quickly construct a VectorNumber with desired units and coefficients, but it can also lead to unexpected results if care is not taken to provide only valid keys and values.

Examples:

VectorNumber.new(1, 2, 3) # ArgumentError
VectorNumber.new([1, 2, 3]) # => (6)
VectorNumber.new(["b", VectorNumber::I]) # => (1⋅"b" + 1i)
VectorNumber.new # => (0)

with a block

VectorNumber.new(["a", "b", "c", 3]) { _1 * 2 } # => (2⋅"a" + 2⋅"b" + 2⋅"c" + 6)
VectorNumber.new(["a", "b", "c", 3], &:-@) # => (-1⋅"a" - 1⋅"b" - 1⋅"c" - 3)
VectorNumber.new(["a", "b", "c", 3], &:digits) # RangeError

using hash for values

v = VectorNumber.new({1 => 15, "a" => 3.4, nil => -3}) # => (15 + 3.4⋅"a" - 3⋅)
v.to_h # => {1 => 15, "a" => 3.4, nil => -3}

Parameters:

  • values (Array, VectorNumber, Hash{Object => Numeric}, nil) (defaults to: nil)

    values for this vector

Yield Parameters:

  • coefficient (Numeric)

    a real number

Yield Returns:

  • (Numeric)

    new coefficient

Raises:

  • (RangeError)

    if a block is used and it returns a non-number or non-real number

Since:

  • 0.1.0



184
185
186
187
188
189
190
# File 'lib/vector_number.rb', line 184

def initialize(values = nil, **nil, &transform)
  initialize_from(values)
  apply_transform(&transform)
  finalize_contents
  @data.freeze
  freeze
end

Instance Attribute Details

#sizeInteger (readonly)

Number of non-zero dimensions.

Returns:

  • (Integer)

Since:

  • 0.1.0



148
149
150
# File 'lib/vector_number.rb', line 148

def size
  @size
end

Class Method Details

.[](*values) ⇒ Object

Create new VectorNumber from a list of values.

Examples:

VectorNumber[1, 2, 3] # => (6)
VectorNumber[[1, 2, 3]] # => (1⋅[1, 2, 3])
VectorNumber[] # => (0)
VectorNumber["b", VectorNumber["b"]] # => (2⋅'b')
VectorNumber["a", "b", "a"] # => (2⋅'a' + 1⋅'b')

Parameters:

  • values (Array<Object>)

    values to put in the number

Since:

  • 0.1.0



124
125
126
# File 'lib/vector_number.rb', line 124

def self.[](*values, **nil)
  new(values)
end

.numeric_unit?(unit) ⇒ Boolean

Check if an object is a unit representing a numeric dimension (real or imaginary unit).

Examples:

VectorNumber.numeric_unit?(VectorNumber::R) # => true
VectorNumber.numeric_unit?(VectorNumber::I) # => true
VectorNumber.numeric_unit?(:i) # => false

Parameters:

  • unit (Object)

Returns:

  • (Boolean)

Since:

  • 0.6.0



141
142
143
# File 'lib/vector_number.rb', line 141

def self.numeric_unit?(unit)
  NUMERIC_UNITS.include?(unit)
end

Instance Method Details

#%(other) ⇒ VectorNumber Also known as: modulo

Return the modulus of dividing self by a real other as a vector.

This is equal to self - other * (self/other).floor, or, alternatively, self - other * self.div(other).

Examples:

VectorNumber[10] % 3 # => (1)
VectorNumber["a", "b", 6].modulo(2) # => (1⋅"a" + 1⋅"b")
-VectorNumber["a"] % VectorNumber[2] # => (1⋅"a")
# Can't divide by a non-real:
VectorNumber["a"] % VectorNumber["b"] # RangeError

numeric types can be divided in reverse

3 % VectorNumber[10] # => (3)
# Can't divide by a non-real:
3 % VectorNumber["a"] # RangeError

compare to #remainder

VectorNumber[-5] % 3 # => (1)
VectorNumber[-5].remainder(3) # => (-2)

Parameters:

  • other (Integer, Float, Rational, BigDecimal, VectorNumber)

Returns:

Raises:

  • (RangeError)

    if other is not a number or is not a real number

  • (ZeroDivisionError)

    if other is zero

See Also:

Since:

  • 0.2.6



259
260
261
262
263
264
# File 'lib/vector_number/mathing.rb', line 259

def %(other)
  check_divisibility(other)

  other = other.real
  new { _1 % other }
end

#*(other) ⇒ VectorNumber Also known as: mult

Multiply all coefficients by a real other, returning new vector.

This effectively multiplies #magnitude by other.

Examples:

VectorNumber[5] * 2 # => (10)
VectorNumber["a", "b", 6].mult(2) # => (2⋅"a" + 2⋅"b" + 12)
VectorNumber["a"] * VectorNumber[2] # => (2⋅"a")
# Can't multiply by a non-real:
VectorNumber["a"] * VectorNumber["b"] # RangeError

numeric types can be multiplied in reverse

2 * VectorNumber[5] # => (10)
2 * VectorNumber["a"] # => (2⋅"a")

Parameters:

  • other (Integer, Float, Rational, BigDecimal, VectorNumber)

Returns:

Raises:

  • (RangeError)

    if other is not a number or other can’t be multiplied by this one

Since:

  • 0.2.1



115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/vector_number/mathing.rb', line 115

def *(other)
  if real_number?(other)
    other = other.real
    # @type var other: Float
    new { _1 * other }
  elsif real_number?(self) && other.is_a?(self.class)
    # @type var other: untyped
    other * self
  else
    raise RangeError, "can't multiply #{self} and #{other}"
  end
end

#+(other) ⇒ VectorNumber Also known as: add

Return new vector as a sum of this and other value. This is analogous to [].

Examples:

VectorNumber[5] + 10 # => (15)
VectorNumber["a"].add(VectorNumber["b"]) # => (1⋅"a" + 1⋅"b")
VectorNumber["a"] + "b" # => (1⋅"a" + 1⋅"b")

numeric types can be added in reverse

10 + VectorNumber[5] # => (15)
10 + VectorNumber["a"] # => (10 + 1⋅"a")

Parameters:

  • other (Object)

Returns:

Since:

  • 0.2.0



66
67
68
# File 'lib/vector_number/mathing.rb', line 66

def +(other)
  new([self, other])
end

#+@VectorNumber Also known as: dup

Return self.

Returns:

Since:

  • 0.2.0



199
200
# File 'lib/vector_number.rb', line 199

def +@ = self
# @since 0.2.4

#-(other) ⇒ VectorNumber Also known as: sub

Return new vector as a sum of this and additive inverse of other value.

This is implemented through #+ and #-@.

Examples:

VectorNumber[5] - 3 # => (2)
VectorNumber["a"].sub(VectorNumber["b"]) # => (1⋅"a" - 1⋅"b")
VectorNumber["a"] - "b" # => (1⋅"a" - 1⋅"b")

numeric types can be subtracted in reverse

3 - VectorNumber[5] # => (-2)
3 - VectorNumber["a"] # => (3 - 1⋅"a")

Parameters:

  • other (Object)

Returns:

Since:

  • 0.2.0



89
90
91
# File 'lib/vector_number/mathing.rb', line 89

def -(other)
  self + new([other], &:-@)
end

#-@VectorNumber Also known as: neg

Return new vector with negated coefficients (additive inverse).

Examples:

-VectorNumber[12, "i"] # => (-12 - 1⋅"i")
VectorNumber["a", "b", "a"].neg # => (-2⋅"a" - 1⋅"b")
-VectorNumber["a"] + VectorNumber["a"] # => (0)

Returns:

Since:

  • 0.2.0



44
45
46
# File 'lib/vector_number/mathing.rb', line 44

def -@
  new(&:-@)
end

#/(other) ⇒ VectorNumber Also known as: quo

Note:

This method never does integer division.

Divide all coefficients by a real other, returning new vector.

This effectively multiplies #magnitude by reciprocal of other.

Examples:

VectorNumber[10] / 2 # => (5)
VectorNumber["a", "b", 6].quo(2) # => (1/2⋅"a" + 1/2⋅"b" + 3/1)
VectorNumber["a"] / VectorNumber[2] # => (1/2⋅"a")
# Can't divide by a non-real:
VectorNumber["a"] / VectorNumber["b"] # RangeError

numeric types can be divided in reverse

2 / VectorNumber[10] # => (1/5)
# Can't divide by a non-real:
2 / VectorNumber["a"] # RangeError

Parameters:

  • other (Integer, Float, Rational, BigDecimal, VectorNumber)

Returns:

Raises:

  • (RangeError)

    if other is not a number or is not a real number

  • (ZeroDivisionError)

    if other is zero

Since:

  • 0.2.1



153
154
155
156
157
158
159
160
161
# File 'lib/vector_number/mathing.rb', line 153

def /(other)
  check_divisibility(other)

  other = other.real
  # Prevent integer division, but without loss of accuracy.
  other = Rational(other) if other.integer?
  # @type var other: Float
  new { _1 / other }
end

#<=>(other) ⇒ Integer?

Compare to other and return -1, 0, or 1 if self is less than, equal, or larger than other on real number line, or nil if any or both values are non-real.

Most VectorNumbers are non-real and therefore not comparable with this method.

Examples:

VectorNumber[130] <=> 12 # => 1
1 <=> VectorNumber[13] # => -1
VectorNumber[12.1] <=> Complex(12.1, 0) # => 0
# This doesn't work as expected without NumericRefinements:
Complex(12.1, 0) <=> VectorNumber[12.1] # => nil

# Any non-real comparison returns nil:
VectorNumber[12.1] <=> Complex(12.1, 1) # => nil
VectorNumber[12.1i] <=> 2 # => nil
VectorNumber["a"] <=> 2 # => nil

Parameters:

  • other (Object)

Returns:

  • (Integer)
  • (nil)

    if self or other isn’t a real number.

See Also:

Since:

  • 0.2.0



109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/vector_number/comparing.rb', line 109

def <=>(other)
  return nil unless numeric?(1)

  case other
  when VectorNumber
    other.numeric?(1) ? real <=> other.real : nil
  when Numeric
    other.imaginary.zero? ? real <=> other.real : nil
  else
    nil
  end
end

#==(other) ⇒ Boolean

Test whether other has the same value with == semantics.

Values are considered equal if

  • other is a VectorNumber and it has the same units and equal coefficients, or

  • other is a Numeric equal in value to this (real or complex) number.

Examples:

VectorNumber[3.13] == 3.13 # => true
VectorNumber[1.4, 1.5i] == Complex(1.4, 1.5) # => true
VectorNumber["a", "b", "c"] == VectorNumber["c", "b", "a"] # => true
VectorNumber["a", 14] == 14 # => false
VectorNumber["a"] == "a" # => false

Parameters:

  • other (Object)

Returns:

  • (Boolean)

Since:

  • 0.2.0



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/vector_number/comparing.rb', line 32

def ==(other)
  return true if equal?(other)

  case other
  when VectorNumber
    size == other.size && @data == other.to_h
  when Numeric
    numeric?(2) && other.real == real && other.imaginary == imaginary
  else
    # Can't compare a number-like value to a non-number.
    false
  end
end

#[](unit) ⇒ Numeric

Get the coefficient for the unit.

If the unit?(unit) is false, 0 is returned. Note that units for real and imaginary parts are VectorNumber::R and VectorNumber::I respectively.

Examples:

VectorNumber["a", "b", 6]["a"] # => 1
VectorNumber["a", "b", 6]["c"] # => 0

Parameters:

  • unit (Object)

Returns:

  • (Numeric)

Since:

  • 0.2.4



98
# File 'lib/vector_number/enumerating.rb', line 98

def [](unit) = @data[unit]

#absFloat Also known as: magnitude

Calculate the absolute value of the vector (its length).

Examples:

VectorNumber[5.3].abs # => 5.3
VectorNumber[-5.3i].magnitude # => 5.3
VectorNumber[-5.3i, "i"].abs # => 5.3935146240647205

Returns:

  • (Float)

Since:

  • 0.2.2



16
17
18
# File 'lib/vector_number/math_converting.rb', line 16

def abs
  Math.sqrt(abs2)
end

#abs2Float

Calculate the square of absolute value.

Examples:

VectorNumber[5.3].abs2 # => 5.3
VectorNumber[-5.3i].abs2 # => 5.3
VectorNumber[-5.3i, "i"].abs2 # => 29.09

Returns:

  • (Float)

Since:

  • 0.2.2



32
33
34
# File 'lib/vector_number/math_converting.rb', line 32

def abs2
  coefficients.sum(&:abs2)
end

#ceil(digits = 0) ⇒ VectorNumber

Return a new vector with every coefficient rounded using their #ceil.

Examples:

VectorNumber[5.39].ceil # => (6)
VectorNumber[-5.35i].ceil # => (-5i)
VectorNumber[-5.35i, "i"].ceil # => (-5i + 1⋅"i")
VectorNumber[-5.35i, "i"].ceil(1) # => (-5.3i + 1⋅"i")
VectorNumber[-5.35i, "i"].ceil(-1) # => (10⋅"i")

Parameters:

  • digits (Integer) (defaults to: 0)

Returns:

Since:

  • 0.2.2



66
67
68
# File 'lib/vector_number/math_converting.rb', line 66

def ceil(digits = 0)
  new { _1.ceil(digits) }
end

#clone(freeze: true) ⇒ VectorNumber

Return self.

Returns:

Raises:

  • (ArgumentError)

    if freeze is not true or nil.

Since:

  • 0.2.4



209
210
211
212
213
214
215
216
217
218
# File 'lib/vector_number.rb', line 209

def clone(freeze: true)
  case freeze
  when true, nil
    self
  when false
    raise ArgumentError, "can't unfreeze #{self.class}"
  else
    raise ArgumentError, "unexpected value for freeze: #{freeze.class}"
  end
end

#coefficientsArray<Numeric> Also known as: values

Get a list of non-zero coefficients.

Examples:

VectorNumber["a", "b", 6].coefficients # => [1, 1, 6]
VectorNumber.new.values # => []

Returns:

  • (Array<Numeric>)

Since:

  • 0.1.0



62
# File 'lib/vector_number/enumerating.rb', line 62

def coefficients = @data.values

#coerce(other) ⇒ Array(VectorNumber, VectorNumber)

The coerce method provides support for Ruby type coercion.

Unlike other numeric types, VectorNumber can coerce anything.

Examples:

VectorNumber["a"].coerce(5) # => [(5), (1⋅"a")]
VectorNumber[7].coerce([]) # => [(1⋅[]), (7)]
VectorNumber["a"] + 5 # => (1⋅"a" + 5)
# Direct reverse coercion doesn't work, but Numeric types know how to call #coerce:
5.coerce(VectorNumber["a"]) # RangeError
5 + VectorNumber["a"] # => (5 + 1⋅"a")

Parameters:

  • other (Object)

Returns:

Since:

  • 0.2.0



25
26
27
28
29
30
31
32
# File 'lib/vector_number/mathing.rb', line 25

def coerce(other)
  case other
  when VectorNumber
    [other, self]
  else
    [new([other]), self]
  end
end

#div(other) ⇒ VectorNumber

Divide all coefficients by a real other, converting results to integers using #floor.

This is requal to (self / other).floor.

Examples:

VectorNumber[10].div(3) # => (3)
VectorNumber["a"].div(2) # => (0)
VectorNumber["a"].div(VectorNumber[2]) # => (0)
# Can't divide by a non-real:
VectorNumber["a"].div(VectorNumber["b"]) # RangeError

numeric types can be divided in reverse

2.div(VectorNumber[10]) # => (0)
# Can't divide by a non-real:
2.div(VectorNumber["a"]) # RangeError

Parameters:

  • other (Integer, Float, Rational, BigDecimal, VectorNumber)

Returns:

Raises:

  • (RangeError)

    if other is not a number or is not a real number

  • (ZeroDivisionError)

    if other is zero

See Also:

Since:

  • 0.2.6



222
223
224
225
226
227
# File 'lib/vector_number/mathing.rb', line 222

def div(other)
  check_divisibility(other)

  other = other.real
  new { _1.div(other) }
end

#divmod(other) ⇒ Array(VectorNumber, VectorNumber)

Return the quotient and modulus of dividing self by a real other. There is no performance benefit compared to calling #div and #% separately.

Examples:

VectorNumber[10].divmod(3) # => [(3), (1)]
VectorNumber["a"].divmod(2) # => [(0), (1⋅"a")]
VectorNumber["a"].divmod(VectorNumber[2]) # => [(0), (1⋅"a")]
# Can't divide by a non-real:
VectorNumber["a"].divmod(VectorNumber["b"]) # RangeError

numeric types can be divided in reverse

3.divmod(VectorNumber[10]) # => [(0), (3)]
# Can't divide by a non-real:
3.divmod(VectorNumber["a"]) # RangeError

Parameters:

  • other (Integer, Float, Rational, BigDecimal, VectorNumber)

Returns:

Raises:

  • (RangeError)

    if other is not a number or is not a real number

  • (ZeroDivisionError)

    if other is zero

See Also:

Since:

  • 0.2.6



292
293
294
# File 'lib/vector_number/mathing.rb', line 292

def divmod(other)
  [div(other), modulo(other)]
end

#each {|unit, coefficient| ... } ⇒ VectorNumber #eachEnumerator Also known as: each_pair

Iterate through every pair of unit and coefficient. Returns Enumerator (with set size) if no block is given.

Examples:

v = VectorNumber["a", "b", 6]
units = []
v.each { |u, c| units << u unless u.is_a?(Numeric) } # => (1⋅'a' + 1⋅'b' + 6)
units # => ["a", "b"]

Enumerator

v.each.size # => 3
(v.each + [["d", 0]]).map(&:first) # => ["a", "b", 1, "d"]
v.each_pair.peek # => ["a", 1]

Overloads:

  • #each {|unit, coefficient| ... } ⇒ VectorNumber

    Returns self.

    Yield Parameters:

    • unit (Object)
    • coefficient (Numeric)

    Yield Returns:

    • (void)

    Returns:

  • #eachEnumerator

    Returns:

    • (Enumerator)

See Also:

  • Enumerable
  • Enumerator

Since:

  • 0.1.0



35
36
37
38
39
40
# File 'lib/vector_number/enumerating.rb', line 35

def each(&block)
  return to_enum { size } unless block_given?

  @data.each(&block)
  self
end

#eql?(other) ⇒ Boolean

Test whether other is VectorNumber and has the same value with eql? semantics.

Values are considered equal only if other is a VectorNumber and it has exactly the same units and coefficients, though possibly in a different order. Additionally, ‘a.eql?(b)` implies `a.hash == b.hash`.

Examples:

VectorNumber["a", "b", "c"].eql? VectorNumber["c", "b", "a"] # => true
VectorNumber[3.13].eql? 3.13 # => false
VectorNumber[1.4, 1.5i].eql? Complex(1.4, 1.5) # => false
VectorNumber["a", 14].eql? 14 # => false
VectorNumber["a"].eql? "a" # => false

Parameters:

  • other (Object)

Returns:

  • (Boolean)

Since:

  • 0.1.0



61
62
63
64
65
66
# File 'lib/vector_number/comparing.rb', line 61

def eql?(other)
  return true if equal?(other)
  return false unless other.is_a?(VectorNumber)

  size.eql?(other.size) && @data.eql?(other.to_h)
end

#fdiv(other) ⇒ VectorNumber

Divide all coefficients by a real other using fdiv, returning new vector with decimal coefficients.

There isn’t much benefit to this method, as #/ doesn’t do integer division, but it is provided for consistency.

Examples:

VectorNumber[10].fdiv(2) # => (5.0)
VectorNumber["a", "b", 6].fdiv(2) # => (0.5⋅"a" + 0.5⋅"b" + 3.0)
VectorNumber["a"].fdiv(VectorNumber[2]) # => (0.5⋅"a")
# Can't divide by a non-real:
VectorNumber["a"].fdiv(VectorNumber["b"]) # RangeError

reverse division may return non-vector results

2.fdiv(VectorNumber[10]) # => 0.2 (Float)
2.0.fdiv(VectorNumber[10]) # => (0.2) (VectorNumber)

Parameters:

  • other (Integer, Float, Rational, BigDecimal, VectorNumber)

Returns:

Raises:

  • (RangeError)

    if other is not a number or is not a real number

  • (ZeroDivisionError)

    if other is zero

Since:

  • 0.2.1



189
190
191
192
193
194
# File 'lib/vector_number/mathing.rb', line 189

def fdiv(other)
  check_divisibility(other)

  other = other.real
  new { _1.fdiv(other) }
end

#finite?Boolean

Returns true if all coefficients are finite, false otherwise.

Examples:

VectorNumber[2].finite? # => true
VectorNumber[Float::NAN].finite? # => false
VectorNumber["a"].mult(Float::INFINITY).finite? # => false

Returns:

  • (Boolean)

Since:

  • 0.1.0



60
61
62
# File 'lib/vector_number/querying.rb', line 60

def finite?
  all? { |_u, v| v.finite? }
end

#floor(digits = 0) ⇒ VectorNumber

Return a new vector with every coefficient rounded using their #floor.

Examples:

VectorNumber[5.39].floor # => (5)
VectorNumber[-5.35i].floor # => (-6i)
VectorNumber[-5.35i, "i"].floor # => (-6i + 1⋅"i")
VectorNumber[-5.35i, "i"].floor(1) # => (-5.4i + 1⋅"i")
VectorNumber[-5.35i, "i"].floor(-1) # => (-10i)

Parameters:

  • digits (Integer) (defaults to: 0)

Returns:

Since:

  • 0.2.2



83
84
85
# File 'lib/vector_number/math_converting.rb', line 83

def floor(digits = 0)
  new { _1.floor(digits) }
end

#hashInteger

Generate an Integer hash value for self.

Examples:

VectorNumber["b", "a"].hash # => 3081872088394655324
VectorNumber["a", "b"].hash # => 3081872088394655324
VectorNumber["b", "c"].hash # => -1002381358514682371

Returns:

  • (Integer)

Since:

  • 0.4.2



78
79
80
# File 'lib/vector_number/comparing.rb', line 78

def hash
  [self.class, @data].hash
end

#imaginaryInteger, ... Also known as: imag

Return imaginary part of the number.

Examples:

VectorNumber[23, "a"].imaginary # => 0
VectorNumber["a", Complex(1, 2r)].imag # => (2/1)

Returns:

  • (Integer, Float, Rational, BigDecimal)

Since:

  • 0.1.0



22
# File 'lib/vector_number/converting.rb', line 22

def imaginary = @data[I]

#infinite?1?

Returns 1 if any coefficients are non-finite, nil otherwise.

This behavior is the same as Complex‘s.

Examples:

VectorNumber[2].infinite? # => nil
VectorNumber[Float::NAN].infinite? # => 1
VectorNumber["a"].mult(-Float::INFINITY).infinite? # => 1

Returns:

  • (1, nil)

Since:

  • 0.1.0



74
75
76
# File 'lib/vector_number/querying.rb', line 74

def infinite?
  finite? ? nil : 1 # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
end

#inspectString

Return string representation of the vector.

This is similar to Complex#inspect: it returns result of #to_s in round brackets.

Examples:

VectorNumber[5, :s].inspect # => "(5 + 1⋅s)"

Returns:

  • (String)

See Also:

Since:

  • 0.1.0



68
69
70
# File 'lib/vector_number/stringifying.rb', line 68

def inspect
  "(#{self})"
end

#integer?false

Always returns false, as vectors are not Integers.

Returns:

  • (false)

Since:

  • 0.2.1



149
# File 'lib/vector_number/querying.rb', line 149

def integer? = false

#negative?Boolean

Returns true if number is non-zero and all non-zero coefficients are negative, and false otherwise.

Examples:

VectorNumber["a"].neg.negative? # => true
VectorNumber[-2].neg.negative? # => false
(VectorNumber["1"] - VectorNumber[1]).negative? # => false
VectorNumber[0].negative? # => false

Returns:

  • (Boolean)

Since:

  • 0.1.0



131
132
133
# File 'lib/vector_number/querying.rb', line 131

def negative?
  !zero? && all? { |_u, c| c.negative? }
end

#nonnumeric?(dimensions = 2) ⇒ Boolean

Returns true if this VectorNumber contains any non-zero dimensions with non-numeric units, and false otherwise.

This is exactly the opposite of #numeric?.

Examples:

VectorNumber[2].nonnumeric? # => false
VectorNumber[2, 3i].nonnumeric? # => false
VectorNumber[2, "a"].nonnumeric? # => true
VectorNumber[2, 3i].nonnumeric?(1) # => true

Parameters:

  • dimensions (Integer) (defaults to: 2)

    number of dimensions to consider “numeric”

    • 0 — zero

    • 1 — real number

    • 2 — complex number

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

    if dimensions is negative

Since:

  • 0.2.1



50
# File 'lib/vector_number/querying.rb', line 50

def nonnumeric?(dimensions = 2) = !numeric?(dimensions)

#nonzero?VectorNumber?

Returns self if there are any non-zero coefficients, nil otherwise.

This is synonymous with size not being equal to 0. Behavior of returning self or nil is the same as Numeric‘s.

Examples:

VectorNumber["ab", "cd"].nonzero? # => (1⋅"ab" + 1⋅"cd")
VectorNumber[].nonzero? # => nil

Returns:

See Also:

Since:

  • 0.1.0



103
104
105
# File 'lib/vector_number/querying.rb', line 103

def nonzero?
  zero? ? nil : self # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
end

#numeric?(dimensions = 2) ⇒ Boolean

Returns true if all non-zero dimensions in this VectorNumber are numeric (real or complex), and false otherwise.

This is exactly the opposite of #nonnumeric?.

Examples:

VectorNumber[2].numeric? # => true
VectorNumber[2, 3i].numeric? # => true
VectorNumber[2, "a"].numeric? # => false
VectorNumber[2, 3i].numeric?(1) # => false

Parameters:

  • dimensions (Integer) (defaults to: 2)

    number of dimensions to consider “numeric”

    • 0 — zero

    • 1 — real number

    • 2 — complex number

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

    if dimensions is negative

Since:

  • 0.2.0



27
28
29
30
31
32
# File 'lib/vector_number/querying.rb', line 27

def numeric?(dimensions = 2)
  raise ArgumentError, "`dimensions` must be non-negative" unless dimensions >= 0

  size <= dimensions &&
    (0...dimensions).count { (unit = NUMERIC_UNITS[_1]) && @data[unit].nonzero? } == size
end

#positive?Boolean

Returns true if number is non-zero and all non-zero coefficients are positive, and false otherwise.

Examples:

VectorNumber["a"].positive? # => true
VectorNumber[2].neg.positive? # => false
(VectorNumber["1"] - VectorNumber[1]).positive? # => false
VectorNumber[0].positive? # => false

Returns:

  • (Boolean)

Since:

  • 0.1.0



117
118
119
# File 'lib/vector_number/querying.rb', line 117

def positive?
  !zero? && all? { |_u, c| c.positive? }
end

#realInteger, ...

Return real part of the number.

Examples:

VectorNumber[23, "a"].real # => 23
VectorNumber["a"].real # => 0

Returns:

  • (Integer, Float, Rational, BigDecimal)

Since:

  • 0.1.0



13
# File 'lib/vector_number/converting.rb', line 13

def real = @data[R]

#real?false

Always returns false, as vectors are not real numbers.

This behavior is the same as Complex‘s.

Returns:

  • (false)

See Also:

Since:

  • 0.1.0



142
# File 'lib/vector_number/querying.rb', line 142

def real? = false

#remainder(other) ⇒ VectorNumber

Return the remainder of dividing self by a real other as a vector.

This is equal to self - other * (self/other).truncate.

Examples:

VectorNumber[10].remainder(3) # => (1)
VectorNumber["a"].remainder(2) # => (1⋅"a")
-VectorNumber["a"].remainder(VectorNumber[2]) # => (-1⋅"a")
# Can't divide by a non-real:
VectorNumber["a"].remainder(VectorNumber["b"]) # RangeError

numeric types can be divided in reverse

3.remainder(VectorNumber[10]) # => (3)
# Can't divide by a non-real:
3.remainder(VectorNumber["a"]) # RangeError

compare to #%

VectorNumber[-5] % 3 # => (1)
VectorNumber[-5].remainder(3) # => (-2)

Parameters:

  • other (Integer, Float, Rational, BigDecimal, VectorNumber)

Returns:

Raises:

  • (RangeError)

    if other is not a number or is not a real number

  • (ZeroDivisionError)

    if other is zero

See Also:

  • #%
  • Numeric#remainder

Since:

  • 0.2.6



323
324
325
326
327
328
# File 'lib/vector_number/mathing.rb', line 323

def remainder(other)
  check_divisibility(other)

  other = other.real
  new { _1.remainder(other) }
end

#round(digits = 0, half: :up) ⇒ VectorNumber

Return a new vector with every coefficient rounded using their #round.

In the case of BigDecimal coefficients, the half parameter is converted to the corresponding BigDecimal rounding mode (one of :half_up, :half_down, or :half_even). Other modes can not be specified.

Examples:

VectorNumber[-4.5i, "i"].round(half: :up) # => (-5i + 1⋅"i")
VectorNumber[-4.5i, "i"].round(half: :even) # => (-4i + 1⋅"i")
VectorNumber[-5.5i, "i"].round(half: :even) # => (-6i + 1⋅"i")
VectorNumber[-5.5i, "i"].round(half: :down) # => (-5i + 1⋅"i")
VectorNumber[-5.35i, "i"].round(1) # => (-5.4i + 1⋅"i")
VectorNumber[-5.35i, "i"].round(-1) # => (-10i)

Parameters:

  • digits (Integer) (defaults to: 0)
  • half (Symbol, nil) (defaults to: :up)

    one of :up, :down or :even, see Float#round for meaning

Returns:

See Also:

  • Float#round

Since:

  • 0.2.2



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/vector_number/math_converting.rb', line 110

def round(digits = 0, half: :up)
  if defined?(BigDecimal)
    bd_mode =
      case half
      when :down then :half_down
      when :even then :half_even
      else :half_up
      end
    new { _1.is_a?(BigDecimal) ? _1.round(digits, bd_mode) : _1.round(digits, half: half) }
  # :nocov:
  else
    new { _1.round(digits, half: half) }
  end
  # :nocov:
end

#to_cComplex

Return value as a Complex if only real and/or imaginary parts are non-zero.

Examples:

VectorNumber[13.5].to_c # => (13.5+0i)
VectorNumber[13r/12].to_c # => ((13/12)+0i)
VectorNumber[2, 2i].to_c # => (2+2i)
VectorNumber[2, :i].to_c # RangeError

Complex(VectorNumber[2]) # => (2+0i)
Complex(VectorNumber[2, "i"]) # RangeError

Returns:

  • (Complex)

Raises:

  • (RangeError)

    if any non-real, non-imaginary part is non-zero

Since:

  • 0.1.0



131
132
133
134
135
# File 'lib/vector_number/converting.rb', line 131

def to_c
  raise_convert_error(Complex) unless numeric?(2)

  Complex(real, imaginary)
end

#to_d(ndigits = nil) ⇒ BigDecimal

Return value as a BigDecimal if only real part is non-zero.

Examples:

VectorNumber[13.5].to_d # => 0.135e2
VectorNumber[13r/12].to_d # ArgumentError
VectorNumber[13r/12].to_d(5) # => 0.10833e1
VectorNumber[2, 2i].to_d # RangeError
VectorNumber[2, :i].to_d # RangeError

# This does't work without NumericRefinements:
BigDecimal(VectorNumber[2]) # TypeError
# #to_s can be used as a workaround if refinements aren't used:
BigDecimal(VectorNumber[2].to_s) # => 0.2e1
BigDecimal(VectorNumber[2, "i"].to_s) # => ArgumentError

Parameters:

  • ndigits (Integer) (defaults to: nil)

    precision

Returns:

  • (BigDecimal)

Raises:

  • (RangeError)

    if any non-real part is non-zero

  • (NameError)

    if BigDecimal is not defined

See Also:

Since:

  • 0.1.0



109
110
111
112
113
114
115
116
# File 'lib/vector_number/converting.rb', line 109

def to_d(ndigits = nil)
  raise_convert_error(BigDecimal) unless numeric?(1)

  return BigDecimal(real, ndigits) if ndigits
  return BigDecimal(real, Float::DIG) if real.is_a?(Float)

  BigDecimal(real)
end

#to_fFloat

Return value as a Float if only real part is non-zero.

Examples:

VectorNumber[13.5].to_f # => 13.5
VectorNumber[13r/12].to_f # => 1.0833333333333333
VectorNumber[2, 2i].to_f # RangeError
VectorNumber[2, :i].to_f # RangeError

Float(VectorNumber[2]) # => 2.0
Float(VectorNumber[2, "i"]) # RangeError

Returns:

  • (Float)

Raises:

  • (RangeError)

    if any non-real part is non-zero

Since:

  • 0.1.0



62
63
64
65
66
# File 'lib/vector_number/converting.rb', line 62

def to_f
  raise_convert_error(Float) unless numeric?(1)

  real.to_f
end

#to_h(&block) ⇒ Hash{Object => Numeric}

Get mutable hash with vector’s data.

Returned hash has a default value of 0.

Examples:

VectorNumber["a", "b", 6].to_h # => {"a"=>1, "b"=>1, 1=>6}
VectorNumber["a", "b", 6].to_h["c"] # => 0

Returns:

  • (Hash{Object => Numeric})

Since:

  • 0.1.0



75
76
77
78
79
80
81
82
# File 'lib/vector_number/enumerating.rb', line 75

def to_h(&block)
  # TODO: Remove block argument.
  if block_given?
    @data.to_h(&block)
  else
    @data.dup
  end
end

#to_iInteger Also known as: to_int

Return value as an Integer, truncating it, if only real part is non-zero.

Examples:

VectorNumber[13.5].to_i # => 13
VectorNumber[13r/12].to_int # => 1
[1.1, 2.2, 3.3][VectorNumber[2]] # => 3.3
VectorNumber[2, 2i].to_i # RangeError
VectorNumber[2, :i].to_f # RangeError

Integer(VectorNumber[2]) # => 2
Integer(VectorNumber[2, "i"]) # RangeError

Returns:

  • (Integer)

Raises:

  • (RangeError)

    if any non-real part is non-zero

Since:

  • 0.1.0



41
42
43
44
45
# File 'lib/vector_number/converting.rb', line 41

def to_i
  raise_convert_error(Integer) unless numeric?(1)

  real.to_i
end

#to_rRational

Return value as a Rational if only real part is non-zero.

Examples:

VectorNumber[13.5].to_r # => (27/2)
VectorNumber[13r/12].to_r # => (13/12)
VectorNumber[2, 2i].to_r # RangeError
VectorNumber[2, :i].to_r # RangeError

Rational(VectorNumber[2]) # => (2/1)
Rational(VectorNumber[2, "i"]) # RangeError

Returns:

  • (Rational)

Raises:

  • (RangeError)

    if any non-real part is non-zero

Since:

  • 0.1.0



81
82
83
84
85
# File 'lib/vector_number/converting.rb', line 81

def to_r
  raise_convert_error(Rational) unless numeric?(1)

  real.to_r
end

#to_s(mult: :dot) {|unit, coefficient, index, operator| ... } ⇒ String

Return string representation of the vector.

An optional block can be supplied to provide customized substrings for each unit and coefficient pair. Care needs to be taken in handling VectorNumber::R and VectorNumber::I units. numeric_unit? can be used to check if a particular unit needs special handling.

Examples:

VectorNumber[5, "s"].to_s # => "5 + 1⋅\"s\""
VectorNumber["s", 5].to_s # => "1⋅\"s\" + 5"

with :mult argument

VectorNumber[5, :s].to_s(mult: :asterisk) # => "5 + 1*s"
(-VectorNumber[5, :s]).to_s(mult: "~~~") # => "-5 - 1~~~s"

with a block

VectorNumber[5, :s].to_s { |k, v| "#{format("%+.0f", v)}%#{k}" } # => "+5%1+1%s"
VectorNumber[5, :s].to_s(mult: :cross) { |k, v, i, op|
  "#{',' unless i.zero?}#{v}#{op+k.to_s unless k == VectorNumber::R}"
} # => "5,1×s"

Parameters:

  • mult (Symbol, String) (defaults to: :dot)

    text to use between coefficient and unit, can be one of the keys in MULT_STRINGS or an arbitrary string

Yield Parameters:

  • unit (Object)
  • coefficient (Numeric)
  • index (Integer)
  • operator (String)

Yield Returns:

  • (String)

    a string for this unit and coefficient

Returns:

  • (String)

Raises:

  • (ArgumentError)

    if mult is not a String and is not in MULT_STRINGS‘s keys

Since:

  • 0.1.0



48
49
50
51
52
53
54
55
56
# File 'lib/vector_number/stringifying.rb', line 48

def to_s(mult: :dot, &block)
  if !mult.is_a?(String) && !MULT_STRINGS.key?(mult)
    raise ArgumentError, "unknown key #{mult.inspect}", caller
  end
  return "0" if zero?

  operator = mult.is_a?(String) ? mult : MULT_STRINGS[mult]
  build_string(operator, &block)
end

#truncate(digits = 0) ⇒ VectorNumber

Return a new vector with every coefficient truncated using their #truncate.

Examples:

VectorNumber[5.39].truncate # => (5)
VectorNumber[-5.35i].truncate # => (-5i)
VectorNumber[-5.35i, "i"].truncate # => (-5i + 1⋅"i")
VectorNumber[-5.35i, "i"].truncate(1) # => (-5.3i + 1⋅"i")
VectorNumber[-5.35i, "i"].truncate(-1) # => (0)

Parameters:

  • digits (Integer) (defaults to: 0)

Returns:

Since:

  • 0.2.1



49
50
51
# File 'lib/vector_number/math_converting.rb', line 49

def truncate(digits = 0)
  new { _1.truncate(digits) }
end

#unit?(unit) ⇒ Boolean Also known as: key?

Check if a unit has a non-zero coefficient.

Examples:

VectorNumber["a", "b", 6].unit?("a") # => true
VectorNumber["a", "b", 6].key?("c") # => false

Parameters:

  • unit (Object)

Returns:

  • (Boolean)

Since:

  • 0.2.4



110
# File 'lib/vector_number/enumerating.rb', line 110

def unit?(unit) = @data.key?(unit)

#unitsArray<Object> Also known as: keys

Get a list of units with non-zero coefficients.

Examples:

VectorNumber["a", "b", 6].units # => ["a", "b", 1]
VectorNumber.new.keys # => []

Returns:

  • (Array<Object>)

Since:

  • 0.1.0



51
# File 'lib/vector_number/enumerating.rb', line 51

def units = @data.keys

#zero?Boolean

Returns true if there are no non-zero coefficients, and false otherwise.

This is synonymous with size being 0.

Examples:

VectorNumber["c"].zero? # => false
VectorNumber[].zero? # => true

Returns:

  • (Boolean)

See Also:

Since:

  • 0.1.0



89
# File 'lib/vector_number/querying.rb', line 89

def zero? = size.zero?