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