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 +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
|