summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ast.nim4
-rw-r--r--compiler/ccgexprs.nim12
-rw-r--r--compiler/jsgen.nim7
-rw-r--r--compiler/nim.cfg1
-rw-r--r--compiler/rodutils.nim10
-rw-r--r--compiler/semfold.nim1
-rw-r--r--compiler/vmgen.nim3
-rw-r--r--compiler/vmhooks.nim32
-rw-r--r--compiler/vmops.nim11
-rw-r--r--lib/system.nim3
-rw-r--r--lib/system/dollars.nim13
-rw-r--r--lib/system/formatfloat.nim166
-rw-r--r--lib/system/jssys.nim18
-rw-r--r--lib/system/repr_v2.nim6
-rw-r--r--lib/system/strmantle.nim41
-rw-r--r--tests/config.nims2
-rw-r--r--tests/errmsgs/treportunused.nim14
-rw-r--r--tests/float/nim.cfg1
-rw-r--r--tests/float/tfloats.nim117
-rw-r--r--tests/stdlib/tjson.nim6
-rw-r--r--tests/stdlib/tjsonutils.nim19
21 files changed, 287 insertions, 200 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index f1f73dcee..8b836e088 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -652,7 +652,7 @@ type
     mUnaryMinusI, mUnaryMinusI64, mAbsI, mNot,
     mUnaryPlusI, mBitnotI,
     mUnaryPlusF64, mUnaryMinusF64,
-    mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr,
+    mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mCStrToStr,
     mStrToStr, mEnumToStr,
     mAnd, mOr,
     mImplies, mIff, mExists, mForall, mOld,
@@ -720,7 +720,7 @@ const
     mEqRef, mEqProc, mLePtr, mLtPtr, mEqCString, mXor,
     mUnaryMinusI, mUnaryMinusI64, mAbsI, mNot, mUnaryPlusI, mBitnotI,
     mUnaryPlusF64, mUnaryMinusF64,
-    mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr,
+    mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mCStrToStr,
     mStrToStr, mEnumToStr,
     mAnd, mOr,
     mEqStr, mLeStr, mLtStr,
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 09326ceeb..79c73b602 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -94,9 +94,12 @@ proc genLiteral(p: BProc, n: PNode, ty: PType): Rope =
     else:
       result = makeCString(n.strVal)
   of nkFloatLit, nkFloat64Lit:
-    result = rope(n.floatVal.toStrMaxPrecision)
+    if ty.kind == tyFloat32:
+      result = rope(n.floatVal.float32.toStrMaxPrecision)
+    else:
+      result = rope(n.floatVal.toStrMaxPrecision)
   of nkFloat32Lit:
-    result = rope(n.floatVal.toStrMaxPrecision("f"))
+    result = rope(n.floatVal.float32.toStrMaxPrecision)
   else:
     internalError(p.config, n.info, "genLiteral(" & $n.kind & ')')
     result = nil
@@ -2300,11 +2303,6 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   of mInt64ToStr: genDollar(p, e, d, "#nimInt64ToStr($1)")
   of mBoolToStr: genDollar(p, e, d, "#nimBoolToStr($1)")
   of mCharToStr: genDollar(p, e, d, "#nimCharToStr($1)")
-  of mFloatToStr:
-    if e[1].typ.skipTypes(abstractInst).kind == tyFloat32:
-      genDollar(p, e, d, "#nimFloat32ToStr($1)")
-    else:
-      genDollar(p, e, d, "#nimFloatToStr($1)")
   of mCStrToStr: genDollar(p, e, d, "#cstrToNimstr($1)")
   of mStrToStr, mUnown: expr(p, e[1], d)
   of mIsolate: genCall(p, e, d)
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 82fba02a6..4b89b0cde 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -434,7 +434,6 @@ const # magic checked op; magic unchecked op;
     mBoolToStr: ["nimBoolToStr", "nimBoolToStr"],
     mIntToStr: ["cstrToNimstr", "cstrToNimstr"],
     mInt64ToStr: ["cstrToNimstr", "cstrToNimstr"],
-    mFloatToStr: ["cstrToNimstr", "cstrToNimstr"],
     mCStrToStr: ["cstrToNimstr", "cstrToNimstr"],
     mStrToStr: ["", ""]]
 
@@ -656,9 +655,6 @@ proc arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
   of mBoolToStr: applyFormat("nimBoolToStr($1)", "nimBoolToStr($1)")
   of mIntToStr: applyFormat("cstrToNimstr(($1) + \"\")", "cstrToNimstr(($1) + \"\")")
   of mInt64ToStr: applyFormat("cstrToNimstr(($1) + \"\")", "cstrToNimstr(($1) + \"\")")
-  of mFloatToStr:
-    useMagic(p, "nimFloatToString")
-    applyFormat "cstrToNimstr(nimFloatToString($1))"
   of mCStrToStr: applyFormat("cstrToNimstr($1)", "cstrToNimstr($1)")
   of mStrToStr, mUnown, mIsolate: applyFormat("$1", "$1")
   else:
@@ -682,8 +678,7 @@ proc arith(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
     gen(p, n[1], x)
     gen(p, n[2], y)
     r.res = "($1 >>> $2)" % [x.rdLoc, y.rdLoc]
-  of mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mFloatToStr,
-      mCStrToStr, mStrToStr, mEnumToStr:
+  of mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mCStrToStr, mStrToStr, mEnumToStr:
     arithAux(p, n, r, op)
   of mEqRef:
     if mapType(n[1].typ) != etyBaseIndex:
diff --git a/compiler/nim.cfg b/compiler/nim.cfg
index 9ecd00b0b..f10d847ac 100644
--- a/compiler/nim.cfg
+++ b/compiler/nim.cfg
@@ -4,6 +4,7 @@ hint:XDeclaredButNotUsed:off
 
 define:booting
 define:nimcore
+define:nimFpRoundtrips
 
 #import:"$projectpath/testability"
 
diff --git a/compiler/rodutils.nim b/compiler/rodutils.nim
index e13b08e05..a4f7a5146 100644
--- a/compiler/rodutils.nim
+++ b/compiler/rodutils.nim
@@ -39,7 +39,10 @@ when not declared(signbit):
   proc signbit*(x: SomeFloat): bool {.inline.} =
     result = c_signbit(x) != 0
 
-proc toStrMaxPrecision*(f: BiggestFloat, literalPostfix = ""): string =
+import system/formatfloat
+
+proc toStrMaxPrecision*(f: BiggestFloat | float32): string =
+  const literalPostfix = when f is float32: "f" else: ""
   case classify(f)
   of fcNan:
     if signbit(f):
@@ -55,9 +58,8 @@ proc toStrMaxPrecision*(f: BiggestFloat, literalPostfix = ""): string =
   of fcNegInf:
     result = "-INF"
   else:
-    result = newString(81)
-    let n = c_snprintf(result.cstring, result.len.uint, "%#.16e%s", f, literalPostfix.cstring)
-    setLen(result, n)
+    result.addFloatRoundtrip(f)
+    result.add literalPostfix
 
 proc encodeStr*(s: string, result: var string) =
   for i in 0..<s.len:
diff --git a/compiler/semfold.nim b/compiler/semfold.nim
index 04ed73209..e5f0643bc 100644
--- a/compiler/semfold.nim
+++ b/compiler/semfold.nim
@@ -290,7 +290,6 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; idgen: IdGenerator; g: ModuleGraph): P
   of mBoolToStr:
     if getOrdValue(a) == 0: result = newStrNodeT("false", n, g)
     else: result = newStrNodeT("true", n, g)
-  of mFloatToStr: result = newStrNodeT($getFloat(a), n, g)
   of mCStrToStr, mCharToStr:
     if a.kind == nkBracket:
       var s = ""
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index e5e4a854e..be86550ae 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -1127,8 +1127,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
     let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})
     if t.kind in {tyUInt8..tyUInt32} or (t.kind == tyUInt and t.size < 8):
       c.gABC(n, opcNarrowU, dest, TRegister(t.size*8))
-  of mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr,
-     mFloatToStr, mCStrToStr, mStrToStr, mEnumToStr:
+  of mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mCStrToStr, mStrToStr, mEnumToStr:
     genConv(c, n, n[1], dest)
   of mEqStr, mEqCString: genBinaryABC(c, n, dest, opcEqStr)
   of mLeStr: genBinaryABC(c, n, dest, opcLeStr)
diff --git a/compiler/vmhooks.nim b/compiler/vmhooks.nim
index 573d84853..1ede87e5e 100644
--- a/compiler/vmhooks.nim
+++ b/compiler/vmhooks.nim
@@ -36,10 +36,14 @@ proc setResult*(a: VmArgs; v: seq[string]) =
   for x in v: n.add newStrNode(nkStrLit, x)
   a.slots[a.ra].node = n
 
-template getX(k, field) {.dirty.} =
+template getReg(a, i): untyped =
   doAssert i < a.rc-1
-  doAssert a.slots[i+a.rb+1].kind == k
-  result = a.slots[i+a.rb+1].field
+  a.slots[i+a.rb+1].unsafeAddr
+
+template getX(k, field): untyped {.dirty.} =
+  let p = getReg(a, i)
+  doAssert p.kind == k, $p.kind
+  p.field
 
 proc numArgs*(a: VmArgs): int =
   result = a.rc-1
@@ -47,19 +51,17 @@ proc numArgs*(a: VmArgs): int =
 proc getInt*(a: VmArgs; i: Natural): BiggestInt = getX(rkInt, intVal)
 proc getBool*(a: VmArgs; i: Natural): bool = getInt(a, i) != 0
 proc getFloat*(a: VmArgs; i: Natural): BiggestFloat = getX(rkFloat, floatVal)
-proc getString*(a: VmArgs; i: Natural): string =
-  doAssert i < a.rc-1
-  doAssert a.slots[i+a.rb+1].kind == rkNode
-  result = a.slots[i+a.rb+1].node.strVal
-
-proc getNode*(a: VmArgs; i: Natural): PNode =
-  doAssert i < a.rc-1
-  doAssert a.slots[i+a.rb+1].kind == rkNode
-  result = a.slots[i+a.rb+1].node
+proc getNode*(a: VmArgs; i: Natural): PNode = getX(rkNode, node)
+proc getString*(a: VmArgs; i: Natural): string = getX(rkNode, node).strVal
+proc getVar*(a: VmArgs; i: Natural): PNode =
+  let p = getReg(a, i)
+  # depending on whether we come from top-level or proc scope, we need to consider 2 cases
+  case p.kind
+  of rkRegisterAddr: result = p.regAddr.node
+  of rkNodeAddr: result = p.nodeAddr[]
+  else: doAssert false, $p.kind
 
 proc getNodeAddr*(a: VmArgs; i: Natural): PNode =
-  doAssert i < a.rc-1
-  doAssert a.slots[i+a.rb+1].kind == rkNodeAddr
-  let nodeAddr = a.slots[i+a.rb+1].nodeAddr
+  let nodeAddr = getX(rkNodeAddr, nodeAddr)
   doAssert nodeAddr != nil
   result = nodeAddr[]
diff --git a/compiler/vmops.nim b/compiler/vmops.nim
index 283306ecc..bc331bbcd 100644
--- a/compiler/vmops.nim
+++ b/compiler/vmops.nim
@@ -27,6 +27,7 @@ from std/md5 import getMD5
 from std/times import cpuTime
 from std/hashes import hash
 from std/osproc import nil
+from system/formatfloat import addFloatRoundtrip, addFloatSprintf
 
 from sighashes import symBodyDigest
 
@@ -325,3 +326,13 @@ proc registerAdditionalOps*(c: PCtx) =
   registerCallback c, "stdlib.typetraits.hasClosureImpl", proc (a: VmArgs) =
     let fn = getNode(a, 0)
     setResult(a, fn.kind == nkClosure or (fn.typ != nil and fn.typ.callConv == ccClosure))
+
+  registerCallback c, "stdlib.formatfloat.addFloatRoundtrip", proc(a: VmArgs) =
+    let p = a.getVar(0)
+    let x = a.getFloat(1)
+    addFloatRoundtrip(p.strVal, x)
+
+  registerCallback c, "stdlib.formatfloat.addFloatSprintf", proc(a: VmArgs) =
+    let p = a.getVar(0)
+    let x = a.getFloat(1)
+    addFloatSprintf(p.strVal, x)
diff --git a/lib/system.nim b/lib/system.nim
index e878f9f2f..1da1444d2 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -2472,9 +2472,6 @@ when defined(js) or defined(nimscript):
   proc addInt*(result: var string; x: int64) =
     result.add $x
 
-  proc addFloat*(result: var string; x: float) =
-    result.add $x
-
 proc quit*(errormsg: string, errorcode = QuitFailure) {.noreturn.} =
   ## A shorthand for `echo(errormsg); quit(errorcode)`.
   when defined(nimscript) or defined(js) or (hostOS == "standalone"):
diff --git a/lib/system/dollars.nim b/lib/system/dollars.nim
index baae8f090..8634db382 100644
--- a/lib/system/dollars.nim
+++ b/lib/system/dollars.nim
@@ -1,5 +1,6 @@
 import std/private/digitsutils
-
+import system/formatfloat
+export addFloat
 
 proc `$`*(x: int): string {.magic: "IntToStr", noSideEffect.}
   ## The stringify operator for an integer argument. Returns `x`
@@ -40,13 +41,9 @@ proc `$`*(x: int64): string {.magic: "Int64ToStr", noSideEffect.}
   ## The stringify operator for an integer argument. Returns `x`
   ## converted to a decimal string.
 
-proc `$`*(x: float): string {.magic: "FloatToStr", noSideEffect.}
-  ## The stringify operator for a float argument. Returns `x`
-  ## converted to a decimal string.
-
-proc `$`*(x: float32): string {.magic: "FloatToStr", noSideEffect.}
-  ## The stringify operator for a float32 argument. Returns `x`
-  ## converted to a decimal string.
+func `$`*(x: float | float32): string =
+  ## Outplace version of `addFloat`.
+  result.addFloat(x)
 
 proc `$`*(x: bool): string {.magic: "BoolToStr", noSideEffect.}
   ## The stringify operator for a boolean argument. Returns `x`
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()
diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim
index 7cb5652c5..b42dc3a20 100644
--- a/lib/system/jssys.nim
+++ b/lib/system/jssys.nim
@@ -496,23 +496,6 @@ proc negInt(a: int): int {.compilerproc.} =
 proc negInt64(a: int64): int64 {.compilerproc.} =
   result = a*(-1)
 
-proc nimFloatToString(a: float): cstring {.compilerproc.} =
-  ## 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 absInt(a: int): int {.compilerproc.} =
   result = if a < 0: a*(-1) else: a
 
@@ -703,6 +686,7 @@ proc addChar(x: string, c: char) {.compilerproc, asmNoStackFrame.} =
 {.pop.}
 
 proc tenToThePowerOf(b: int): BiggestFloat =
+  # xxx deadcode
   var b = b
   var a = 10.0
   result = 1.0
diff --git a/lib/system/repr_v2.nim b/lib/system/repr_v2.nim
index 618cc2b40..8471ea148 100644
--- a/lib/system/repr_v2.nim
+++ b/lib/system/repr_v2.nim
@@ -19,9 +19,9 @@ proc repr*(x: uint64): string {.noSideEffect.} =
   ## converted to a decimal string.
   $x #Calls `$` from system/strmantle.nim
 
-proc repr*(x: float): string {.magic: "FloatToStr", noSideEffect.}
-  ## repr for a float argument. Returns `x`
-  ## converted to a decimal string.
+proc repr*(x: float): string =
+  ## Same as $x
+  $x
 
 proc repr*(x: bool): string {.magic: "BoolToStr", noSideEffect.}
   ## repr for a boolean argument. Returns `x`
diff --git a/lib/system/strmantle.nim b/lib/system/strmantle.nim
index 041272175..81ac5c751 100644
--- a/lib/system/strmantle.nim
+++ b/lib/system/strmantle.nim
@@ -69,47 +69,6 @@ proc nimIntToStr(x: int): string {.compilerRtl.} =
   result = newStringOfCap(sizeof(x)*4)
   result.addInt x
 
-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
-  copyMem(result[oldLen].addr, buf, buflen)
-
-import formatfloat
-
-proc addFloat*(result: var string; x: float) =
-  ## Converts float to its string representation and appends it to `result`.
-  ##
-  ## .. code-block:: Nim
-  ##   var
-  ##     a = "123"
-  ##     b = 45.67
-  ##   a.addFloat(b) # a <- "12345.67"
-  when nimvm:
-    result.add $x
-  else:
-    var buffer {.noinit.}: array[65, char]
-    let n = writeFloatToBuffer(buffer, x)
-    result.addCstringN(cstring(buffer[0].addr), n)
-
-proc nimFloatToStr(f: float): string {.compilerproc.} =
-  result = newStringOfCap(8)
-  result.addFloat f
-
-when defined(nimFpRoundtrips) and not defined(nimscript) and
-    not defined(js) and defined(nimHasDragonBox):
-  import schubfach
-
-proc nimFloat32ToStr(f: float32): string {.compilerproc.} =
-  when declared(float32ToChars):
-    result = newString(65)
-    let L = float32ToChars(result, f, forceTrailingDotZero=true)
-    setLen(result, L)
-  else:
-    result = newStringOfCap(8)
-    result.addFloat f
-
 proc c_strtod(buf: cstring, endptr: ptr cstring): float64 {.
   importc: "strtod", header: "<stdlib.h>", noSideEffect.}
 
diff --git a/tests/config.nims b/tests/config.nims
index 539de5e8d..12b303318 100644
--- a/tests/config.nims
+++ b/tests/config.nims
@@ -34,3 +34,5 @@ hint("Processing", off)
 switch("define", "nimExperimentalAsyncjsThen")
 switch("define", "nimExperimentalJsfetch")
 switch("define", "nimExperimentalLinenoiseExtra")
+
+switch("define", "nimFpRoundtrips")
diff --git a/tests/errmsgs/treportunused.nim b/tests/errmsgs/treportunused.nim
index f9b7c3d11..3105b35ea 100644
--- a/tests/errmsgs/treportunused.nim
+++ b/tests/errmsgs/treportunused.nim
@@ -13,12 +13,12 @@ treportunused.nim(30, 5) Hint: 's8' is declared but not used [XDeclaredButNotUse
 treportunused.nim(31, 5) Hint: 's9' is declared but not used [XDeclaredButNotUsed]
 treportunused.nim(32, 6) Hint: 's10' is declared but not used [XDeclaredButNotUsed]
 treportunused.nim(33, 6) Hint: 's11' is declared but not used [XDeclaredButNotUsed]
+treportunused.nim(37, 3) Hint: 'v0.99' is declared but not used [XDeclaredButNotUsed]
+treportunused.nim(38, 3) Hint: 'v0.99.99' is declared but not used [XDeclaredButNotUsed]
 '''
 action: compile
 """
 
-#treportunused.nim(37, 3) Hint: 'v0.99' is declared but not used [XDeclaredButNotUsed]
-#treportunused.nim(38, 3) Hint: 'v0.99.99' is declared but not used [XDeclaredButNotUsed]
 # bug #9764
 iterator s1(a:string): int = discard
 iterator s2(): int = discard
@@ -32,9 +32,7 @@ var s9: int
 type s10 = object
 type s11 = type(1.2)
 
-when false:
-  # enabled again when Nim bootstraps with -d:nimFpRoundtrips
-  # https://github.com/nim-lang/Nim/issues/14407
-  let
-    `v0.99` = "0.99"
-    `v0.99.99` = "0.99.99"
+# bug #14407 (requires `compiler/nim.cfg` containing define:nimFpRoundtrips)
+let
+  `v0.99` = "0.99"
+  `v0.99.99` = "0.99.99"
diff --git a/tests/float/nim.cfg b/tests/float/nim.cfg
deleted file mode 100644
index d27bbf43b..000000000
--- a/tests/float/nim.cfg
+++ /dev/null
@@ -1 +0,0 @@
--d:nimFpRoundtrips
diff --git a/tests/float/tfloats.nim b/tests/float/tfloats.nim
index 30d9c50d6..63987bb8d 100644
--- a/tests/float/tfloats.nim
+++ b/tests/float/tfloats.nim
@@ -1,13 +1,14 @@
 discard """
+  matrix: "-d:nimFpRoundtrips; -u:nimFpRoundtrips"
   targets: "c cpp js"
 """
-# disabled: "windows"
 
 #[
 xxx merge all or most float tests into this file
 ]#
 
 import std/[fenv, math, strutils]
+import stdtest/testutils
 
 proc equalsOrNaNs(a, b: float): bool =
   if isNaN(a): isNaN(b)
@@ -62,27 +63,103 @@ template main =
     reject "1_.0"
     reject "1.0_"
 
-  block: # bug #18148
-    var a = 1.1'f32
-    doAssert $a == "1.1", $a # was failing
+  block: # bugs mentioned in https://github.com/nim-lang/Nim/pull/18504#issuecomment-881635317
+    block: # example 1
+      let a = 0.1+0.2
+      doAssert a != 0.3
+      when defined(nimFpRoundtrips):
+        doAssert $a == "0.30000000000000004"
+      else:
+        whenRuntimeJs: discard
+        do: doAssert $a == "0.3"
+    block: # example 2
+      const a = 0.1+0.2
+      when defined(nimFpRoundtrips):
+        doAssert $($a, a) == """("0.30000000000000004", 0.30000000000000004)"""
+      else:
+        whenRuntimeJs: discard
+        do: doAssert $($a, a) == """("0.3", 0.3)"""
+    block: # example 3
+      const a1 = 0.1+0.2
+      let a2 = a1
+      doAssert a1 != 0.3
+      when defined(nimFpRoundtrips):
+        doAssert $[$a1, $a2] == """["0.30000000000000004", "0.30000000000000004"]"""
+      else:
+        whenRuntimeJs: discard
+        do: doAssert $[$a1, $a2] == """["0.3", "0.3"]"""
 
-proc runtimeOnlyTests =
-  # enable for 'static' once -d:nimFpRoundtrips became the default
-  block: # bug #7717
-    proc test(f: float) =
-      let f2 = $f
-      let f3 = parseFloat(f2)
-      doAssert equalsOrNaNs(f, f3), $(f, f2, f3)
+  when defined(nimFpRoundtrips):
+    block: # bug #18148
+      var a = 1.1'f32
+      doAssert $a == "1.1", $a # was failing
 
-    test 1.0 + epsilon(float64)
-    test 1000000.0000000123
-    test log2(100000.0)
-    test maximumPositiveValue(float32)
-    test maximumPositiveValue(float64)
-    test minimumPositiveValue(float32)
-    test minimumPositiveValue(float64)
+    block: # bug #18400
+      block:
+        let a1 = 0.1'f32
+        let a2 = 0.2'f32
+        let a3 = a1 + a2
+        var s = ""
+        s.addFloat(a3)
+        whenVMorJs: discard # xxx refs #12884
+        do:
+          doAssert a3 == 0.3'f32
+          doAssert $a3 == "0.3"
+
+      block:
+        let a1 = 0.1
+        let a2 = 0.2
+        let a3 = a1 + a2
+        var s = ""
+        s.addFloat(a3)
+        doAssert a3 != 0.3
+        doAssert $a3 == "0.30000000000000004"
+
+      block:
+        var s = [-13.888888'f32]
+        whenRuntimeJs: discard
+        do:
+          doAssert $s == "[-13.888888]"
+          doAssert $s[0] == "-13.888888"
+
+    block: # bug #7717
+      proc test(f: float) =
+        let f2 = $f
+        let f3 = parseFloat(f2)
+        doAssert equalsOrNaNs(f, f3), $(f, f2, f3)
+      test 1.0 + epsilon(float64)
+      test 1000000.0000000123
+      test log2(100000.0)
+      test maximumPositiveValue(float32)
+      test maximumPositiveValue(float64)
+      test minimumPositiveValue(float32)
+      test minimumPositiveValue(float64)
+
+    block: # bug #12884
+      block: # example 1
+        const x0: float32 = 1.32
+        let x1 = 1.32
+        let x2 = 1.32'f32
+        var x3: float32 = 1.32
+        doAssert $(x0, x1, x2, x3) == "(1.32, 1.32, 1.32, 1.32)"
+      block: # example https://github.com/nim-lang/Nim/issues/12884#issuecomment-564967962
+        let x = float(1.32'f32)
+        when nimvm: discard # xxx prints 1.3
+        else:
+          when not defined(js):
+            doAssert $x == "1.3200000524520874"
+        doAssert $1.32 == "1.32"
+        doAssert $1.32'f32 == "1.32"
+        let x2 = 1.32'f32
+        doAssert $x2 == "1.32"
+      block:
+        var x = 1.23456789012345'f32
+        when nimvm:
+          discard # xxx, refs #12884
+        else:
+          when not defined(js):
+            doAssert x == 1.2345679'f32
+            doAssert $x == "1.2345679"
 
 static: main()
 main()
-
-runtimeOnlyTests()
diff --git a/tests/stdlib/tjson.nim b/tests/stdlib/tjson.nim
index 000f72038..289ef9d05 100644
--- a/tests/stdlib/tjson.nim
+++ b/tests/stdlib/tjson.nim
@@ -303,7 +303,7 @@ let jsonNode = %*mynode
 doAssert $jsonNode == """{"kind":"P","pChildren":[{"kind":"Text","textStr":"mychild"},{"kind":"Br"}]}"""
 doAssert $jsonNode.to(ContentNode) == """(kind: P, pChildren: @[(kind: Text, textStr: "mychild"), (kind: Br)])"""
 
-when defined(nimFpRoundtrips): # bug #17383
+block: # bug #17383
   testRoundtrip(int32.high): "2147483647"
   testRoundtrip(uint32.high): "4294967295"
   when int.sizeof == 4:
@@ -316,7 +316,7 @@ when defined(nimFpRoundtrips): # bug #17383
     testRoundtrip(int64.high): "9223372036854775807"
     testRoundtrip(uint64.high): "18446744073709551615"
 
-when defined(nimFpRoundtrips): # bug #18007
+block: # bug #18007
   testRoundtrip([NaN, Inf, -Inf, 0.0, -0.0, 1.0]): """["nan","inf","-inf",0.0,-0.0,1.0]"""
   # pending https://github.com/nim-lang/Nim/issues/18025 use:
   # testRoundtrip([float32(NaN), Inf, -Inf, 0.0, -0.0, 1.0])
@@ -332,7 +332,7 @@ when defined(nimFpRoundtrips): # bug #18007
     testRoundtripVal(0.0): "0.0"
     testRoundtripVal(-0.0): "-0.0"
 
-when defined(nimFpRoundtrips): # bug #15397, bug #13196
+block: # bug #15397, bug #13196
   testRoundtripVal(1.0 + epsilon(float64)): "1.0000000000000002"
   testRoundtripVal(0.12345678901234567890123456789): "0.12345678901234568"
 
diff --git a/tests/stdlib/tjsonutils.nim b/tests/stdlib/tjsonutils.nim
index 3e9c422e0..205160471 100644
--- a/tests/stdlib/tjsonutils.nim
+++ b/tests/stdlib/tjsonutils.nim
@@ -161,16 +161,15 @@ template fn() =
     doAssert b[2].signbit
     doAssert not b[3].signbit
 
-  when defined(nimFpRoundtrips):
-    block: # bug #15397, bug #13196
-      let a = 0.1
-      let x = 0.12345678901234567890123456789
-      let b = (a + 0.2, 0.3, x)
-      testRoundtripVal(b): "[0.30000000000000004,0.3,0.12345678901234568]"
-
-      testRoundtripVal(0.12345678901234567890123456789): "0.12345678901234568"
-      testRoundtripVal(epsilon(float64)): "2.220446049250313e-16"
-      testRoundtripVal(1.0 + epsilon(float64)): "1.0000000000000002"
+  block: # bug #15397, bug #13196
+    let a = 0.1
+    let x = 0.12345678901234567890123456789
+    let b = (a + 0.2, 0.3, x)
+    testRoundtripVal(b): "[0.30000000000000004,0.3,0.12345678901234568]"
+
+    testRoundtripVal(0.12345678901234567890123456789): "0.12345678901234568"
+    testRoundtripVal(epsilon(float64)): "2.220446049250313e-16"
+    testRoundtripVal(1.0 + epsilon(float64)): "1.0000000000000002"
 
   block: # case object
     type Foo = object