about summary refs log tree commit diff stats
path: root/412render-float-decimal.mu
blob: 7aaf8b3a4ae612997873729e98885bf4d25e0784 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Python: package ranger.gui</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head><body bgcolor="#f0f0f8">

<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
<tr bgcolor="#7799ee">
<td valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial">&nbsp;<br><big><big><strong><a href="ranger.html"><font color="#ffffff">ranger</font></a>.gui</strong></big></big></font></td
><td align=right valign=bottom
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:/home/hut/work/ranger/ranger/gui/__init__.py">/home/hut/work/ranger/ranger/gui/__init__.py</a></font></td></tr></table>
    <p></p>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#aa55cc">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Package Contents</strong></big></font></td></tr>
    
<tr><td bgcolor="#aa55cc"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="ranger.gui.color.html">color</a><br>
<a href="ranger.gui.colorscheme.html">colorscheme</a><br>
</td><td width="25%" valign=top><a href="ranger.gui.defaultui.html">defaultui</a><br>
<a href="ranger.gui.displayable.html">displayable</a><br>
</td><td width="25%" valign=top><a href="ranger.gui.mouse_event.html">mouse_event</a><br>
<a href="ranger.gui.ui.html">ui</a><br>
</td><td width="25%" valign=top><a href="ranger.gui.widgets.html"><strong>widgets</strong>&nbsp;(package)</a><br>
</td></tr></table></td></tr></table>
</body></html>
href='#n553'>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
}