Class: Float

Inherits:
Object
  • Object
show all
Defined in:
lib/redparse/float_accurate_to_s.rb

Constant Summary collapse

SIZE =
[1.1].pack("d").size
BITSIZE =
SIZE*8
BASE10_DIGITS =
(2**BITSIZE-1).to_s.size

Instance Method Summary collapse

Instance Method Details

#accurate_to_sObject



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/redparse/float_accurate_to_s.rb', line 5

def accurate_to_s
  return "#{'-' if self<0}Infinity" if infinite?
  return "NaN" if nan?
  return "0.0e0" if zero?

  as_str=sprintf("%.#{BASE10_DIGITS+2}e",self)

  #decompose self into sign, mantissa, and exponent (in string form)
  all,sign,first,digits,exp=*as_str.match(/^([+-]?)(\d)\.(\d+)e(.*)$/)
  digits=first<<digits
  exp=exp.to_i+1
  lead=sign<<"0."
  return digits=digits if as_str.to_f.zero? #hopeless

  #recompose back to a float
  result=[lead,digits,"e",exp].join
  result_f=result.to_f
  delta=result_f - self
  return digits=digits if delta.zero? #if representation is exact, return here

  #figure out which direction to go to get toward the right answer
  if delta<0
    incr=1
  else #delta>0
    incr=-1
  end

  #keep adding increasing increments to mantissa
  #until we get to a value on the other side of the correct answer
  while true
    while true
      try_digits=digits.to_i.+(incr).to_s
      if try_digits.size>digits.size
        exp+=1
        digits="0"+digits
      end
      fail if try_digits[0]==?- #can't happen... I think?
      trying=[lead,try_digits,"e",exp].join
      trying_f=trying.to_f
      break unless trying_f.zero?
      digits[-1,1]='' #workaround 1.8 bug
    end
    return digits=try_digits if trying_f==self
    break if self.between?(*[trying_f,result_f].sort) #(trying_f-self)*delta<0
    incr*=2
  end

  #we now have lower and upper bounds on the correct answer
  lower,upper=*[digits.to_i, digits.to_i.+(incr)].sort

  #maybe one of the bounds is already the correct answer?
  result=[lead,lower,"e",exp].join
  return digits=lower if result.to_f==self
  result=[lead,upper,"e",exp].join
  return digits=upper if result.to_f==self

  #binary search between lower and upper bounds til we find a correct answer
  digits=nil
  while true
    return as_str if upper-lower <= 1 #hopeless
    mid=(lower+upper)/2
    mid_s=[lead,mid,"e",exp].join
    mid_f=mid_s.to_f
    return digits=mid if mid_f==self
    if mid_f<self
      lower=mid
    else #mid_f>self
      upper=mid
    end
  end
ensure

  #try to drop unneeded trailing digits
  if digits
    digits=digits.to_s
    begin
      last=digits.slice!( -1 )
      result=[lead,digits,"e",exp].join.to_f
    end while result==self or result.zero? && digits.size.nonzero?
    roundup=(digits.to_i+1).to_s
    if roundup.size>digits.size
      exp+=1
      digits="0"+digits
    end
    roundup.slice!( /0+\Z/ )
    roundup=[lead,roundup,"e",exp].join
    return roundup if roundup.to_f==self
    return [lead,digits<<last,"e",exp].join
  end
end