ruby-units 0.3.9 → 1.0.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.
data/CHANGELOG.txt CHANGED
@@ -156,4 +156,14 @@ Change Log for Ruby-units
156
156
  2006-12-15 0.3.8 * Any object that supports a 'to_unit' method will now be
157
157
  automatically coerced to a unit during math operations.
158
158
 
159
- 2006-12-15 0.3.9 * forgot to increment the version in the gem file..ooops.
159
+ 2006-12-15 0.3.9 * forgot to increment the version in the gem file..ooops.
160
+
161
+ 2007-01-12 1.0.0 * Improved handling of complex numbers. Now you can specify
162
+ '1+1i mm'.unit to get a complex unit.
163
+ * Taking the root of a negative unit will give you a complex unit
164
+ * fixed unary minus to work again
165
+ * Math.hypot now takes units. Both parameters must be the compatible
166
+ units or it will assert. Units will be converted to a common base
167
+ before use.
168
+ * Can now specify units in rational numbers, i.e., '1/4 cup'.unit
169
+ * Seems like a good time to move to 1.0 status
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006 Kevin C. Olbrich
1
+ Copyright (c) 2006-2007 Kevin C. Olbrich
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  =Ruby Units
2
2
 
3
- Version: 0.3.9
3
+ Version: 1.0.0
4
4
 
5
5
  Kevin C. Olbrich, Ph.D.
6
6
  kevin.olbrich@gmail.com
@@ -10,10 +10,14 @@ http://www.sciwerks.com
10
10
  Project page: http://rubyforge.org/projects/ruby-units
11
11
 
12
12
  ==Introduction
13
- Many technical applications make use of specialized calculations at some point. Frequently, these calculations require unit conversions to ensure accurate results. Needless to say, this is a pain to properly keep track of, and is prone to numerous errors.
13
+ Many technical applications make use of specialized calculations at some point.
14
+ Frequently, these calculations require unit conversions to ensure accurate results.
15
+ Needless to say, this is a pain to properly keep track of, and is prone to numerous errors.
14
16
 
15
17
  ==Solution
16
- The 'Ruby units' gem is designed so simplify the handling of units for scientific calculations. The units of each quantity are specified when a Unit object is created and the Unit class will handle all subsequent conversions and manipulations to ensure an accurate result.
18
+ The 'Ruby units' gem is designed so simplify the handling of units for scientific calculations.
19
+ The units of each quantity are specified when a Unit object is created and the Unit class will
20
+ handle all subsequent conversions and manipulations to ensure an accurate result.
17
21
 
18
22
  ==Installation:
19
23
  This package may be installed using:
@@ -34,7 +38,8 @@ This package may be installed using:
34
38
  unit = u'1 mm'
35
39
  unit = '1 mm'.unit
36
40
  unit = '1 mm'.u
37
-
41
+ unit = '1/4 cup'.unit # Rational number
42
+ unit = '1+1i mm'.unit # Complex Number
38
43
 
39
44
  ==Rules:
40
45
  1. only 1 quantity per unit (with 2 exceptions... 6'5" and '8 lbs 8 oz')
@@ -131,5 +136,6 @@ If only one ":" is present, it is interpreted as the separator between hours and
131
136
  works so long as the starting point has an integer scalar
132
137
 
133
138
  ==Math functions
134
- All Trig math functions (sin, cos, sinh, ...) can take a unit as their parameter. It will be converted to radians and then used if possible.
139
+ All Trig math functions (sin, cos, sinh, hypot...) can take a unit as their parameter.
140
+ It will be converted to radians and then used if possible.
135
141
 
data/lib/ruby-units.rb CHANGED
@@ -2,7 +2,7 @@ require 'mathn'
2
2
  require 'rational'
3
3
  require 'date'
4
4
  require 'parsedate'
5
- # = Ruby Units 0.3.9
5
+ # = Ruby Units 1.0.0
6
6
  #
7
7
  # Copyright 2006 by Kevin C. Olbrich, Ph.D.
8
8
  #
@@ -40,7 +40,7 @@ require 'parsedate'
40
40
  class Unit < Numeric
41
41
  require 'units'
42
42
  # pre-generate hashes from unit definitions for performance.
43
- VERSION = '0.3.9'
43
+ VERSION = '1.0.0'
44
44
  @@USER_DEFINITIONS = {}
45
45
  @@PREFIX_VALUES = {}
46
46
  @@PREFIX_MAP = {}
@@ -54,11 +54,15 @@ class Unit < Numeric
54
54
  TIME_REGEX = /(\d+)*:(\d+)*:*(\d+)*[:,]*(\d+)*/
55
55
  LBS_OZ_REGEX = /(\d+)\s*(?:#|lbs|pounds)+[\s,]*(\d+)\s*(?:oz|ounces)/
56
56
  SCI_NUMBER = %r{([+-]?\d*[.]?\d+(?:[Ee][+-]?)?\d*)}
57
+ RATIONAL_NUMBER = /(\d+)\/(\d+)/
58
+ COMPLEX_NUMBER = /#{SCI_NUMBER}?#{SCI_NUMBER}i\b/
57
59
  NUMBER_REGEX = /#{SCI_NUMBER}*\s*(.+)?/
58
60
  UNIT_STRING_REGEX = /#{SCI_NUMBER}*\s*([^\/]*)\/*(.+)*/
59
61
  TOP_REGEX = /([^ \*]+)(?:\^|\*\*)([\d-]+)/
60
62
  BOTTOM_REGEX = /([^* ]+)(?:\^|\*\*)(\d+)/
61
-
63
+ UNCERTAIN_REGEX = /#{SCI_NUMBER}\s*\+\/-\s*#{SCI_NUMBER}\s(.+)/
64
+ COMPLEX_REGEX = /#{COMPLEX_NUMBER}\s?(.+)?/
65
+ RATIONAL_REGEX = /#{RATIONAL_NUMBER}\s?(.+)?/
62
66
  KELVIN = ['<kelvin>']
63
67
  FARENHEIT = ['<farenheit>']
64
68
  RANKINE = ['<rankine>']
@@ -188,7 +192,7 @@ class Unit < Numeric
188
192
  cached = @@cached_units[options[1]] * options[0]
189
193
  copy(cached)
190
194
  rescue
191
- initialize("#{options[0]} #{options[1]}")
195
+ initialize("#{options[0]} #{(options[1].units rescue options[1])}")
192
196
  end
193
197
  return
194
198
  end
@@ -233,7 +237,7 @@ class Unit < Numeric
233
237
 
234
238
  unary_unit = self.units || ""
235
239
  opt_units = options[0].scan(NUMBER_REGEX)[0][1] if String === options[0]
236
- unless @@cached_units.keys.include?(opt_units) || (opt_units =~ /(temp|deg(C|K|R|F))|(pounds|lbs[ ,]\d+ ounces|oz)|('\d+")|(ft|feet[ ,]\d+ in|inch|inches)|%|(#{TIME_REGEX})/)
240
+ unless @@cached_units.keys.include?(opt_units) || (opt_units =~ /(temp|deg(C|K|R|F))|(pounds|lbs[ ,]\d+ ounces|oz)|('\d+")|(ft|feet[ ,]\d+ in|inch|inches)|%|(#{TIME_REGEX})|i\s?(.+)?/)
237
241
  @@cached_units[opt_units] = (self.scalar == 1 ? self : opt_units.unit) if opt_units && !opt_units.empty?
238
242
  end
239
243
  unless @@cached_units.keys.include?(unary_unit) || (unary_unit =~ /(temp|deg)(C|K|R|F)/) then
@@ -345,7 +349,12 @@ class Unit < Numeric
345
349
  out = (Time.gm(0) + self).strftime(target_units)
346
350
  end
347
351
  else
348
- out = "#{'%g' % @scalar} #{self.units}".strip
352
+ out = case @scalar
353
+ when Rational :
354
+ "#{@scalar} #{self.units}"
355
+ else
356
+ "#{'%g' % @scalar} #{self.units}"
357
+ end.strip
349
358
  end
350
359
  @output = {target_units => out}
351
360
  return out
@@ -511,6 +520,13 @@ class Unit < Numeric
511
520
  end
512
521
  end
513
522
 
523
+ def real
524
+ return Unit.new(self.scalar.real,self.units)
525
+ end
526
+
527
+ def imag
528
+ return Unit.new(self.scalar.imag, self.units)
529
+ end
514
530
 
515
531
  # returns the unit raised to the n-th power. Integers only
516
532
  def power(n)
@@ -550,7 +566,7 @@ class Unit < Numeric
550
566
  r = ((x/n)*(n-1)).to_int
551
567
  r.times {|x| den.delete_at(den.index(item))}
552
568
  end
553
- q = @scalar**(1/n)
569
+ q = @scalar < 0 ? (-1)**Rational(1,n) * (@scalar.abs)**Rational(1,n) : @scalar**Rational(1,n)
554
570
  Unit.new(:scalar=>q,:numerator=>num,:denominator=>den)
555
571
  end
556
572
 
@@ -643,7 +659,14 @@ class Unit < Numeric
643
659
  # converts the unit back to a float if it is unitless. Otherwise raises an exception
644
660
  def to_f
645
661
  return @scalar.to_f if self.unitless?
646
- raise RuntimeError, "Can't convert to float unless unitless. Use Unit#scalar"
662
+ raise RuntimeError, "Can't convert to Float unless unitless. Use Unit#scalar"
663
+ end
664
+
665
+ # converts the unit back to a complex if it is unitless. Otherwise raises an exception
666
+
667
+ def to_c
668
+ return Complex(@scalar) if self.unitless?
669
+ raise RuntimeError, "Can't convert to Complex unless unitless. Use Unit#scalar"
647
670
  end
648
671
 
649
672
  # returns the 'unit' part of the Unit object without the scalar
@@ -688,7 +711,8 @@ class Unit < Numeric
688
711
  # negates the scalar of the Unit
689
712
  def -@
690
713
  return -@scalar if self.unitless?
691
- Unit.new(-@scalar,@numerator,@denominator)
714
+ #Unit.new(-@scalar,@numerator,@denominator)
715
+ -1 * self.dup
692
716
  end
693
717
 
694
718
  # returns abs of scalar, without the units
@@ -707,17 +731,17 @@ class Unit < Numeric
707
731
  end
708
732
 
709
733
  # if unitless, returns an int, otherwise raises an error
710
- def to_int
734
+ def to_i
711
735
  return @scalar.to_int if self.unitless?
712
- raise RuntimeError, 'Cannot convert to Integer, use Unit#scalar'
736
+ raise RuntimeError, 'Cannot convert to Integer unless unitless'
713
737
  end
738
+ alias :to_int :to_i
714
739
 
715
740
  # Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch.
716
741
  def to_time
717
742
  Time.at(self)
718
743
  end
719
744
  alias :time :to_time
720
- alias :to_i :to_int
721
745
 
722
746
  def truncate
723
747
  return @scalar.truncate if self.unitless?
@@ -955,11 +979,32 @@ class Unit < Numeric
955
979
  unit_string = "#{$1} USD"
956
980
  end
957
981
 
982
+
958
983
  unit_string.gsub!(/%/,'percent')
959
984
  unit_string.gsub!(/'/,'feet')
960
985
  unit_string.gsub!(/"/,'inch')
961
986
  unit_string.gsub!(/#/,'pound')
962
-
987
+ if defined?(Uncertain) && unit_string =~ /(\+\/-|&plusmn;)/
988
+ value, uncertainty, unit_s = unit_string.scan(UNCERTAIN_REGEX)[0]
989
+ result = unit_s.unit * Uncertain(value.to_f,uncertainty.to_f)
990
+ copy(result)
991
+ return
992
+ end
993
+
994
+ if defined?(Complex) && unit_string =~ COMPLEX_NUMBER
995
+ real, imaginary, unit_s = unit_string.scan(COMPLEX_REGEX)[0]
996
+ result = Unit(unit_s || '1') * Complex(real.to_f,imaginary.to_f)
997
+ copy(result)
998
+ return
999
+ end
1000
+
1001
+ if defined?(Rational) && unit_string =~ RATIONAL_NUMBER
1002
+ numerator, denominator, unit_s = unit_string.scan(RATIONAL_REGEX)[0]
1003
+ result = Unit(unit_s || '1') * Rational(numerator.to_i,denominator.to_i)
1004
+ copy(result)
1005
+ return
1006
+ end
1007
+
963
1008
  unit_string =~ NUMBER_REGEX
964
1009
  unit = @@cached_units[$2]
965
1010
  mult = ($1.empty? ? 1.0 : $1.to_f) rescue 1.0
@@ -1212,7 +1257,11 @@ class String
1212
1257
  end
1213
1258
 
1214
1259
 
1215
- # Allow time objects to use
1260
+ #
1261
+ # Time math is handled slightly differently. The difference is considered to be an exact duration if
1262
+ # the subtracted value is in hours, minutes, or seconds. It is rounded to the nearest day if the offset
1263
+ # is in years, decades, or centuries. This leads to less precise values, but ones that match the
1264
+ # calendar better.
1216
1265
  class Time
1217
1266
 
1218
1267
  class << self
@@ -1263,6 +1312,7 @@ class Time
1263
1312
  end
1264
1313
 
1265
1314
  alias :unit_sub :-
1315
+
1266
1316
  def -(other)
1267
1317
  case other
1268
1318
  when Unit:
@@ -1280,61 +1330,70 @@ class Time
1280
1330
  end
1281
1331
  end
1282
1332
 
1333
+ # Math will convert unit objects to radians and then attempt to use the value for
1334
+ # trigonometric functions.
1283
1335
  module Math
1336
+ alias unit_sqrt sqrt
1337
+ def sqrt(n)
1338
+ Unit === n ? n**(1/2) : unit_sqrt(n)
1339
+ end
1340
+
1284
1341
  alias unit_sin sin
1285
- def sin(n)
1286
- if Unit === n
1287
- unit_sin(n.to('radian').scalar)
1288
- else
1289
- unit_sin(n)
1290
- end
1291
- end
1342
+ def sin(n)
1343
+ Unit === n ? unit_sin(n.to('radian').scalar) : unit_sin(n)
1344
+ end
1292
1345
 
1293
- alias unit_cos cos
1294
- def cos(n)
1295
- if Unit === n
1296
- unit_cos(n.to('radian').scalar)
1297
- else
1298
- unit_cos(n)
1299
- end
1300
- end
1346
+ alias unit_cos cos
1347
+ def cos(n)
1348
+ Unit === n ? unit_cos(n.to('radian').scalar) : unit_cos(n)
1349
+ end
1301
1350
 
1302
- alias unit_sinh sinh
1303
- def sinh(n)
1304
- if Unit === n
1305
- unit_sinh(n.to('radian').scalar)
1306
- else
1307
- unit_sinh(n)
1308
- end
1309
- end
1351
+ alias unit_sinh sinh
1352
+ def sinh(n)
1353
+ Unit === n ? unit_sinh(n.to('radian').scalar) : unit_sinh(n)
1354
+ end
1310
1355
 
1311
- alias unit_cosh cosh
1312
- def cosh(n)
1313
- if Unit === n
1314
- unit_cosh(n.to('radian').scalar)
1315
- else
1316
- unit_cosh(n)
1317
- end
1318
- end
1356
+ alias unit_cosh cosh
1357
+ def cosh(n)
1358
+ Unit === n ? unit_cosh(n.to('radian').scalar) : unit_cosh(n)
1359
+ end
1319
1360
 
1320
- alias unit_tan tan
1321
- def tan(n)
1322
- if Unit === n
1323
- unit_tan(n.to('radian').scalar)
1324
- else
1325
- unit_tan(n)
1326
- end
1327
- end
1361
+ alias unit_tan tan
1362
+ def tan(n)
1363
+ Unit === n ? unit_tan(n.to('radian').scalar) : unit_tan(n)
1364
+ end
1328
1365
 
1329
- alias unit_tanh tanh
1330
- def tanh(n)
1331
- if Unit === n
1332
- unit_tanh(n.to('radian').scalar)
1333
- else
1334
- unit_tanh(n)
1335
- end
1336
- end
1366
+ alias unit_tanh tanh
1367
+ def tanh(n)
1368
+ Unit === n ? unit_tanh(n.to('radian').scalar) : unit_tanh(n)
1369
+ end
1337
1370
 
1371
+ alias unit_hypot hypot
1372
+ # Convert parameters to consistent units and perform the function
1373
+ def hypot(x,y)
1374
+ if Unit === x && Unit === y
1375
+ (x**2 + y**2)**(1/2)
1376
+ else
1377
+ unit_hypot(x,y)
1378
+ end
1379
+ end
1380
+
1381
+ alias unit_atan2 atan2
1382
+ def atan2(x,y)
1383
+ case
1384
+ when (Unit === x && Unit === y) && (x !~ y)
1385
+ raise ArgumentError, "Incompatible Units"
1386
+ when (Unit === x && Unit === y) && (x =~ y)
1387
+ unit_atan2(x.base_scalar, y.base_scalar)
1388
+ else
1389
+ unit_atan2(x,y)
1390
+ end
1391
+ end
1392
+
1393
+ module_function :unit_hypot
1394
+ module_function :hypot
1395
+ module_function :unit_sqrt
1396
+ module_function :sqrt
1338
1397
  module_function :unit_sin
1339
1398
  module_function :sin
1340
1399
  module_function :unit_cos
@@ -1347,6 +1406,9 @@ module Math
1347
1406
  module_function :tan
1348
1407
  module_function :unit_tanh
1349
1408
  module_function :tanh
1409
+ module_function :unit_atan2
1410
+ module_function :atan2
1411
+
1350
1412
  end
1351
1413
 
1352
1414
  Unit.setup
@@ -129,8 +129,8 @@ class TestRubyUnits < Test::Unit::TestCase
129
129
  end
130
130
 
131
131
  def test_unary_minus
132
- unit1 = Unit.new("1 mm")
133
- unit2 = Unit.new("-1 mm")
132
+ unit1 = Unit.new("1 mm^2")
133
+ unit2 = Unit.new("-1 mm^2")
134
134
  assert_equal unit1, -unit2
135
135
  end
136
136
 
@@ -847,4 +847,43 @@ class TestRubyUnits < Test::Unit::TestCase
847
847
  b = '1 mm'.unit
848
848
  assert_equal '2 mm'.unit, b + a
849
849
  end
850
+
851
+ def test_create_with_other_unit
852
+ a = '1 g'.unit
853
+ b = 0.unit(a)
854
+ assert_equal b, '0 g'.unit
855
+ end
856
+
857
+ def test_sqrt
858
+ assert_equal '1 mm'.unit, Math.sqrt('1 mm^2'.unit)
859
+ assert_raises(ArgumentError) {Math.sqrt('1 mm'.unit)}
860
+ assert_equal Math.sqrt("-1 mm^2".unit), '0+1i mm'.unit
861
+ end
862
+
863
+ def test_hypot
864
+ assert_equal Math.hypot('3 mm'.unit,'4 mm'.unit), '5 mm'.unit
865
+ assert_raises(ArgumentError) { Math.hypot('3 mm'.unit, '4 kg'.unit)}
866
+ end
867
+
868
+ def test_complex
869
+ assert_equal '1+1i mm'.unit.scalar, Complex(1,1)
870
+ assert_equal '1+1i mm'.unit.real, '1 mm'.unit
871
+ assert_equal '1+1i mm'.unit.imag, '1i mm'.unit
872
+ assert_equal '1+1i'.unit, Complex(1,1)
873
+ assert_raises (RuntimeError) { '1+1i mm'.unit.to_c}
874
+ end
875
+
876
+ def test_atan2
877
+ assert_equal Math.atan2('1 mm'.unit,'1 mm'.unit), Math.atan2(1,1)
878
+ assert_raises (ArgumentError) {Math.atan2('1 mm'.unit, '1 lb'.unit)}
879
+ assert_raises (ArgumentError) {Math.atan2('1 mm'.unit, 1)}
880
+ end
881
+
882
+ def test_rational_units
883
+ assert_equal '1/4 cup'.unit, '0.25 cup'.unit
884
+ assert_equal '1/4 in/s'.unit, '0.25 in/s'.unit
885
+ assert_equal '1/4'.unit, 0.25
886
+ end
887
+
888
+
850
889
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: ruby-units
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.9
7
- date: 2006-12-15 00:00:00 -05:00
6
+ version: 1.0.0
7
+ date: 2007-01-12 00:00:00 -05:00
8
8
  summary: A model that performs unit conversions and unit math
9
9
  require_paths:
10
10
  - lib