Class: Measure

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

Overview

Measure represents a decimal value and a unit. It depends on nomenclatures Unit and Dimension.

Defined Under Namespace

Classes: AmbiguousUnit, IncompatibleDimensions, InvalidExpression

Constant Summary collapse

@@dimensions =
Nomen.find_or_initialize(:dimensions)
@@units =
Nomen.find_or_initialize(:units)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Measure

Ways to instanciate a measure: $ Measure.new(55.23, 'kilogram') $ Measure.new(55.23, :kilogram) $ Measure.new('55.23 kilogram') $ Measure.new('55.23kilogram') $ Measure.new('55.23 kg') $ Measure.new('55.23kg') $ 55.23.in_kilogram $ 55.23.in(:kilogram) $ 55.23.in('kilogram')


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/measure.rb', line 53

def initialize(*args)
  value = nil
  unit = nil
  if args.size == 1
    expr = args.shift.to_s.gsub(/[[:space:]]+/, ' ').strip
    unless expr =~ /\A-?([\,\.]\d+|\d+([\,\.]\d+)?)\s*[^\s]+\z/
      raise InvalidExpression, "#{expr} cannot be parsed."
    end
    unit  = expr.gsub(/\A-?([\,\.]\d+|\d+([\,\.]\d+)?)\s*/, '').strip
    value = expr[0..-unit.size].strip.to_d # expr.split(/[a-zA-Z\s]/).first.strip.gsub(/\,/, '.').to_d
  elsif args.size == 2
    value = args.shift
    unit  = args.shift
  else
    raise ArgumentError, "wrong number of arguments (#{args.size} for 1 or 2)"
  end
  value = 0 if value.blank?
  unless value.is_a? Numeric
    raise ArgumentError, "Value can't be converted to float: #{value.inspect}"
  end
  @value = value.to_r
  unit = unit.name.to_s if unit.is_a?(Nomen::Item)
  @unit = unit.to_s
  unless @@units.items[@unit]
    units = @@units.where(symbol: @unit)
    if units.size > 1
      raise AmbiguousUnit, "The unit #{@unit} match with too many units: #{units.map(&:name).to_sentence}."
    elsif units.size.zero?
      # fail ArgumentError, "Unknown unit: #{unit.inspect}"
      @unit = 'unity'
    else
      @unit = units.first.name.to_s
    end
  end
end

Instance Attribute Details

#unitObject (readonly)

Returns the value of attribute unit


13
14
15
# File 'lib/measure.rb', line 13

def unit
  @unit
end

#valueObject (readonly)

Returns the value of attribute value


13
14
15
# File 'lib/measure.rb', line 13

def value
  @value
end

Class Method Details

.dimension(unit) ⇒ Object

Returns the dimension of the given unit


38
39
40
# File 'lib/measure.rb', line 38

def dimension(unit)
  @@units[unit].dimension.to_sym
end

.siblings(unit) ⇒ Object

Returns the units of same dimension of the given unit


33
34
35
# File 'lib/measure.rb', line 33

def siblings(unit)
  units(dimension(unit))
end

.units(dimension = nil) ⇒ Object

Lists all units. Can be filtered on a given dimension


22
23
24
25
26
27
28
29
30
# File 'lib/measure.rb', line 22

def units(dimension = nil)
  return @@units.all unless dimension
  unless @@dimensions.all.include?(dimension.to_s)
    raise ArgumentError, "Unknown dimension #{dimension.inspect}"
  end
  @@units.items.select do |_n, i|
    i.dimension.to_s == dimension.to_s
  end.keys.map(&:to_sym)
end

Instance Method Details

#!=(other) ⇒ Object

Test if the other measure is equal to self


127
128
129
130
# File 'lib/measure.rb', line 127

def !=(other)
  return true unless other.is_a?(Measure)
  to_r != other.to_r(unit)
end

#*(numeric_or_measure) ⇒ Object


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

def *(numeric_or_measure)
  if numeric_or_measure.is_a? Numeric
    self.class.new(@value * numeric_or_measure.to_r, unit)
  elsif numeric_or_measure.is_a? Measure
    # Find matching dimension
    # Convert
    raise NotImplementedError
  else
    raise ArgumentError, 'Only numerics and measures can be multiplicated to a measure'
  end
end

#+(other) ⇒ Object

Returns the dimension of a other


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

def +(other)
  unless other.is_a?(Measure)
    raise ArgumentError, 'Only measure can be added to another measure'
  end
  self.class.new(@value + other.to_r(unit), unit)
end

#[email protected]Object

Returns self of its value


204
205
206
# File 'lib/measure.rb', line 204

def [email protected]
  self
end

#-(other) ⇒ Object


191
192
193
194
195
196
# File 'lib/measure.rb', line 191

def -(other)
  unless other.is_a?(Measure)
    raise ArgumentError, 'Only measure can be substracted to another measure'
  end
  self.class.new(@value - other.to_r(unit), unit)
end

#[email protected]Object

Returns opposite of its value


199
200
201
# File 'lib/measure.rb', line 199

def [email protected]
  self.class.new(-value, unit)
end

#/(numeric_or_measure) ⇒ Object


220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/measure.rb', line 220

def /(numeric_or_measure)
  if numeric_or_measure.is_a? Numeric
    self.class.new(@value / numeric_or_measure.to_r, unit)
  elsif numeric_or_measure.is_a? Measure
    # Find matching dimension
    # Convert
    if dimension == numeric_or_measure.dimension
      to_d / numeric_or_measure.to_d(unit)
    else
      raise NotImplementedError
    end
  else
    raise ArgumentError, 'Only numerics and measures can divide to a measure'
  end
end

#<(other) ⇒ Object

Returns if self is less than other


139
140
141
142
143
144
# File 'lib/measure.rb', line 139

def <(other)
  unless other.is_a?(Measure)
    raise ArgumentError, 'Only measure can be compared to another measure'
  end
  to_r < other.to_r(unit)
end

#<=(other) ⇒ Object

Returns if self is less than or equal to other


155
156
157
158
159
160
# File 'lib/measure.rb', line 155

def <=(other)
  unless other.is_a?(Measure)
    raise ArgumentError, 'Only measure can be compared to another measure'
  end
  to_r <= other.to_r(unit)
end

#<=>(other) ⇒ Object

Returns if self is greater than other


171
172
173
174
175
176
# File 'lib/measure.rb', line 171

def <=>(other)
  unless other.is_a?(Measure)
    raise ArgumentError, 'Only measure can be compared to another measure'
  end
  to_r <=> other.to_r(unit)
end

#==(other) ⇒ Object

Test if the other measure is equal to self


133
134
135
136
# File 'lib/measure.rb', line 133

def ==(other)
  return false unless other.is_a?(Measure)
  to_r == other.to_r(unit)
end

#>(other) ⇒ Object

Returns if self is greater than other


147
148
149
150
151
152
# File 'lib/measure.rb', line 147

def >(other)
  unless other.is_a?(Measure)
    raise ArgumentError, 'Only measure can be compared to another measure'
  end
  to_r > other.to_r(unit)
end

#>=(other) ⇒ Object

Returns if self is greater than or equal to other


163
164
165
166
167
168
# File 'lib/measure.rb', line 163

def >=(other)
  unless other.is_a?(Measure)
    raise ArgumentError, 'Only measure can be compared to another measure'
  end
  to_r >= other.to_r(unit)
end

#convert(unit) ⇒ Object Also known as: in

Returns a new measure in the given unit


90
91
92
# File 'lib/measure.rb', line 90

def convert(unit)
  Measure.new(to_r(unit), unit)
end

#convert!(unit) ⇒ Object Also known as: in!

Converts measure inline without instanciating a new Measure


96
97
98
99
100
# File 'lib/measure.rb', line 96

def convert!(unit)
  @value = to_r(unit)
  @unit = unit.to_s
  self
end

#dimensionObject

Returns the dimension of a measure


122
123
124
# File 'lib/measure.rb', line 122

def dimension
  self.class.dimension(unit)
end

#inspectObject


113
114
115
# File 'lib/measure.rb', line 113

def inspect
  "#{@value.to_f} #{@unit}"
end

#localize(options = {}) ⇒ Object Also known as: l

Localize a measure FIXME: Measure l10n must be configurable in translation files.


269
270
271
# File 'lib/measure.rb', line 269

def localize(options = {})
  "#{value.to_f.localize(options)} #{@@units.items[unit].symbol}"
end

#nomenclature_unitObject

Returns the unit from the nomenclature


275
276
277
# File 'lib/measure.rb', line 275

def nomenclature_unit
  @@units[unit]
end

#round(ndigits = 0) ⇒ Object


109
110
111
# File 'lib/measure.rb', line 109

def round(ndigits = 0)
  Measure.new(to_d.round(ndigits), unit)
end

#to_d(unit = nil, precision = 16) ⇒ Object

Return BigDecimal value


263
264
265
# File 'lib/measure.rb', line 263

def to_d(unit = nil, precision = 16)
  to_r(unit, precision).to_d(precision)
end

#to_f(unit = nil, precision = 16) ⇒ Object

Return Float value


258
259
260
# File 'lib/measure.rb', line 258

def to_f(unit = nil, precision = 16)
  to_r(unit, precision).to_f
end

#to_r(other_unit = nil, precision = 16) ⇒ Object


236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/measure.rb', line 236

def to_r(other_unit = nil, precision = 16)
  if other_unit.nil?
    return value
  else
    other_unit = other_unit.name if other_unit.is_a?(Nomen::Item)
    unless @@units[other_unit]
      raise ArgumentError, "Unknown unit: #{other_unit.inspect}"
    end
    if @@units[unit.to_s].dimension != @@units[other_unit.to_s].dimension
      raise IncompatibleDimensions, "Measure can't be converted from one dimension (#{@@units[unit].dimension}) to an other (#{@@units[other_unit].dimension})"
    end
    return value if unit.to_s == other_unit.to_s
    # Reduce to base
    ref = @@units[unit]
    reduced = ((ref.a * value.to_d(precision)) / ref.d) + ref.b
    # Coeff to dest
    ref = @@units[other_unit]
    return (ref.d * ((reduced - ref.b) / ref.a)).to_r
  end
end

#to_sObject


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

def to_s
  inspect
end

#zero?Boolean

Test if measure is null

Returns:

  • (Boolean)

179
180
181
# File 'lib/measure.rb', line 179

def zero?
  @value.zero?
end