about summary refs log tree commit diff stats
path: root/061text.mu
Commit message (Expand)AuthorAgeFilesLines
* 3688Kartik K. Agaram2016-11-251-0/+1
* 3683Kartik K. Agaram2016-11-231-0/+26
* 3674Kartik K. Agaram2016-11-131-1/+1
* 3672Kartik K. Agaram2016-11-131-3/+3
* 3670 - parsing whole numbers from textKartik K. Agaram2016-11-111-0/+83
* 3552Kartik K. Agaram2016-10-221-1/+1
* 3429 - standardize Mu scenariosKartik K. Agaram2016-09-281-158/+182
* 3390Kartik K. Agaram2016-09-171-41/+41
* 3389Kartik K. Agaram2016-09-171-17/+17
* 3386Kartik K. Agaram2016-09-171-56/+56
* 3385Kartik K. Agaram2016-09-171-72/+72
* 3379Kartik K. Agaram2016-09-171-67/+67
* 3368Kartik K. Agaram2016-09-151-13/+0
* 3365 - create strings out of arbitrary typesKartik K. Agaram2016-09-151-1/+28
* 3357 - multi-ary 'append'Kartik K. Agaram2016-09-151-33/+36
* 3356Kartik K. Agaram2016-09-151-10/+10
* 3344Kartik K. Agaram2016-09-121-1/+0
* 3343Kartik K. Agaram2016-09-121-15/+0
* 3341Kartik K. Agaram2016-09-121-13/+13
* 3338Kartik K. Agaram2016-09-121-1/+1
* 3337 - first use of type abbreviations: textKartik K. Agaram2016-09-121-123/+123
* 3324 - completely redo type abbreviationsKartik K. Agaram2016-09-111-0/+1
* 3186 - handle 'new-buffer' without ingredientsKartik K. Agaram2016-08-141-0/+14
* 3155Kartik K. Agaram2016-07-271-2/+2
* 3055Kartik K. Agaram2016-06-131-0/+1269
ref='#n265'>265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
# print out floats in decimal
# https://research.swtch.com/ftoa
#
# Basic idea:
#   Ignoring sign, floating point numbers are represented as 1.mantissa * 2^exponent
#   Therefore, to print a float in decimal, we need to:
#     - compute its value without decimal point
#     - convert to an array of decimal digits
#     - print out the array while inserting the decimal point appropriately
#
# Basic complication: the computation generates numbers larger than an int can
# hold. We need a way to represent big ints.
#
# Key insight: use a representation for big ints that's close to what we need
# anyway, an array of decimal digits.
#
# Style note: we aren't creating a big int library here. The only operations
# we need are halving and doubling. Following the link above, it seems more
# comprehensible to keep these operations inlined so that we can track the
# position of the decimal point with dispatch.
#
# This approach turns out to be fast enough for most purposes.
# Optimizations, however, get wildly more complex.

fn test-write-float-decimal-approximate-normal {
  var s-storage: (stream byte 0x10)
  var s/ecx: (addr stream byte) <- address s-storage
  # 0.5
  var half/xmm0: float <- rational 1, 2
  write-float-decimal-approximate s, half, 3
  check-stream-equal s, "0.5", "F - test-write-float-decimal-approximate-normal 0.5"
  # 0.25
  clear-stream s
  var quarter/xmm0: float <- rational 1, 4
  write-float-decimal-approximate s, quarter, 3
  check-stream-equal s, "0.25", "F - test-write-float-decimal-approximate-normal 0.25"
  # 0.75
  clear-stream s
  var three-quarters/xmm0: float <- rational 3, 4
  write-float-decimal-approximate s, three-quarters, 3
  check-stream-equal s, "0.75", "F - test-write-float-decimal-approximate-normal 0.75"
  # 0.125
  clear-stream s
  var eighth/xmm0: float <- rational 1, 8
  write-float-decimal-approximate s, eighth, 3
  check-stream-equal s, "0.125", "F - test-write-float-decimal-approximate-normal 0.125"
  # 0.0625; start using scientific notation
  clear-stream s
  var sixteenth/xmm0: float <- rational 1, 0x10
  write-float-decimal-approximate s, sixteenth, 3
  check-stream-equal s, "6.25e-2", "F - test-write-float-decimal-approximate-normal 0.0625"
  # sqrt(2); truncate floats with lots of digits after the decimal but not too many before
  clear-stream s
  var two-f/xmm0: float <- rational 2, 1
  var sqrt-2/xmm0: float <- square-root two-f
  write-float-decimal-approximate s, sqrt-2, 3
  check-stream-equal s, "1.414", "F - test-write-float-decimal-approximate-normal √2"
}

# print whole integers without decimals
fn test-write-float-decimal-approximate-integer {
  var s-storage: (stream byte 0x10)
  var s/ecx: (addr stream byte) <- address s-storage
  # 1
  var one-f/xmm0: float <- rational 1, 1
  write-float-decimal-approximate s, one-f, 3
  check-stream-equal s, "1", "F - test-write-float-decimal-approximate-integer 1"
  # 2
  clear-stream s
  var two-f/xmm0: float <- rational 2, 1
  write-float-decimal-approximate s, two-f, 3
  check-stream-equal s, "2", "F - test-write-float-decimal-approximate-integer 2"
  # 10
  clear-stream s
  var ten-f/xmm0: float <- rational 0xa, 1
  write-float-decimal-approximate s, ten-f, 3
  check-stream-equal s, "10", "F - test-write-float-decimal-approximate-integer 10"
  # -10
  clear-stream s
  var minus-ten-f/xmm0: float <- rational -0xa, 1
  write-float-decimal-approximate s, minus-ten-f, 3
  check-stream-equal s, "-10", "F - test-write-float-decimal-approximate-integer -10"
  # 999
  clear-stream s
  var minus-ten-f/xmm0: float <- rational 0x3e7, 1
  write-float-decimal-approximate s, minus-ten-f, 3
  check-stream-equal s, "999", "F - test-write-float-decimal-approximate-integer 1000"
  # 1000 - start using scientific notation
  clear-stream s
  var minus-ten-f/xmm0: float <- rational 0x3e8, 1
  write-float-decimal-approximate s, minus-ten-f, 3
  check-stream-equal s, "1.00e3", "F - test-write-float-decimal-approximate-integer 1000"
  # 100,000
  clear-stream s
  var hundred-thousand/eax: int <- copy 0x186a0
  var hundred-thousand-f/xmm0: float <- convert hundred-thousand
  write-float-decimal-approximate s, hundred-thousand-f, 3
  check-stream-equal s, "1.00e5", "F - test-write-float-decimal-approximate-integer 100,000"
}

fn test-write-float-decimal-approximate-zero {
  var s-storage: (stream byte 0x10)
  var s/ecx: (addr stream byte) <- address s-storage
  var zero: float
  write-float-decimal-approximate s, zero, 3
  check-stream-equal s, "0", "F - test-write-float-decimal-approximate-zero"
}

fn test-write-float-decimal-approximate-negative-zero {
  var s-storage: (stream byte 0x10)
  var s/ecx: (addr stream byte) <- address s-storage
  var n: int
  copy-to n, 0x80000000
  var negative-zero/xmm0: float <- reinterpret n
  write-float-decimal-approximate s, negative-zero, 3
  check-stream-equal s, "-0", "F - test-write-float-decimal-approximate-negative-zero"
}

fn test-write-float-decimal-approximate-infinity {
  var s-storage: (stream byte 0x10)
  var s/ecx: (addr stream byte) <- address s-storage
  var n: int
  #          0|11111111|00000000000000000000000
  #          0111|1111|1000|0000|0000|0000|0000|0000
  copy-to n, 0x7f800000
  var infinity/xmm0: float <- reinterpret n
  write-float-decimal-approximate s, infinity, 3
  check-stream-equal s, "Inf", "F - test-write-float-decimal-approximate-infinity"
}

fn test-write-float-decimal-approximate-negative-infinity {
  var s-storage: (stream byte 0x10)
  var s/ecx: (addr stream byte) <- address s-storage
  var n: int
  copy-to n, 0xff800000
  var negative-infinity/xmm0: float <- reinterpret n
  write-float-decimal-approximate s, negative-infinity, 3
  check-stream-equal s, "-Inf", "F - test-write-float-decimal-approximate-negative-infinity"
}

fn test-write-float-decimal-approximate-not-a-number {
  var s-storage: (stream byte 0x10)
  var s/ecx: (addr stream byte) <- address s-storage
  var n: int
  copy-to n, 0xffffffff  # exponent must be all 1's, and mantissa must be non-zero
  var nan/xmm0: float <- reinterpret n
  write-float-decimal-approximate s, nan, 3
  check-stream-equal s, "NaN", "F - test-write-float-decimal-approximate-not-a-number"
}

fn render-float-decimal screen: (addr screen), in: float, precision: int, x: int, y: int, color: int, background-color: int -> _/eax: int {
  var s-storage: (stream byte 0x10)
  var s/esi: (addr stream byte) <- address s-storage
  write-float-decimal-approximate s, in, precision
  var width/eax: int <- copy 0
  var height/ecx: int <- copy 0
  width, height <- screen-size screen
  var result/eax: int <- draw-stream-rightward screen, s, x, width, y, color, background-color
  return result
}

# 'precision' controls the maximum width past which we resort to scientific notation
fn write-float-decimal-approximate out: (addr stream byte), in: float, precision: int {
  # - special names
  var bits/eax: int <- reinterpret in
  compare bits, 0
  {
    break-if-!=
    write out, "0"
    return
  }
  compare bits, 0x80000000
  {
    break-if-!=
    write out, "-0"
    return
  }
  compare bits, 0x7f800000
  {
    break-if-!=
    write out, "Inf"
    return
  }
  compare bits, 0xff800000
  {
    break-if-!=
    write out, "-Inf"
    return
  }
  var exponent/ecx: int <- copy bits
  exponent <- shift-right 0x17  # 23 bits of mantissa
  exponent <- and 0xff
  exponent <- subtract 0x7f
  compare exponent, 0x80
  {
    break-if-!=
    write out, "NaN"
    return
  }
  # - regular numbers
  var sign/edx: int <- copy bits
  sign <- shift-right 0x1f
  {
    compare sign, 1
    break-if-!=
    append-byte out, 0x2d/minus
  }

  # v = 1.mantissa (in base 2) << 0x17
  var v/ebx: int <- copy bits
  v <- and 0x7fffff
  v <- or 0x00800000  # insert implicit 1
  # e = exponent - 0x17
  var e/ecx: int <- copy exponent
  e <- subtract 0x17  # move decimal place from before mantissa to after

  # initialize buffer with decimal representation of v
  # unlike https://research.swtch.com/ftoa, no ascii here
  var buf-storage: (array byte 0x7f)
  var buf/edi: (addr array byte) <- address buf-storage
  var n/eax: int <- decimal-digits v, buf
  # I suspect we can do without reversing, but we'll follow https://research.swtch.com/ftoa
  # closely for now.
  reverse-digits buf, n

  # loop if e > 0
  {
    compare e, 0
    break-if-<=
    n <- double-array-of-decimal-digits buf, n
    e <- decrement
    loop
  }

  var dp/edx: int <- copy n

  # loop if e < 0
  {
    compare e, 0
    break-if->=
    n, dp <- halve-array-of-decimal-digits buf, n, dp
    e <- increment
    loop
  }

  _write-float-array-of-decimal-digits out, buf, n, dp, precision
}

# store the decimal digits of 'n' into 'buf', units first
# n must be positive
fn decimal-digits n: int, _buf: (addr array byte) -> _/eax: int {
  var buf/edi: (addr array byte) <- copy _buf
  var i/ecx: int <- copy 0
  var curr/eax: int <- copy n
  var curr-byte/edx: int <- copy 0
  {
    compare curr, 0
    break-if-=
    curr, curr-byte <- integer-divide curr, 0xa
    var dest/ebx: (addr byte) <- index buf, i
    copy-byte-to *dest, curr-byte
    i <- increment
    loop
  }
  return i
}

fn reverse-digits _buf: (addr array byte), n: int {
  var buf/esi: (addr array byte) <- copy _buf
  var left/ecx: int <- copy 0
  var right/edx: int <- copy n
  right <- decrement
  {
    compare left, right
    break-if->=
    {
      var l-a/ecx: (addr byte) <- index buf, left
      var r-a/edx: (addr byte) <- index buf, right
      var l/ebx: byte <- copy-byte *l-a
      var r/eax: byte <- copy-byte *r-a
      copy-byte-to *l-a, r
      copy-byte-to *r-a, l
    }
    left <- increment
    right <- decrement
    loop
  }
}

fn double-array-of-decimal-digits _buf: (addr array byte), _n: int -> _/eax: int {
  var buf/edi: (addr array byte) <- copy _buf
  # initialize delta
  var delta/edx: int <- copy 0
  {
    var curr/ebx: (addr byte) <- index buf, 0
    var tmp/eax: byte <- copy-byte *curr
    compare tmp, 5
    break-if-<
    delta <- copy 1
  }
  # loop
  var x/eax: int <- copy 0
  var i/ecx: int <- copy _n
  i <- decrement
  {
    compare i, 0
    break-if-<=
    # x += 2*buf[i]
    {
      var tmp/ecx: (addr byte) <- index buf, i
      var tmp2/ecx: byte <- copy-byte *tmp
      x <- add tmp2
      x <- add tmp2
    }
    # x, buf[i+delta] = x/10, x%10
    {
      var dest-index/ecx: int <- copy i
      dest-index <- add delta
      var dest/edi: (addr byte) <- index buf, dest-index
      var next-digit/edx: int <- copy 0
      x, next-digit <- integer-divide x, 0xa
      copy-byte-to *dest, next-digit
    }
    #
    i <- decrement
    loop
  }
  # final patch-up
  var n/eax: int <- copy _n
  compare delta, 1
  {
    break-if-!=
    var curr/ebx: (addr byte) <- index buf, 0
    var one/edx: int <- copy 1
    copy-byte-to *curr, one
    n <- increment
  }
  return n
}

fn halve-array-of-decimal-digits _buf: (addr array byte), _n: int, _dp: int -> _/eax: int, _/edx: int {
  var buf/edi: (addr array byte) <- copy _buf
  var n/eax: int <- copy _n
  var dp/edx: int <- copy _dp
  # initialize one side
  {
    # if buf[n-1]%2 == 0, break
    var right-index/ecx: int <- copy n
    right-index <- decrement
    var right-a/ecx: (addr byte) <- index buf, right-index
    var right/ecx: byte <- copy-byte *right-a
    var right-int/ecx: int <- copy right
    var remainder/edx: int <- copy 0
    {
      var dummy/eax: int <- copy 0
      dummy, remainder <- integer-divide right-int, 2
    }
    compare remainder, 0
    break-if-=
    # buf[n] = 0
    var next-a/ecx: (addr byte) <- index buf, n
    var zero/edx: byte <- copy 0
    copy-byte-to *next-a, zero
    # n++
    n <- increment
  }
  # initialize the other
  var delta/ebx: int <- copy 0
  var x/esi: int <- copy 0
  {
    # if buf[0] >= 2, break
    var left/ecx: (addr byte) <- index buf, 0
    var src/ecx: byte <- copy-byte *left
    compare src, 2
    break-if->=
    # delta, x = 1, buf[0]
    delta <- copy 1
    x <- copy src
    # n--
    n <- decrement
    # dp--
    dp <- decrement
  }
  # loop
  var i/ecx: int <- copy 0
  {
    compare i, n
    break-if->=
    # x = x*10 + buf[i+delta]
    {
      var ten/edx: int <- copy 0xa
      x <- multiply ten
      var src-index/edx: int <- copy i
      src-index <- add delta
      var src-a/edx: (addr byte) <- index buf, src-index
      var src/edx: byte <- copy-byte *src-a
      x <- add src
    }
    # buf[i], x = x/2, x%2
    {
      var quotient/eax: int <- copy 0
      var remainder/edx: int <- copy 0
      quotient, remainder <- integer-divide x, 2
      x <- copy remainder
      var dest/edx: (addr byte) <- index buf, i
      copy-byte-to *dest, quotient
    }
    #
    i <- increment
    loop
  }
  return n, dp
}

fn _write-float-array-of-decimal-digits out: (addr stream byte), _buf: (addr array byte), n: int, dp: int, precision: int {
  var buf/edi: (addr array byte) <- copy _buf
  {
    compare dp, 0
    break-if->=
    _write-float-array-of-decimal-digits-in-scientific-notation out, buf, n, dp, precision
    return
  }
  {
    var dp2/eax: int <- copy dp
    compare dp2, precision
    break-if-<=
    _write-float-array-of-decimal-digits-in-scientific-notation out, buf, n, dp, precision
    return
  }
  {
    compare dp, 0
    break-if-!=
    append-byte out, 0x30/0
  }
  var i/eax: int <- copy 0
  # bounds = min(n, dp+3)
  var limit/edx: int <- copy dp
  limit <- add 3
  {
    compare limit, n
    break-if-<=
    limit <- copy n
  }
  {
    compare i, limit
    break-if->=
    # print '.' if necessary
    compare i, dp
    {
      break-if-!=
      append-byte out, 0x2e/decimal-point
    }
    var curr-a/ecx: (addr byte) <- index buf, i
    var curr/ecx: byte <- copy-byte *curr-a
    var curr-int/ecx: int <- copy curr
    curr-int <- add 0x30/0
    append-byte out, curr-int
    #
    i <- increment
    loop
  }
}

fn _write-float-array-of-decimal-digits-in-scientific-notation out: (addr stream byte), _buf: (addr array byte), n: int, dp: int, precision: int {
  var buf/edi: (addr array byte) <- copy _buf
  var i/eax: int <- copy 0
  {
    compare i, n
    break-if->=
    compare i, precision
    break-if->=
    compare i, 1
    {
      break-if-!=
      append-byte out, 0x2e/decimal-point
    }
    var curr-a/ecx: (addr byte) <- index buf, i
    var curr/ecx: byte <- copy-byte *curr-a
    var curr-int/ecx: int <- copy curr
    curr-int <- add 0x30/0
    append-byte out, curr-int
    #
    i <- increment
    loop
  }
  append-byte out, 0x65/e
  decrement dp
  write-int32-decimal out, dp
}

# follows the structure of write-float-decimal-approximate
# 'precision' controls the maximum width past which we resort to scientific notation
fn float-size in: float, precision: int -> _/eax: int {
  # - special names
  var bits/eax: int <- reinterpret in
  compare bits, 0
  {
    break-if-!=
    return 1  # for "0"
  }
  compare bits, 0x80000000
  {
    break-if-!=
    return 2  # for "-0"
  }
  compare bits, 0x7f800000
  {
    break-if-!=
    return 3  # for "Inf"
  }
  compare bits, 0xff800000
  {
    break-if-!=
    return 4  # for "-Inf"
  }
  var exponent/ecx: int <- copy bits
  exponent <- shift-right 0x17  # 23 bits of mantissa
  exponent <- and 0xff
  exponent <- subtract 0x7f
  compare exponent, 0x80
  {
    break-if-!=
    return 3  # for "NaN"
  }
  # - regular numbers
  # v = 1.mantissa (in base 2) << 0x17
  var v/ebx: int <- copy bits
  v <- and 0x7fffff
  v <- or 0x00800000  # insert implicit 1
  # e = exponent - 0x17
  var e/ecx: int <- copy exponent
  e <- subtract 0x17  # move decimal place from before mantissa to after

  # initialize buffer with decimal representation of v
  var buf-storage: (array byte 0x7f)
  var buf/edi: (addr array byte) <- address buf-storage
  var n/eax: int <- decimal-digits v, buf
  reverse-digits buf, n

  # loop if e > 0
  {
    compare e, 0
    break-if-<=
    n <- double-array-of-decimal-digits buf, n
    e <- decrement
    loop
  }

  var dp/edx: int <- copy n

  # loop if e < 0
  {
    compare e, 0
    break-if->=
    n, dp <- halve-array-of-decimal-digits buf, n, dp
    e <- increment
    loop
  }

  compare dp, 0
  {
    break-if->=
    return 8  # hacky for scientific notation
  }
  {
    var dp2/eax: int <- copy dp
    compare dp2, precision
    break-if-<=
    return 8  # hacky for scientific notation
  }

  # result = min(n, dp+3)
  var result/ecx: int <- copy dp
  result <- add 3
  {
    compare result, n
    break-if-<=
    result <- copy n
  }

  # account for decimal point
  compare dp, n
  {
    break-if->=
    result <- increment
  }

  # account for sign
  var sign/edx: int <- reinterpret in
  sign <- shift-right 0x1f
  {
    compare sign, 1
    break-if-!=
    result <- increment
  }
  return result
}