1require 'test/unit'
2require_relative 'allpairs'
3
4class TestSprintfComb < Test::Unit::TestCase
5  VS = [
6    #-0x1000000000000000000000000000000000000000000000002,
7    #-0x1000000000000000000000000000000000000000000000001,
8    #-0x1000000000000000000000000000000000000000000000000,
9    #-0xffffffffffffffffffffffffffffffffffffffffffffffff,
10    #-0x1000000000000000000000002,
11    #-0x1000000000000000000000001,
12    #-0x1000000000000000000000000,
13    #-0xffffffffffffffffffffffff,
14    -0x10000000000000002,
15    -0x10000000000000001,
16    -0x10000000000000000,
17    -0xffffffffffffffff,
18    -0x4000000000000002,
19    -0x4000000000000001,
20    -0x4000000000000000,
21    -0x3fffffffffffffff,
22    -0x100000002,
23    -0x100000001,
24    -0x100000000,
25    -0xffffffff,
26    #-0xc717a08d, # 0xc717a08d * 0x524b2245 = 0x4000000000000001
27    -0x80000002,
28    -0x80000001,
29    -0x80000000,
30    -0x7fffffff,
31    #-0x524b2245,
32    -0x40000002,
33    -0x40000001,
34    -0x40000000,
35    -0x3fffffff,
36    #-0x10002,
37    #-0x10001,
38    #-0x10000,
39    #-0xffff,
40    #-0x8101, # 0x8101 * 0x7f01 = 0x40000001
41    #-0x8002,
42    #-0x8001,
43    #-0x8000,
44    #-0x7fff,
45    #-0x7f01,
46    #-65,
47    #-64,
48    #-63,
49    #-62,
50    #-33,
51    #-32,
52    #-31,
53    #-30,
54    -3,
55    -2,
56    -1,
57    0,
58    1,
59    2,
60    3,
61    #30,
62    #31,
63    #32,
64    #33,
65    #62,
66    #63,
67    #64,
68    #65,
69    #0x7f01,
70    #0x7ffe,
71    #0x7fff,
72    #0x8000,
73    #0x8001,
74    #0x8101,
75    #0xfffe,
76    #0xffff,
77    #0x10000,
78    #0x10001,
79    0x3ffffffe,
80    0x3fffffff,
81    0x40000000,
82    0x40000001,
83    #0x524b2245,
84    0x7ffffffe,
85    0x7fffffff,
86    0x80000000,
87    0x80000001,
88    #0xc717a08d,
89    0xfffffffe,
90    0xffffffff,
91    0x100000000,
92    0x100000001,
93    0x3ffffffffffffffe,
94    0x3fffffffffffffff,
95    0x4000000000000000,
96    0x4000000000000001,
97    0xfffffffffffffffe,
98    0xffffffffffffffff,
99    0x10000000000000000,
100    0x10000000000000001,
101    #0xffffffffffffffffffffffff,
102    #0x1000000000000000000000000,
103    #0x1000000000000000000000001,
104    #0xffffffffffffffffffffffffffffffffffffffffffffffff,
105    #0x1000000000000000000000000000000000000000000000000,
106    #0x1000000000000000000000000000000000000000000000001
107  ]
108  VS.reverse!
109
110  FLAGS = [['', ' '], ['', '#'], ['', '+'], ['', '-'], ['', '0']]
111
112  def self.combination(*args, &b)
113    #AllPairs.exhaustive_each(*args, &b)
114    AllPairs.each(*args, &b)
115  end
116
117  def emu_int(format, v)
118    /\A%( )?(\#)?(\+)?(-)?(0)?(\d+)?(?:\.(\d*))?(.)\z/ =~ format
119    sp = $1
120    hs = $2
121    pl = $3
122    mi = $4
123    zr = $5
124    width = $6
125    precision = $7
126    type = $8
127    width = width.to_i if width
128    precision = precision.to_i if precision
129    prefix = ''
130
131    zr = nil if precision
132
133    zr = nil if mi && zr
134
135    case type
136    when 'B'
137      radix = 2
138      digitmap = {0 => '0', 1 => '1'}
139      complement = !pl && !sp
140      prefix = '0B' if hs && v != 0
141    when 'b'
142      radix = 2
143      digitmap = {0 => '0', 1 => '1'}
144      complement = !pl && !sp
145      prefix = '0b' if hs && v != 0
146    when 'd'
147      radix = 10
148      digitmap = {}
149      10.times {|i| digitmap[i] = i.to_s }
150      complement = false
151    when 'o'
152      radix = 8
153      digitmap = {}
154      8.times {|i| digitmap[i] = i.to_s }
155      complement = !pl && !sp
156    when 'X'
157      radix = 16
158      digitmap = {}
159      16.times {|i| digitmap[i] = i.to_s(16).upcase }
160      complement = !pl && !sp
161      prefix = '0X' if hs && v != 0
162    when 'x'
163      radix = 16
164      digitmap = {}
165      16.times {|i| digitmap[i] = i.to_s(16) }
166      complement = !pl && !sp
167      prefix = '0x' if hs && v != 0
168    else
169      raise "unexpected type: #{type.inspect}"
170    end
171
172    digits = []
173    abs = v.abs
174    sign = ''
175    while 0 < abs
176      digits << (abs % radix)
177      abs /= radix
178    end
179
180    if v < 0
181      if complement
182        digits.map! {|d| radix-1 - d }
183        carry = 1
184        digits.each_index {|i|
185          digits[i] += carry
186          carry = 0
187          if radix <= digits[i]
188            digits[i] -= radix
189            carry = 1
190          end
191        }
192        if digits.last != radix-1
193          digits << (radix-1)
194        end
195        sign = '..'
196      else
197        sign = '-'
198      end
199    else
200      if pl
201        sign = '+'
202      elsif sp
203        sign = ' '
204      end
205    end
206
207    dlen = digits.length
208    dlen += 2 if sign == '..'
209
210    if v < 0 && complement
211      d = radix - 1
212    else
213      d = 0
214    end
215    if precision
216      if dlen < precision
217        (precision - dlen).times {
218          digits << d
219        }
220      end
221    else
222      if dlen == 0
223        digits << d
224      end
225    end
226    if type == 'o' && hs
227      if digits.empty? || digits.last != d
228        digits << d
229      end
230    end
231
232    digits.reverse!
233
234    str = digits.map {|digit| digitmap[digit] }.join
235
236    pad = ''
237    nlen = prefix.length + sign.length + str.length
238    if width && nlen < width
239      len = width - nlen
240      if zr
241        if complement && v < 0
242          pad = digitmap[radix-1] * len
243        else
244          pad = '0' * len
245        end
246      else
247        pad = ' ' * len
248      end
249    end
250
251    if / / =~ pad
252      if sign == '..'
253        str = prefix + sign + str
254      else
255        str = sign + prefix + str
256      end
257      if mi
258        str = str + pad
259      else
260        str = pad + str
261      end
262    else
263      if sign == '..'
264        str = prefix + sign + pad + str
265      else
266        str = sign + prefix + pad + str
267      end
268    end
269
270    str
271  end
272
273  def self.assertions_format_integer(format)
274    proc {
275      VS.each {|v|
276        r = sprintf format, v
277        e = emu_int format, v
278        if true
279          assert_equal(e, r, "sprintf(#{format.dump}, #{v})")
280        else
281          if e != r
282            puts "#{e.dump}\t#{r.dump}\tsprintf(#{format.dump}, #{v})"
283          end
284        end
285      }
286    }
287  end
288
289  combination(%w[B b d o X x],
290              [nil, 0, 5, 20],
291              ["", ".", ".0", ".8", ".20"],
292              *FLAGS) {|type, width, precision, sp, hs, pl, mi, zr|
293    format = "%#{sp}#{hs}#{pl}#{mi}#{zr}#{width}#{precision}#{type}"
294    define_method("test_format_integer(#{format})", assertions_format_integer(format))
295  }
296
297  FLOAT_VALUES = [
298    -1e100,
299    -123456789.0,
300    -1.0,
301    -0.0,
302    0.0,
303    0.01,
304    1/3.0,
305    2/3.0,
306    1.0,
307    2.0,
308    9.99999999,
309    123456789.0,
310    1e100,
311    Float::MAX,
312    Float::MIN,
313    Float::EPSILON,
314    1+Float::EPSILON,
315    #1-Float::EPSILON/2,
316    10 + Float::EPSILON*10,
317    10 - Float::EPSILON*5,
318    1.0/0.0,
319    -1.0/0.0,
320    0.0/0.0,
321  ]
322
323  def split_float10(v)
324    if v == 0
325      if 1/v < 0
326        sign = -1
327        v = -v
328      else
329        sign = 1
330      end
331    else
332      if v < 0
333        sign = -1
334        v = -v
335      else
336        sign = 1
337      end
338    end
339    exp = 0
340    int = v.floor
341    v -= int
342    while v != 0
343      v *= 2
344      int *= 2
345      i = v.floor
346      v -= i
347      int += i
348      exp -= 1
349    end
350    int *= 5 ** (-exp)
351    [sign, int, exp]
352  end
353
354  def emu_e(sp, hs, pl, mi, zr, width, precision, type, v, sign, int, exp)
355    precision = 6 unless precision
356    if int == 0
357      if precision == 0 && !hs
358        result = "0#{type}+00"
359      else
360        result = "0." + "0" * precision + "#{type}+00"
361      end
362    else
363      if int < 10**precision
364        int *= 10**precision
365        exp -= precision
366      end
367      digits = int.to_s.length
368      discard = digits - (precision+1)
369      if discard != 0
370        q, r = int.divmod(10**discard)
371        if r < 10**discard / 2
372          int = q
373          exp += discard
374        elsif (q+1).to_s.length == q.to_s.length
375          int = q+1
376          exp += discard
377        else
378          discard += 1
379          q, r = int.divmod(10**discard)
380          int = q+1
381          exp += discard
382        end
383      end
384      ints = int.to_s
385      frac = ints[1..-1]
386      result = ints[0,1]
387      e = exp + frac.length
388      if precision != 0 || hs
389        result << "."
390        if precision != 0
391          result << frac
392        end
393      end
394      result << type
395      if e == 0
396        if v.abs < 1
397          result << '-00' # glibc 2.7 causes '+00'
398        else
399          result << '+00'
400        end
401      else
402        result << sprintf("%+03d", e)
403      end
404      result
405    end
406    result
407  end
408
409  def emu_f(sp, hs, pl, mi, zr, width, precision, type, sign, int, exp)
410    precision = 6 unless precision
411    if int == 0
412      if precision == 0 && !hs
413        result = '0'
414      else
415        result = '0.' + '0' * precision
416      end
417    else
418      if -precision < exp
419        int *= 10 ** (precision+exp)
420        exp = -precision
421      end
422      if exp < -precision
423        discard = -exp - precision
424        q, r = int.divmod(10**discard)
425        if 10**discard / 2 <= r
426          q += 1
427        end
428        int = q
429        exp += discard
430      end
431      result = int.to_s
432      if result.length <= precision
433        result = '0' * (precision+1 - result.length) + result
434      end
435      if precision != 0 || hs
436        if precision == 0
437          result << '.'
438        else
439          result[-precision,0] = '.'
440        end
441      end
442    end
443    result
444  end
445
446  def emu_float(format, v)
447    /\A%( )?(\#)?(\+)?(-)?(0)?(\d+)?(?:\.(\d*))?(.)\z/ =~ format
448    sp = $1
449    hs = $2
450    pl = $3
451    mi = $4
452    zr = $5
453    width = $6
454    precision = $7
455    type = $8
456    width = width.to_i if width
457    precision = precision.to_i if precision
458
459    zr = nil if mi && zr
460
461    if v.infinite?
462      sign = v < 0 ? -1 : 1
463      int = :inf
464      hs = zr = nil
465    elsif v.nan?
466      sign = 1
467      int = :nan
468      hs = zr = nil
469    else
470      sign, int, exp = split_float10(v)
471    end
472
473    if sign < 0
474      sign = '-'
475    elsif sign == 0
476      sign = ''
477    elsif pl
478      sign = '+'
479    elsif sp
480      sign = ' '
481    else
482      sign = ''
483    end
484
485    if v.nan?
486      result = 'NaN'
487    elsif v.infinite?
488      result = 'Inf'
489    else
490      case type
491      when /[eE]/
492        result = emu_e(sp, hs, pl, mi, zr, width, precision, type, v, sign, int, exp)
493      when /f/
494        result = emu_f(sp, hs, pl, mi, zr, width, precision, type, sign, int, exp)
495      when /[gG]/
496        precision = 6 unless precision
497        precision = 1 if precision == 0
498        r = emu_e(sp, hs, pl, mi, zr, width, precision-1, type.tr('gG', 'eE'), v, sign, int, exp)
499        /[eE]([+-]\d+)/ =~ r
500        e = $1.to_i
501        if e < -4 || precision <= e
502          result = r
503        else
504          result = emu_f(sp, hs, pl, mi, zr, width, precision-1-e, type, sign, int, exp)
505        end
506        result.sub!(/\.[0-9]*/) { $&.sub(/\.?0*\z/, '') } if !hs
507      else
508        raise "unexpected type: #{type}"
509      end
510    end
511
512    pad = ''
513    if width && sign.length + result.length < width
514      if zr
515        pad = '0' * (width - sign.length - result.length)
516      else
517        pad = ' ' * (width - sign.length - result.length)
518      end
519    end
520    if mi
521      sign + result + pad
522    elsif zr
523      sign + pad + result
524    else
525      pad + sign + result
526    end
527
528  end
529
530  def self.assertions_format_float(format)
531    proc {
532      FLOAT_VALUES.each {|v|
533        r = sprintf format, v
534        e = emu_float format, v
535        if true
536          assert_equal(e, r, "sprintf(#{format.dump}, #{'%.20g' % v})")
537        else
538          if e != r
539            puts "#{e.dump}\t#{r.dump}\tsprintf(#{format.dump}, #{'%.20g' % v})"
540          end
541        end
542      }
543    }
544  end
545
546  combination(%w[e E f g G],
547              [nil, 0, 5, 20],
548              ["", ".", ".0", ".8", ".20", ".200", ".9999"],
549              *FLAGS) {|type, width, precision, sp, hs, pl, mi, zr|
550    format = "%#{sp}#{hs}#{pl}#{mi}#{zr}#{width}#{precision}#{type}"
551    define_method("test_format_float(#{format})", assertions_format_float(format))
552  }
553end
554