ruby-units 0.3.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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