about summary refs log tree commit diff stats
path: root/baremetal/412render-float-decimal.mu
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2021-02-11 00:44:13 -0800
committerKartik K. Agaram <vc@akkartik.com>2021-02-11 00:44:13 -0800
commite396b3a0a354c017c7582124d51a0380a6e5c7de (patch)
tree7babc5b7340385957cd7f86a826b2307c978ac94 /baremetal/412render-float-decimal.mu
parent79e6d41bea6d57afdb78e4be2fc3cda0a89f57b2 (diff)
downloadmu-e396b3a0a354c017c7582124d51a0380a6e5c7de.tar.gz
7719 - baremetal: print floats
Diffstat (limited to 'baremetal/412render-float-decimal.mu')
-rw-r--r--baremetal/412render-float-decimal.mu604
1 files changed, 604 insertions, 0 deletions
diff --git a/baremetal/412render-float-decimal.mu b/baremetal/412render-float-decimal.mu
new file mode 100644
index 00000000..498781e9
--- /dev/null
+++ b/baremetal/412render-float-decimal.mu
@@ -0,0 +1,604 @@
+# 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-render-float-decimal-normal {
+  var screen-on-stack: screen
+  var screen/esi: (addr screen) <- address screen-on-stack
+  initialize-screen screen, 0x20, 5  # 32 columns should be more than enough
+  # 0.5
+  var half/xmm0: float <- rational 1, 2
+  var dummy/eax: int <- render-float-decimal screen, half, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "0.5 ", "F - test-render-float-decimal-normal 0.5"
+  # 0.25
+  clear-screen screen
+  var quarter/xmm0: float <- rational 1, 4
+  var dummy/eax: int <- render-float-decimal screen, quarter, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "0.25 ", "F - test-render-float-decimal-normal 0.25"
+  # 0.75
+  clear-screen screen
+  var three-quarters/xmm0: float <- rational 3, 4
+  var dummy/eax: int <- render-float-decimal screen, three-quarters, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "0.75 ", "F - test-render-float-decimal-normal 0.75"
+  # 0.125
+  clear-screen screen
+  var eighth/xmm0: float <- rational 1, 8
+  var dummy/eax: int <- render-float-decimal screen, eighth, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "0.125 ", "F - test-render-float-decimal-normal 0.125"
+  # 0.0625; start using scientific notation
+  clear-screen screen
+  var sixteenth/xmm0: float <- rational 1, 0x10
+  var dummy/eax: int <- render-float-decimal screen, sixteenth, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "6.25e-2 ", "F - test-render-float-decimal-normal 0.0625"
+  # sqrt(2); truncate floats with lots of digits after the decimal but not too many before
+  clear-screen screen
+  var two-f/xmm0: float <- rational 2, 1
+  var sqrt-2/xmm0: float <- square-root two-f
+  var dummy/eax: int <- render-float-decimal screen, sqrt-2, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "1.414 ", "F - test-render-float-decimal-normal √2"
+}
+
+# print whole integers without decimals
+fn test-render-float-decimal-integer {
+  var screen-on-stack: screen
+  var screen/esi: (addr screen) <- address screen-on-stack
+  initialize-screen screen, 0x20, 5  # 32 columns should be more than enough
+  # 1
+  var one-f/xmm0: float <- rational 1, 1
+  var dummy/eax: int <- render-float-decimal screen, one-f, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "1 ", "F - test-render-float-decimal-integer 1"
+  # 2
+  clear-screen screen
+  var two-f/xmm0: float <- rational 2, 1
+  var dummy/eax: int <- render-float-decimal screen, two-f, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "2 ", "F - test-render-float-decimal-integer 2"
+  # 10
+  clear-screen screen
+  var ten-f/xmm0: float <- rational 0xa, 1
+  var dummy/eax: int <- render-float-decimal screen, ten-f, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "10 ", "F - test-render-float-decimal-integer 10"
+  # -10
+  clear-screen screen
+  var minus-ten-f/xmm0: float <- rational -0xa, 1
+  var dummy/eax: int <- render-float-decimal screen, minus-ten-f, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "-10 ", "F - test-render-float-decimal-integer -10"
+  # 999
+  clear-screen screen
+  var minus-ten-f/xmm0: float <- rational 0x3e7, 1
+  var dummy/eax: int <- render-float-decimal screen, minus-ten-f, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "999 ", "F - test-render-float-decimal-integer 1000"
+  # 1000 - start using scientific notation
+  clear-screen screen
+  var minus-ten-f/xmm0: float <- rational 0x3e8, 1
+  var dummy/eax: int <- render-float-decimal screen, minus-ten-f, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "1.00e3 ", "F - test-render-float-decimal-integer 1000"
+  # 100,000
+  clear-screen screen
+  var hundred-thousand/eax: int <- copy 0x186a0
+  var hundred-thousand-f/xmm0: float <- convert hundred-thousand
+  var dummy/eax: int <- render-float-decimal screen, hundred-thousand-f, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "1.00e5 ", "F - test-render-float-decimal-integer 100,000"
+}
+
+fn test-render-float-decimal-zero {
+  var screen-on-stack: screen
+  var screen/esi: (addr screen) <- address screen-on-stack
+  initialize-screen screen, 0x20, 5  # 32 columns should be more than enough
+  var zero: float
+  var dummy/eax: int <- render-float-decimal screen, zero, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "0 ", "F - test-render-float-decimal-zero"
+}
+
+fn test-render-float-decimal-negative-zero {
+  var screen-on-stack: screen
+  var screen/esi: (addr screen) <- address screen-on-stack
+  initialize-screen screen, 0x20, 5  # 32 columns should be more than enough
+  var n: int
+  copy-to n, 0x80000000
+  var negative-zero/xmm0: float <- reinterpret n
+  var dummy/eax: int <- render-float-decimal screen, negative-zero, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "-0 ", "F - test-render-float-decimal-negative-zero"
+}
+
+fn test-render-float-decimal-infinity {
+  var screen-on-stack: screen
+  var screen/esi: (addr screen) <- address screen-on-stack
+  initialize-screen screen, 0x20, 5  # 32 columns should be more than enough
+  var n: int
+  #          0|11111111|00000000000000000000000
+  #          0111|1111|1000|0000|0000|0000|0000|0000
+  copy-to n, 0x7f800000
+  var infinity/xmm0: float <- reinterpret n
+  var dummy/eax: int <- render-float-decimal screen, infinity, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "Inf ", "F - test-render-float-decimal-infinity"
+}
+
+fn test-render-float-decimal-negative-infinity {
+  var screen-on-stack: screen
+  var screen/esi: (addr screen) <- address screen-on-stack
+  initialize-screen screen, 0x20, 5  # 32 columns should be more than enough
+  var n: int
+  copy-to n, 0xff800000
+  var negative-infinity/xmm0: float <- reinterpret n
+  var dummy/eax: int <- render-float-decimal screen, negative-infinity, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "-Inf ", "F - test-render-float-decimal-negative-infinity"
+}
+
+fn test-render-float-decimal-not-a-number {
+  var screen-on-stack: screen
+  var screen/esi: (addr screen) <- address screen-on-stack
+  initialize-screen screen, 0x20, 5  # 32 columns should be more than enough
+  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
+  var dummy/eax: int <- render-float-decimal screen, nan, 3/precision, 0, 0, 3/fg, 0/bg
+  check-screen-row screen, 0/y, "NaN ", "F - test-render-float-decimal-not-a-number"
+}
+
+# 'precision' controls the maximum width past which we resort to scientific notation
+fn render-float-decimal screen: (addr screen), in: float, precision: int, x: int, y: int, color: int, background-color: int -> _/eax: int {
+  # - special names
+  var bits/eax: int <- reinterpret in
+  compare bits, 0
+  {
+    break-if-!=
+    var new-x/eax: int <- draw-text-rightward-over-full-screen screen, "0", x, y, color, background-color
+    return new-x
+  }
+  compare bits, 0x80000000
+  {
+    break-if-!=
+    var new-x/eax: int <- draw-text-rightward-over-full-screen screen, "-0", x, y, color, background-color
+    return new-x
+  }
+  compare bits, 0x7f800000
+  {
+    break-if-!=
+    var new-x/eax: int <- draw-text-rightward-over-full-screen screen, "Inf", x, y, color, background-color
+    return new-x
+  }
+  compare bits, 0xff800000
+  {
+    break-if-!=
+    var new-x/eax: int <- draw-text-rightward-over-full-screen screen, "-Inf", x, y, color, background-color
+    return new-x
+  }
+  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-!=
+    var new-x/eax: int <- draw-text-rightward-over-full-screen screen, "NaN", x, y, color, background-color
+    return new-x
+  }
+  # - regular numbers
+  var sign/edx: int <- copy bits
+  sign <- shift-right 0x1f
+  {
+    compare sign, 1
+    break-if-!=
+    draw-code-point screen, 0x2d/minus, x, y, color, background-color
+    increment x
+  }
+
+  # 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
+  }
+
+  var new-x/eax: int <- render-float-buffer screen, buf, n, dp, precision, x, y, color, background-color
+  return new-x
+}
+
+# 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 render-float-buffer screen: (addr screen), _buf: (addr array byte), n: int, dp: int, precision: int, x: int, y: int, color: int, background-color: int -> _/eax: int {
+  var buf/edi: (addr array byte) <- copy _buf
+  {
+    compare dp, 0
+    break-if->=
+    var new-x/eax: int <- render-float-buffer-in-scientific-notation screen, buf, n, dp, precision, x, y, color, background-color
+    return new-x
+  }
+  {
+    var dp2/eax: int <- copy dp
+    compare dp2, precision
+    break-if-<=
+    var new-x/eax: int <- render-float-buffer-in-scientific-notation screen, buf, n, dp, precision, x, y, color, background-color
+    return new-x
+  }
+  {
+    compare dp, 0
+    break-if-!=
+    draw-code-point screen, 0x30/0, x, y, color, background-color
+    increment x
+  }
+  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-!=
+      draw-code-point screen, 0x2e/decimal-point, x, y, color, background-color
+      increment x
+    }
+    var curr-a/ecx: (addr byte) <- index buf, i
+    var curr/ecx: byte <- copy-byte *curr-a
+    curr <- add 0x30/0
+    var curr-grapheme/ecx: grapheme <- copy curr
+    draw-grapheme screen, curr-grapheme, x, y, color, background-color
+    increment x
+    i <- increment
+    loop
+  }
+  return x
+}
+
+fn render-float-buffer-in-scientific-notation screen: (addr screen), _buf: (addr array byte), n: int, dp: int, precision: int, x: int, y: int, color: int, background-color: int -> _/eax: 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-!=
+      draw-code-point screen, 0x2e/decimal-point, x, y, color, background-color
+      increment x
+    }
+    var curr-a/ecx: (addr byte) <- index buf, i
+    var curr/ecx: byte <- copy-byte *curr-a
+    curr <- add 0x30/0
+    var curr-grapheme/ecx: grapheme <- copy curr
+    draw-grapheme screen, curr-grapheme, x, y, color, background-color
+    increment x
+    #
+    i <- increment
+    loop
+  }
+  draw-code-point screen, 0x65/e, x, y, color, background-color
+  increment x
+  decrement dp
+  var new-x/eax: int <- copy 0
+  var new-y/ecx: int <- copy 0
+  new-x, new-y <- draw-int32-decimal-wrapping-right-then-down-over-full-screen screen, dp, x, y, color, background-color
+  return new-x
+}
+
+# follows the structure of render-float-decimal
+# '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
+}