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 +11 -1
- data/LICENSE.txt +1 -1
- data/README.txt +11 -5
- data/lib/ruby-units.rb +123 -61
- data/test/test_ruby-units.rb +41 -2
- metadata +2 -2
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
data/README.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
=Ruby Units
|
2
2
|
|
3
|
-
Version: 0.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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 =
|
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
|
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
|
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
|
734
|
+
def to_i
|
711
735
|
return @scalar.to_int if self.unitless?
|
712
|
-
raise RuntimeError, 'Cannot convert to Integer
|
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 =~ /(\+\/-|±)/
|
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
|
-
#
|
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
|
-
|
1286
|
-
|
1287
|
-
|
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
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
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
|
-
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
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
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
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
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
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
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
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
|
data/test/test_ruby-units.rb
CHANGED
@@ -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.
|
7
|
-
date:
|
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
|