1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

* complex.c, rational.c, lib/cmath.rb, lib/date.rb lib/date/delta*:

reverted r27484-27486.  now official spec.



git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@27503 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
tadf 2010-04-26 11:14:40 +00:00
parent 76b33197e2
commit a90469602c
9 changed files with 1147 additions and 2 deletions

View file

@ -1,3 +1,8 @@
Mon Apr 26 20:11:05 2010 Tadayoshi Funaba <tadf@dotrb.org>
* complex.c, rational.c, lib/cmath.rb, lib/date.rb lib/date/delta*:
reverted r27484-27486. now official spec.
Mon Apr 26 15:42:59 2010 NAKAMURA Usaku <usa@ruby-lang.org>
* ext/json/generator/generator.c (convert_UTF8_to_JSON_ASCII): get rid

View file

@ -1333,6 +1333,20 @@ nucomp_to_r(VALUE self)
return f_to_r(dat->real);
}
/*
* call-seq:
* cmp.rationalize([eps]) -> rational
*
* Returns the value as a rational if possible. An optional argument
* eps is always ignored.
*/
static VALUE
nucomp_rationalize(int argc, VALUE *argv, VALUE self)
{
rb_scan_args(argc, argv, "01", NULL);
return nucomp_to_r(self);
}
/*
* call-seq:
* nil.to_c -> (0+0i)
@ -1923,6 +1937,7 @@ Init_Complex(void)
rb_define_method(rb_cComplex, "to_i", nucomp_to_i, 0);
rb_define_method(rb_cComplex, "to_f", nucomp_to_f, 0);
rb_define_method(rb_cComplex, "to_r", nucomp_to_r, 0);
rb_define_method(rb_cComplex, "rationalize", nucomp_rationalize, -1);
rb_define_method(rb_cNilClass, "to_c", nilclass_to_c, 0);
rb_define_method(rb_cNumeric, "to_c", numeric_to_c, 0);

View file

@ -4,8 +4,10 @@ module CMath
alias exp! exp
alias log! log
alias log2! log2
alias log10! log10
alias sqrt! sqrt
alias cbrt! cbrt
alias sin! sin
alias cos! cos
@ -47,6 +49,14 @@ module CMath
end
end
def log2(z)
if z.real? and z >= 0
log2!(z)
else
log(z) / log!(2)
end
end
def log10(z)
if z.real? and z >= 0
log10!(z)
@ -74,6 +84,14 @@ module CMath
end
end
def cbrt(z)
if z.real? and z >= 0
cbrt!(z)
else
Complex(z) ** (1.0/3)
end
end
def sin(z)
if z.real?
sin!(z)
@ -186,10 +204,14 @@ module CMath
module_function :exp
module_function :log!
module_function :log
module_function :log2!
module_function :log2
module_function :log10!
module_function :log10
module_function :sqrt!
module_function :sqrt
module_function :cbrt!
module_function :cbrt
module_function :sin!
module_function :sin
@ -221,8 +243,6 @@ module CMath
module_function :atanh!
module_function :atanh
module_function :log2
module_function :cbrt
module_function :frexp
module_function :ldexp
module_function :hypot

View file

@ -1371,6 +1371,12 @@ class Date
case other
when Numeric; return @ajd <=> other
when Date; return @ajd <=> other.ajd
else
begin
l, r = other.coerce(self)
return l <=> r
rescue NoMethodError
end
end
nil
end
@ -1385,6 +1391,9 @@ class Date
case other
when Numeric; return jd == other
when Date; return jd == other.jd
else
l, r = other.coerce(self)
return l === r
end
false
end

431
lib/date/delta.rb Normal file
View file

@ -0,0 +1,431 @@
# delta.rb: Written by Tadayoshi Funaba 2004-2009
require 'date'
require 'date/delta/parser'
class Date
class Delta
include Comparable
UNIT_PREFIXES = {
'yotta' => Rational(10**24),
'zetta' => Rational(10**21),
'exa' => Rational(10**18),
'peta' => Rational(10**15),
'tera' => Rational(10**12),
'giga' => Rational(10**9),
'mega' => Rational(10**6),
'kilo' => Rational(10**3),
'hecto' => Rational(10**2),
'deca' => Rational(10**1),
'deka' => Rational(10**1),
'deci' => Rational(1, 10**1),
'centi' => Rational(1, 10**2),
'milli' => Rational(1, 10**3),
'decimilli' => Rational(1, 10**4),
'centimilli' => Rational(1, 10**5),
'micro' => Rational(1, 10**6),
'nano' => Rational(1, 10**9),
'millimicro' => Rational(1, 10**9),
'pico' => Rational(1, 10**12),
'micromicro' => Rational(1, 10**12),
'femto' => Rational(1, 10**15),
'atto' => Rational(1, 10**18),
'zepto' => Rational(1, 10**21),
'yocto' => Rational(1, 10**24)
}
IUNITS = {
'year' => Complex(0, 12),
'month' => Complex(0, 1)
}
RUNITS = {
'day' => Rational(1),
'week' => Rational(7),
'sennight' => Rational(7),
'fortnight' => Rational(14),
'hour' => Rational(1, 24),
'minute' => Rational(1, 1440),
'second' => Rational(1, 86400)
}
UNIT_PREFIXES.each do |k, v|
RUNITS[k + 'second'] = v * RUNITS['second']
end
remove_const :UNIT_PREFIXES
UNITS = {}
IUNITS.each do |k, v|
UNITS[k] = v
end
RUNITS.each do |k, v|
UNITS[k] = v
end
UNITS4KEY = {}
UNITS.each do |k, v|
UNITS4KEY[k] = UNITS4KEY[k + 's'] = v
end
UNITS4KEY['y'] = UNITS4KEY['years']
UNITS4KEY['yr'] = UNITS4KEY['years']
UNITS4KEY['yrs'] = UNITS4KEY['years']
UNITS4KEY['m'] = UNITS4KEY['months']
UNITS4KEY['mo'] = UNITS4KEY['months']
UNITS4KEY['mon'] = UNITS4KEY['months']
UNITS4KEY['mnth'] = UNITS4KEY['months']
UNITS4KEY['mnths'] = UNITS4KEY['months']
UNITS4KEY['w'] = UNITS4KEY['weeks']
UNITS4KEY['wk'] = UNITS4KEY['weeks']
UNITS4KEY['d'] = UNITS4KEY['days']
UNITS4KEY['dy'] = UNITS4KEY['days']
UNITS4KEY['dys'] = UNITS4KEY['days']
UNITS4KEY['h'] = UNITS4KEY['hours']
UNITS4KEY['hr'] = UNITS4KEY['hours']
UNITS4KEY['hrs'] = UNITS4KEY['hours']
UNITS4KEY['min'] = UNITS4KEY['minutes']
UNITS4KEY['mins'] = UNITS4KEY['minutes']
UNITS4KEY['s'] = UNITS4KEY['seconds']
UNITS4KEY['sec'] = UNITS4KEY['seconds']
UNITS4KEY['secs'] = UNITS4KEY['seconds']
UNITS4KEY['ms'] = UNITS4KEY['milliseconds']
UNITS4KEY['msec'] = UNITS4KEY['milliseconds']
UNITS4KEY['msecs'] = UNITS4KEY['milliseconds']
UNITS4KEY['milli'] = UNITS4KEY['milliseconds']
UNITS4KEY['us'] = UNITS4KEY['microseconds']
UNITS4KEY['usec'] = UNITS4KEY['microseconds']
UNITS4KEY['usecs'] = UNITS4KEY['microseconds']
UNITS4KEY['micro'] = UNITS4KEY['microseconds']
UNITS4KEY['ns'] = UNITS4KEY['nanoseconds']
UNITS4KEY['nsec'] = UNITS4KEY['nanoseconds']
UNITS4KEY['nsecs'] = UNITS4KEY['nanoseconds']
UNITS4KEY['nano'] = UNITS4KEY['nanoseconds']
def self.delta_to_dhms(delta)
fr = delta.imag.abs
y, fr = fr.divmod(12)
m, fr = fr.divmod(1)
if delta.imag < 0
y = -y
m = -m
end
fr = delta.real.abs
ss, fr = fr.divmod(SECONDS_IN_DAY) # 4p
d, ss = ss.divmod(86400)
h, ss = ss.divmod(3600)
min, s = ss.divmod(60)
if delta.real < 0
d = -d
h = -h
min = -min
s = -s
end
return y, m, d, h, min, s, fr
end
def self.dhms_to_delta(y, m, d, h, min, s, fr)
fr = 0 if fr == 0
Complex(0, y.to_i * 12 + m.to_i) +
Rational(d * 86400 + h * 3600 + min * 60 + (s + fr), 86400) # 4p
end
def initialize(delta)
@delta = delta
@__ca__ = {}
end
class << self; alias_method :new!, :new end
def self.new(arg=0, h=0, min=0, s=0)
if Hash === arg
d = Complex(0)
arg.each do |k, v|
k = k.to_s.downcase
unless UNITS4KEY[k]
raise ArgumentError, "unknown keyword #{k}"
end
d += v * UNITS4KEY[k]
end
else
d = dhms_to_delta(0, 0, arg, h, min, s, 0)
end
new!(d)
end
UNITS.each_key do |k|
module_eval <<-"end;"
def self.#{k}s(n=1)
new(:d=>n * UNITS['#{k}'])
end
end;
end
class << self; alias_method :mins, :minutes end
class << self; alias_method :secs, :seconds end
def self.parse(str)
d = begin (@@pa ||= Parser.new).parse(str)
rescue Racc::ParseError
raise ArgumentError, 'syntax error'
end
new!(d)
end
def self.diff(d1, d2) new(d1.ajd - d2.ajd) end
class << self
def once(*ids) # :nodoc: -- restricted
for id in ids
module_eval <<-"end;"
alias_method :__#{id.object_id}__, :#{id.to_s}
private :__#{id.object_id}__
def #{id.to_s}(*args)
@__ca__[#{id.object_id}] ||= __#{id.object_id}__(*args)
end
end;
end
end
private :once
end
def dhms() self.class.delta_to_dhms(@delta) end
once :dhms
def delta() @delta end
protected :delta
def years() dhms[0] end
def months() dhms[1] end
def days() dhms[2] end
def hours() dhms[3] end
def minutes() dhms[4] end
def seconds() dhms[5] end
def second_fractions() dhms[6] end
alias_method :mins, :minutes
alias_method :secs, :seconds
alias_method :sec_fractions, :second_fractions
RUNITS.each_key do |k|
module_eval <<-"end;"
def in_#{k}s(u=1)
if @delta.imag != 0
raise ArgumentError, "#{k}: #{self} has month"
end
@delta.real / (u * RUNITS['#{k}'])
end
end;
end
alias_method :in_mins, :in_minutes
alias_method :in_secs, :in_seconds
def zero?() @delta.zero? end
def nonzero?() unless zero? then self end end
def integer? () @delta.imag == 0 && @delta.real.integer? end
def -@ () self.class.new!(-@delta) end
def +@ () self.class.new!(+@delta) end
def dx_addsub(m, n)
case n
when Numeric; return self.class.new!(@delta.__send__(m, n))
when Delta; return self.class.new!(@delta.__send__(m, n.delta))
else
l, r = n.coerce(self)
return l.__send__(m, r)
end
end
private :dx_addsub
def + (n) dx_addsub(:+, n) end
def - (n) dx_addsub(:-, n) end
def dx_muldiv(m, n)
case n
when Numeric
return self.class.new!(@delta.__send__(m, n))
else
l, r = n.coerce(self)
return l.__send__(m, r)
end
end
private :dx_muldiv
def * (n) dx_muldiv(:*, n) end
def / (n) dx_muldiv(:/, n) end
def dx_conv1(m, n)
if @delta.imag != 0
raise ArgumentError, "#{m}: #{self} has month"
end
case n
when Numeric
return self.class.new!(Complex(@delta.real.__send__(m, n), 0))
else
l, r = n.coerce(self)
return l.__send__(m, r)
end
end
private :dx_conv1
def % (n) dx_conv1(:%, n) end
def div(n) dx_conv1(:div, n) end
def modulo(n) dx_conv1(:modulo, n) end
def divmod(n) [div(n), modulo(n)] end
def quotient(n)
if @delta.imag != 0
raise ArgumentError, "quotient: #{self} has month"
end
case n
when Numeric
return self.class.new!(Complex((@delta.real / n).truncate))
else
l, r = n.coerce(self)
return l.__send__(m, r)
end
end
def remainder(n) dx_conv1(:remainder, n) end
def quotrem(n) [quotient(n), remainder(n)] end
def ** (n) dx_conv1(:**, n) end
def quo(n) dx_muldiv(:quo, n) end
def <=> (other)
if @delta.imag != 0
raise ArgumentError, "<=>: #{self} has month"
end
case other
when Numeric; return @delta.real <=> other
when Delta; return @delta.real <=> other.delta.real
else
begin
l, r = other.coerce(self)
return l <=> r
rescue NoMethodError
end
end
nil
end
def == (other)
case other
when Numeric; return @delta == other
when Delta; return @delta == other
else
begin
l, r = other.coerce(self)
return l == r
rescue NoMethodError
end
end
nil
end
def coerce(other)
case other
when Numeric; return other, @delta
else
super
end
end
def eql? (other) Delta === other && self == other end
def hash() @delta.hash end
def dx_conv0(m)
if @delta.imag != 0
raise ArgumentError, "#{m}: #{self} has month"
end
@delta.real.__send__(m)
end
private :dx_conv0
def abs() dx_conv0(:abs) end
def ceil() dx_conv0(:ceil) end
def floor() dx_conv0(:floor) end
def round() dx_conv0(:round) end
def truncate() dx_conv0(:truncate) end
def to_i() dx_conv0(:to_i) end
def to_f() dx_conv0(:to_f) end
def to_r() dx_conv0(:to_r) end
def to_c() @delta end
alias_method :to_int, :to_i
def inspect() format('#<%s: %s (%s)>', self.class, to_s, @delta) end
def to_s
format(%(%s(%dd %.02d:%02d'%02d"%03d)%s(%dy %dm)), # '
if @delta.real < 0 then '-' else '+' end,
days.abs, hours.abs, mins.abs, secs.abs, sec_fractions.abs * 1000,
if @delta.imag < 0 then '-' else '+' end,
years.abs, months.abs)
end
def marshal_dump() @delta end
def marshal_load(a)
@delta = a
@__ca__ = {}
end
end
end
vsave = $VERBOSE
$VERBOSE = false
class Date
def + (n)
case n
when Numeric; return self.class.new!(@ajd + n, @of, @sg)
when Delta
d = n.__send__(:delta)
return (self >> d.imag) + d.real
end
raise TypeError, 'expected numeric'
end
def - (x)
case x
when Numeric; return self.class.new!(@ajd - x, @of, @sg)
when Date; return @ajd - x.ajd
when Delta
d = x.__send__(:delta)
return (self << d.imag) - d.real
end
raise TypeError, 'expected numeric'
end
end
$VERBOSE = vsave

301
lib/date/delta/parser.rb Normal file
View file

@ -0,0 +1,301 @@
#
# DO NOT MODIFY!!!!
# This file is automatically generated by racc 1.4.5
# from racc grammer file "parser.ry".
#
require 'racc/parser'
class Date
class Delta
class Parser < Racc::Parser
module_eval <<'..end parser.ry modeval..id43bff5dec9', 'parser.ry', 42
def lookup(str)
t = str.downcase
k = UNITS4KEY[t]
return [:UNIT, k] if k
return [:AND, nil] if t == 'and'
return [:UNKNOWNWORD, nil]
end
def parse(str)
@q = []
until str.empty?
case str
when /\A\s+/
when /\AP(\d+y)?(\d+m)?(\d+d)?t?(\d+h)?(\d+m)?(\d+s)?(\d+w)?/i
y, m, d, h, min, s, w =
[$1, $2, $3, $4, $5, $6, $7].collect{|x| x.to_i}
y *= UNITS4KEY['years']
m *= UNITS4KEY['months']
d *= UNITS4KEY['days']
h *= UNITS4KEY['hours']
min *= UNITS4KEY['minutes']
s *= UNITS4KEY['seconds']
w *= UNITS4KEY['weeks']
@q.push [:DURATION, y + m + d + h + min + s + w]
when /\A\d+/
@q.push [:DIGITS, $&.to_i]
when /\A[a-z]+/i
@q.push lookup($&)
when /\A.|\n/
@q.push [$&, $&]
end
str = $'
end
@q.push [false, false]
do_parse
end
def next_token
@q.shift
end
..end parser.ry modeval..id43bff5dec9
##### racc 1.4.5 generates ###
racc_reduce_table = [
0, 0, :racc_error,
1, 16, :_reduce_none,
1, 17, :_reduce_none,
1, 17, :_reduce_none,
3, 17, :_reduce_4,
3, 17, :_reduce_5,
3, 17, :_reduce_6,
3, 17, :_reduce_7,
3, 17, :_reduce_8,
3, 17, :_reduce_9,
3, 17, :_reduce_10,
2, 17, :_reduce_11,
2, 17, :_reduce_12,
3, 17, :_reduce_13,
2, 18, :_reduce_14,
0, 20, :_reduce_15,
1, 20, :_reduce_none,
1, 19, :_reduce_none ]
racc_reduce_n = 18
racc_shift_n = 32
racc_action_table = [
13, 14, 15, 16, 17, 18, 19, 4, 27, 23,
8, 9, 1, 4, 25, 2, 8, 9, 1, 4,
24, 2, 8, 9, 1, 4, 21, 2, 8, 9,
1, 4, 11, 2, 8, 9, 1, 4, 26, 2,
8, 9, 1, 4, nil, 2, 8, 9, 1, 4,
nil, 2, 8, 9, 1, nil, nil, 2, 13, 14,
15, 16, 17, 18, 19, 13, 14, 15, 13, 14,
15, 13, 14, 15, 13, 14, 15 ]
racc_action_check = [
10, 10, 10, 10, 10, 10, 10, 17, 15, 10,
17, 17, 17, 18, 13, 17, 18, 18, 18, 4,
11, 18, 4, 4, 4, 1, 9, 4, 1, 1,
1, 8, 3, 1, 8, 8, 8, 19, 14, 8,
19, 19, 19, 0, nil, 19, 0, 0, 0, 16,
nil, 0, 16, 16, 16, nil, nil, 16, 5, 5,
5, 5, 5, 5, 5, 30, 30, 30, 28, 28,
28, 29, 29, 29, 31, 31, 31 ]
racc_action_pointer = [
37, 19, nil, 32, 13, 55, nil, nil, 25, 13,
-3, 20, nil, 4, 28, -2, 43, 1, 7, 31,
nil, nil, nil, nil, nil, nil, nil, nil, 65, 68,
62, 71 ]
racc_action_default = [
-18, -18, -17, -18, -18, -1, -2, -3, -18, -15,
-18, -18, -12, -18, -18, -18, -18, -18, -18, -18,
-11, -16, -14, -13, 32, -10, -8, -9, -4, -5,
-6, -7 ]
racc_goto_table = [
5, 10, 3, 22, 12, nil, nil, nil, 20, nil,
nil, nil, nil, nil, nil, nil, 28, 29, 30, 31 ]
racc_goto_check = [
2, 2, 1, 5, 2, nil, nil, nil, 2, nil,
nil, nil, nil, nil, nil, nil, 2, 2, 2, 2 ]
racc_goto_pointer = [
nil, 2, 0, nil, nil, -6 ]
racc_goto_default = [
nil, nil, nil, 6, 7, nil ]
racc_token_table = {
false => 0,
Object.new => 1,
:UNARY => 2,
"^" => 3,
"*" => 4,
"/" => 5,
"+" => 6,
"," => 7,
:AND => 8,
"-" => 9,
:DIGITS => 10,
"(" => 11,
")" => 12,
:UNIT => 13,
:DURATION => 14 }
racc_use_result_var = true
racc_nt_base = 15
Racc_arg = [
racc_action_table,
racc_action_check,
racc_action_default,
racc_action_pointer,
racc_goto_table,
racc_goto_check,
racc_goto_default,
racc_goto_pointer,
racc_nt_base,
racc_reduce_table,
racc_token_table,
racc_shift_n,
racc_reduce_n,
racc_use_result_var ]
Racc_token_to_s_table = [
'$end',
'error',
'UNARY',
'"^"',
'"*"',
'"/"',
'"+"',
'","',
'AND',
'"-"',
'DIGITS',
'"("',
'")"',
'UNIT',
'DURATION',
'$start',
'stmt',
'expr',
'time',
'iso',
'unit']
Racc_debug_parser = false
##### racc system variables end #####
# reduce 0 omitted
# reduce 1 omitted
# reduce 2 omitted
# reduce 3 omitted
module_eval <<'.,.,', 'parser.ry', 18
def _reduce_4( val, _values, result )
result += val[2]
result
end
.,.,
module_eval <<'.,.,', 'parser.ry', 19
def _reduce_5( val, _values, result )
result += val[2]
result
end
.,.,
module_eval <<'.,.,', 'parser.ry', 20
def _reduce_6( val, _values, result )
result += val[2]
result
end
.,.,
module_eval <<'.,.,', 'parser.ry', 21
def _reduce_7( val, _values, result )
result -= val[2]
result
end
.,.,
module_eval <<'.,.,', 'parser.ry', 22
def _reduce_8( val, _values, result )
result *= val[2]
result
end
.,.,
module_eval <<'.,.,', 'parser.ry', 23
def _reduce_9( val, _values, result )
result /= val[2]
result
end
.,.,
module_eval <<'.,.,', 'parser.ry', 24
def _reduce_10( val, _values, result )
result **= val[2]
result
end
.,.,
module_eval <<'.,.,', 'parser.ry', 25
def _reduce_11( val, _values, result )
result = -val[1]
result
end
.,.,
module_eval <<'.,.,', 'parser.ry', 26
def _reduce_12( val, _values, result )
result = +val[1]
result
end
.,.,
module_eval <<'.,.,', 'parser.ry', 27
def _reduce_13( val, _values, result )
result = val[1]
result
end
.,.,
module_eval <<'.,.,', 'parser.ry', 30
def _reduce_14( val, _values, result )
result = val[0] * val[1]
result
end
.,.,
module_eval <<'.,.,', 'parser.ry', 33
def _reduce_15( val, _values, result )
result = 1
result
end
.,.,
# reduce 16 omitted
# reduce 17 omitted
def _reduce_none( val, _values, result )
result
end
end # class Parser
end # class Delta
end # class Date

84
lib/date/delta/parser.ry Normal file
View file

@ -0,0 +1,84 @@
# parser.ry: Written by Tadayoshi Funaba 2006,2008,2009 -*- ruby -*-
class Date::Delta::Parser
prechigh
nonassoc UNARY
left '^'
left '*' '/'
left '+' ',' AND '-'
preclow
rule
stmt : expr
;
expr : time
| iso
| expr '+' expr {result += val[2]}
| expr ',' expr {result += val[2]}
| expr AND expr {result += val[2]}
| expr '-' expr {result -= val[2]}
| expr '*' DIGITS {result *= val[2]}
| expr '/' DIGITS {result /= val[2]}
| expr '^' DIGITS {result **= val[2]}
| '-' expr =UNARY {result = -val[1]}
| '+' expr =UNARY {result = +val[1]}
| '(' expr ')' {result = val[1]}
;
time : DIGITS unit {result = val[0] * val[1]}
;
unit : {result = 1} | UNIT
;
iso : DURATION
;
---- header ----
---- inner ----
def lookup(str)
t = str.downcase
k = UNITS4KEY[t]
return [:UNIT, k] if k
return [:AND, nil] if t == 'and'
return [:UNKNOWNWORD, nil]
end
def parse(str)
@q = []
until str.empty?
case str
when /\A\s+/
when /\AP(\d+y)?(\d+m)?(\d+d)?t?(\d+h)?(\d+m)?(\d+s)?(\d+w)?/i
y, m, d, h, min, s, w =
[$1, $2, $3, $4, $5, $6, $7].collect{|x| x.to_i}
y *= UNITS4KEY['years']
m *= UNITS4KEY['months']
d *= UNITS4KEY['days']
h *= UNITS4KEY['hours']
min *= UNITS4KEY['minutes']
s *= UNITS4KEY['seconds']
w *= UNITS4KEY['weeks']
@q.push [:DURATION, y + m + d + h + min + s + w]
when /\A\d+/
@q.push [:DIGITS, $&.to_i]
when /\A[a-z]+/i
@q.push lookup($&)
when /\A.|\n/
@q.push [$&, $&]
end
str = $'
end
@q.push [false, false]
do_parse
end
def next_token
@q.shift
end
---- footer ----

View file

@ -1354,6 +1354,141 @@ nurat_to_r(VALUE self)
return self;
}
#define id_ceil rb_intern("ceil")
#define f_ceil(x) rb_funcall(x, id_ceil, 0)
#define id_quo rb_intern("quo")
#define f_quo(x,y) rb_funcall(x, id_quo, 1, y)
#define f_reciprocal(x) f_quo(ONE, x)
/*
The algorithm here is the method described in CLISP. Bruno Haible has
graciously given permission to use this algorithm. He says, "You can use
it, if you present the following explanation of the algorithm."
Algorithm (recursively presented):
If x is a rational number, return x.
If x = 0.0, return 0.
If x < 0.0, return (- (rationalize (- x))).
If x > 0.0:
Call (integer-decode-float x). It returns a m,e,s=1 (mantissa,
exponent, sign).
If m = 0 or e >= 0: return x = m*2^e.
Search a rational number between a = (m-1/2)*2^e and b = (m+1/2)*2^e
with smallest possible numerator and denominator.
Note 1: If m is a power of 2, we ought to take a = (m-1/4)*2^e.
But in this case the result will be x itself anyway, regardless of
the choice of a. Therefore we can simply ignore this case.
Note 2: At first, we need to consider the closed interval [a,b].
but since a and b have the denominator 2^(|e|+1) whereas x itself
has a denominator <= 2^|e|, we can restrict the search to the open
interval (a,b).
So, for given a and b (0 < a < b) we are searching a rational number
y with a <= y <= b.
Recursive algorithm fraction_between(a,b):
c := (ceiling a)
if c < b
then return c ; because a <= c < b, c integer
else
; a is not integer (otherwise we would have had c = a < b)
k := c-1 ; k = floor(a), k < a < b <= k+1
return y = k + 1/fraction_between(1/(b-k), 1/(a-k))
; note 1 <= 1/(b-k) < 1/(a-k)
You can see that we are actually computing a continued fraction expansion.
Algorithm (iterative):
If x is rational, return x.
Call (integer-decode-float x). It returns a m,e,s (mantissa,
exponent, sign).
If m = 0 or e >= 0, return m*2^e*s. (This includes the case x = 0.0.)
Create rational numbers a := (2*m-1)*2^(e-1) and b := (2*m+1)*2^(e-1)
(positive and already in lowest terms because the denominator is a
power of two and the numerator is odd).
Start a continued fraction expansion
p[-1] := 0, p[0] := 1, q[-1] := 1, q[0] := 0, i := 0.
Loop
c := (ceiling a)
if c >= b
then k := c-1, partial_quotient(k), (a,b) := (1/(b-k),1/(a-k)),
goto Loop
finally partial_quotient(c).
Here partial_quotient(c) denotes the iteration
i := i+1, p[i] := c*p[i-1]+p[i-2], q[i] := c*q[i-1]+q[i-2].
At the end, return s * (p[i]/q[i]).
This rational number is already in lowest terms because
p[i]*q[i-1]-p[i-1]*q[i] = (-1)^i.
*/
static void
nurat_rationalize_internal(VALUE a, VALUE b, VALUE *p, VALUE *q)
{
VALUE c, k, t, p0, p1, p2, q0, q1, q2;
p0 = ZERO;
p1 = ONE;
q0 = ONE;
q1 = ZERO;
while (1) {
c = f_ceil(a);
if (f_lt_p(c, b))
break;
k = f_sub(c, ONE);
p2 = f_add(f_mul(k, p1), p0);
q2 = f_add(f_mul(k, q1), q0);
t = f_reciprocal(f_sub(b, k));
b = f_reciprocal(f_sub(a, k));
a = t;
p0 = p1;
q0 = q1;
p1 = p2;
q1 = q2;
}
*p = f_add(f_mul(c, p1), p0);
*q = f_add(f_mul(c, q1), q0);
}
/*
* call-seq:
* rat.rationalize -> self
* rat.rationalize(eps) -> rational
*
* Returns a simpler approximation of the value if an optional
* argument eps is given (rat-|eps| <= result <= rat+|eps|), self
* otherwise.
*
* For example:
*
* r = Rational(5033165, 16777216)
* r.rationalize #=> (5033165/16777216)
* r.rationalize(Rational('0.01')) #=> (3/10)
* r.rationalize(Rational('0.1')) #=> (1/3)
*/
static VALUE
nurat_rationalize(int argc, VALUE *argv, VALUE self)
{
VALUE e, a, b, p, q;
if (argc == 0)
return self;
if (f_negative_p(self))
return f_negate(nurat_rationalize(argc, argv, f_abs(self)));
rb_scan_args(argc, argv, "01", &e);
e = f_abs(e);
a = f_sub(self, e);
b = f_add(self, e);
if (f_eqeq_p(a, b))
return self;
nurat_rationalize_internal(a, b, &p, &q);
return f_rational_new2(CLASS_OF(self), p, q);
}
/* :nodoc: */
static VALUE
nurat_hash(VALUE self)
@ -1651,6 +1786,20 @@ nilclass_to_r(VALUE self)
return rb_rational_new1(INT2FIX(0));
}
/*
* call-seq:
* nil.rationalize([eps]) -> (0/1)
*
* Returns zero as a rational. An optional argument eps is always
* ignored.
*/
static VALUE
nilclass_rationalize(int argc, VALUE *argv, VALUE self)
{
rb_scan_args(argc, argv, "01", NULL);
return nilclass_to_r(self);
}
/*
* call-seq:
* int.to_r -> rational
@ -1668,6 +1817,20 @@ integer_to_r(VALUE self)
return rb_rational_new1(self);
}
/*
* call-seq:
* int.rationalize([eps]) -> rational
*
* Returns the value as a rational. An optional argument eps is
* always ignored.
*/
static VALUE
integer_rationalize(int argc, VALUE *argv, VALUE self)
{
rb_scan_args(argc, argv, "01", NULL);
return integer_to_r(self);
}
static void
float_decode_internal(VALUE self, VALUE *rf, VALUE *rn)
{
@ -1733,6 +1896,64 @@ float_to_r(VALUE self)
#endif
}
/*
* call-seq:
* flt.rationalize([eps]) -> rational
*
* Returns a simpler approximation of the value (flt-|eps| <= result
* <= flt+|eps|). if eps is not given, it will be chosen
* automatically.
*
* For example:
*
* 0.3.rationalize #=> (3/10)
* 1.333.rationalize #=> (1333/1000)
* 1.333.rationalize(0.01) #=> (4/3)
*/
static VALUE
float_rationalize(int argc, VALUE *argv, VALUE self)
{
VALUE e, a, b, p, q;
if (f_negative_p(self))
return f_negate(float_rationalize(argc, argv, f_abs(self)));
rb_scan_args(argc, argv, "01", &e);
if (argc != 0) {
e = f_abs(e);
a = f_sub(self, e);
b = f_add(self, e);
}
else {
VALUE f, n;
float_decode_internal(self, &f, &n);
if (f_zero_p(f) || f_positive_p(n))
return rb_rational_new1(f_lshift(f, n));
#if FLT_RADIX == 2
a = rb_rational_new2(f_sub(f_mul(TWO, f), ONE),
f_lshift(ONE, f_sub(ONE, n)));
b = rb_rational_new2(f_add(f_mul(TWO, f), ONE),
f_lshift(ONE, f_sub(ONE, n)));
#else
a = rb_rational_new2(f_sub(f_mul(INT2FIX(FLT_RADIX), f),
INT2FIX(FLT_RADIX - 1)),
f_expt(INT2FIX(FLT_RADIX), f_sub(ONE, n)));
b = rb_rational_new2(f_add(f_mul(INT2FIX(FLT_RADIX), f),
INT2FIX(FLT_RADIX - 1)),
f_expt(INT2FIX(FLT_RADIX), f_sub(ONE, n)));
#endif
}
if (f_eqeq_p(a, b))
return f_to_r(self);
nurat_rationalize_internal(a, b, &p, &q);
return rb_rational_new2(p, q);
}
static VALUE rat_pat, an_e_pat, a_dot_pat, underscores_pat, an_underscore;
#define WS "\\s*"
@ -2101,6 +2322,7 @@ Init_Rational(void)
rb_define_method(rb_cRational, "to_i", nurat_truncate, 0);
rb_define_method(rb_cRational, "to_f", nurat_to_f, 0);
rb_define_method(rb_cRational, "to_r", nurat_to_r, 0);
rb_define_method(rb_cRational, "rationalize", nurat_rationalize, -1);
rb_define_method(rb_cRational, "hash", nurat_hash, 0);
@ -2126,8 +2348,11 @@ Init_Rational(void)
rb_define_method(rb_cFloat, "denominator", float_denominator, 0);
rb_define_method(rb_cNilClass, "to_r", nilclass_to_r, 0);
rb_define_method(rb_cNilClass, "rationalize", nilclass_rationalize, -1);
rb_define_method(rb_cInteger, "to_r", integer_to_r, 0);
rb_define_method(rb_cInteger, "rationalize", integer_rationalize, -1);
rb_define_method(rb_cFloat, "to_r", float_to_r, 0);
rb_define_method(rb_cFloat, "rationalize", float_rationalize, -1);
make_patterns();

View file

@ -965,6 +965,61 @@ class Rational_Test < Test::Unit::TestCase
end
end
def test_rationalize
c = nil.rationalize
assert_equal([0,1], [c.numerator, c.denominator])
c = 0.rationalize
assert_equal([0,1], [c.numerator, c.denominator])
c = 1.rationalize
assert_equal([1,1], [c.numerator, c.denominator])
c = 1.1.rationalize
assert_equal([11, 10], [c.numerator, c.denominator])
c = Rational(1,2).rationalize
assert_equal([1,2], [c.numerator, c.denominator])
assert_equal(nil.rationalize(Rational(1,10)), Rational(0))
assert_equal(0.rationalize(Rational(1,10)), Rational(0))
assert_equal(10.rationalize(Rational(1,10)), Rational(10))
r = 0.3333
assert_equal(r.rationalize, Rational(3333, 10000))
assert_equal(r.rationalize(Rational(1,10)), Rational(1,3))
assert_equal(r.rationalize(Rational(-1,10)), Rational(1,3))
r = Rational(5404319552844595,18014398509481984)
assert_equal(r.rationalize, r)
assert_equal(r.rationalize(Rational(1,10)), Rational(1,3))
assert_equal(r.rationalize(Rational(-1,10)), Rational(1,3))
r = -0.3333
assert_equal(r.rationalize, Rational(-3333, 10000))
assert_equal(r.rationalize(Rational(1,10)), Rational(-1,3))
assert_equal(r.rationalize(Rational(-1,10)), Rational(-1,3))
r = Rational(-5404319552844595,18014398509481984)
assert_equal(r.rationalize, r)
assert_equal(r.rationalize(Rational(1,10)), Rational(-1,3))
assert_equal(r.rationalize(Rational(-1,10)), Rational(-1,3))
if @complex
if @keiju
else
assert_raise(RangeError){Complex(1,2).rationalize}
end
end
if (0.0/0).nan?
assert_raise(FloatDomainError){(0.0/0).rationalize}
end
if (1.0/0).infinite?
assert_raise(FloatDomainError){(1.0/0).rationalize}
end
end
def test_gcdlcm
assert_equal(7, 91.gcd(-49))
assert_equal(5, 5.gcd(0))