summary refs log tree commit diff stats
path: root/lib/system/formatfloat.nim
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2021-07-20 13:13:52 -0700
committerGitHub <noreply@github.com>2021-07-20 22:13:52 +0200
commitcf0cf32d276002e850a87667fff62c4df12999d6 (patch)
treed35564ef08d681941158d7d457d797e9775b40eb /lib/system/formatfloat.nim
parenta8b3e7c05919511db62f1aabd706c46316b4f7b6 (diff)
downloadNim-cf0cf32d276002e850a87667fff62c4df12999d6.tar.gz
make -d:nimFpRoundtrips work consistently in vm vs rt, fix #18400, etc (#18531)
* compiler/vmhooks: add getVar to allow vmops with var params
* addFloat vmops with var param
* cgen now renders float32 literals in c backend using roundtrip float to string
Diffstat (limited to 'lib/system/formatfloat.nim')
-rw-r--r--lib/system/formatfloat.nim166
1 files changed, 117 insertions, 49 deletions
diff --git a/lib/system/formatfloat.nim b/lib/system/formatfloat.nim
index 772f0a848..cb46c6ea7 100644
--- a/lib/system/formatfloat.nim
+++ b/lib/system/formatfloat.nim
@@ -7,58 +7,126 @@
 #    distribution, for details about the copyright.
 #
 
-when defined(nimFpRoundtrips) and not defined(nimscript) and
-    not defined(js) and defined(nimHasDragonBox):
-  import dragonbox
+proc c_memcpy(a, b: pointer, size: csize_t): pointer {.importc: "memcpy", header: "<string.h>", discardable.}
 
-  proc writeFloatToBuffer*(buf: var array[65, char]; value: BiggestFloat): int =
-    ## This is the implementation to format floats.
-    ##
-    ## returns the amount of bytes written to `buf` not counting the
-    ## terminating '\0' character.
-    result = toChars(buf, value, forceTrailingDotZero=true)
-    buf[result] = '\0'
+proc addCstringN(result: var string, buf: cstring; buflen: int) =
+  # no nimvm support needed, so it doesn't need to be fast here either
+  let oldLen = result.len
+  let newLen = oldLen + buflen
+  result.setLen newLen
+  c_memcpy(result[oldLen].addr, buf, buflen.csize_t)
 
-else:
-  proc c_sprintf(buf, frmt: cstring): cint {.header: "<stdio.h>",
-                                      importc: "sprintf", varargs, noSideEffect.}
+import dragonbox, schubfach
 
-  proc writeToBuffer(buf: var array[65, char]; value: cstring) =
-    var i = 0
-    while value[i] != '\0':
-      buf[i] = value[i]
-      inc i
+proc writeFloatToBufferRoundtrip*(buf: var array[65, char]; value: BiggestFloat): int =
+  ## This is the implementation to format floats.
+  ##
+  ## returns the amount of bytes written to `buf` not counting the
+  ## terminating '\0' character.
+  result = toChars(buf, value, forceTrailingDotZero=true)
+  buf[result] = '\0'
 
-  proc writeFloatToBuffer*(buf: var array[65, char]; value: BiggestFloat): int =
-    ## This is the implementation to format floats.
-    ##
-    ## returns the amount of bytes written to `buf` not counting the
-    ## terminating '\0' character.
-    var n: int = c_sprintf(addr buf, "%.16g", value)
-    var hasDot = false
-    for i in 0..n-1:
-      if buf[i] == ',':
-        buf[i] = '.'
-        hasDot = true
-      elif buf[i] in {'a'..'z', 'A'..'Z', '.'}:
-        hasDot = true
-    if not hasDot:
-      buf[n] = '.'
-      buf[n+1] = '0'
-      buf[n+2] = '\0'
-      result = n + 2
+proc writeFloatToBufferRoundtrip*(buf: var array[65, char]; value: float32): int =
+  result = float32ToChars(buf, value, forceTrailingDotZero=true)
+  buf[result] = '\0'
+
+proc c_sprintf(buf, frmt: cstring): cint {.header: "<stdio.h>",
+                                    importc: "sprintf", varargs, noSideEffect.}
+
+proc writeToBuffer(buf: var array[65, char]; value: cstring) =
+  var i = 0
+  while value[i] != '\0':
+    buf[i] = value[i]
+    inc i
+
+proc writeFloatToBufferSprintf*(buf: var array[65, char]; value: BiggestFloat): int =
+  ## This is the implementation to format floats.
+  ##
+  ## returns the amount of bytes written to `buf` not counting the
+  ## terminating '\0' character.
+  var n: int = c_sprintf(addr buf, "%.16g", value)
+  var hasDot = false
+  for i in 0..n-1:
+    if buf[i] == ',':
+      buf[i] = '.'
+      hasDot = true
+    elif buf[i] in {'a'..'z', 'A'..'Z', '.'}:
+      hasDot = true
+  if not hasDot:
+    buf[n] = '.'
+    buf[n+1] = '0'
+    buf[n+2] = '\0'
+    result = n + 2
+  else:
+    result = n
+  # On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN' or 'nan(ind)'
+  # of '-1.#IND' are produced.
+  # We want to get rid of these here:
+  if buf[n-1] in {'n', 'N', 'D', 'd', ')'}:
+    writeToBuffer(buf, "nan")
+    result = 3
+  elif buf[n-1] == 'F':
+    if buf[0] == '-':
+      writeToBuffer(buf, "-inf")
+      result = 4
     else:
-      result = n
-    # On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN' or 'nan(ind)'
-    # of '-1.#IND' are produced.
-    # We want to get rid of these here:
-    if buf[n-1] in {'n', 'N', 'D', 'd', ')'}:
-      writeToBuffer(buf, "nan")
+      writeToBuffer(buf, "inf")
       result = 3
-    elif buf[n-1] == 'F':
-      if buf[0] == '-':
-        writeToBuffer(buf, "-inf")
-        result = 4
-      else:
-        writeToBuffer(buf, "inf")
-        result = 3
+
+proc writeFloatToBuffer*(buf: var array[65, char]; value: BiggestFloat | float32): int {.inline.} =
+  when defined(nimFpRoundtrips):
+    writeFloatToBufferRoundtrip(buf, value)
+  else:
+    writeFloatToBufferSprintf(buf, value)
+
+proc addFloatRoundtrip*(result: var string; x: float | float32) =
+  when nimvm:
+    doAssert false
+  else:
+    var buffer {.noinit.}: array[65, char]
+    let n = writeFloatToBufferRoundtrip(buffer, x)
+    result.addCstringN(cstring(buffer[0].addr), n)
+
+proc addFloatSprintf*(result: var string; x: float) =
+  when nimvm:
+    doAssert false
+  else:
+    var buffer {.noinit.}: array[65, char]
+    let n = writeFloatToBufferSprintf(buffer, x)
+    result.addCstringN(cstring(buffer[0].addr), n)
+
+proc nimFloatToString(a: float): cstring =
+  ## ensures the result doesn't print like an integer, i.e. return 2.0, not 2
+  # print `-0.0` properly
+  asm """
+    function nimOnlyDigitsOrMinus(n) {
+      return n.toString().match(/^-?\d+$/);
+    }
+    if (Number.isSafeInteger(`a`))
+      `result` = `a` === 0 && 1 / `a` < 0 ? "-0.0" : `a`+".0"
+    else {
+      `result` = `a`+""
+      if(nimOnlyDigitsOrMinus(`result`)){
+        `result` = `a`+".0"
+      }
+    }
+  """
+
+proc addFloat*(result: var string; x: float | float32) {.inline.} =
+  ## Converts float to its string representation and appends it to `result`.
+  runnableExamples:
+    var
+      s = "foo:"
+      b = 45.67
+    s.addFloat(45.67)
+    assert s == "foo:45.67"
+  template impl =
+    when defined(nimFpRoundtrips):
+      addFloatRoundtrip(result, x)
+    else:
+      addFloatSprintf(result, x)
+  when defined(js):
+    when nimvm: impl()
+    else:
+      result.add nimFloatToString(x)
+  else: impl()