formatfloat.nim 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2022 Nim contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements formatting floats as strings.
  10. when defined(nimPreviewSlimSystem):
  11. import std/assertions
  12. proc c_memcpy(a, b: pointer, size: csize_t): pointer {.importc: "memcpy", header: "<string.h>", discardable.}
  13. proc addCstringN(result: var string, buf: cstring; buflen: int) =
  14. # no nimvm support needed, so it doesn't need to be fast here either
  15. let oldLen = result.len
  16. let newLen = oldLen + buflen
  17. result.setLen newLen
  18. c_memcpy(result[oldLen].addr, buf, buflen.csize_t)
  19. import std/private/[dragonbox, schubfach]
  20. proc writeFloatToBufferRoundtrip*(buf: var array[65, char]; value: BiggestFloat): int =
  21. ## This is the implementation to format floats.
  22. ##
  23. ## returns the amount of bytes written to `buf` not counting the
  24. ## terminating '\0' character.
  25. result = toChars(buf, value, forceTrailingDotZero=true).int
  26. buf[result] = '\0'
  27. proc writeFloatToBufferRoundtrip*(buf: var array[65, char]; value: float32): int =
  28. result = float32ToChars(buf, value, forceTrailingDotZero=true).int
  29. buf[result] = '\0'
  30. proc c_snprintf(buf: cstring, n: csize_t, frmt: cstring): cint {.header: "<stdio.h>",
  31. importc: "snprintf", varargs, noSideEffect.}
  32. proc writeToBuffer(buf: var array[65, char]; value: cstring) =
  33. var i = 0
  34. while value[i] != '\0':
  35. buf[i] = value[i]
  36. inc i
  37. proc writeFloatToBufferSprintf*(buf: var array[65, char]; value: BiggestFloat): int =
  38. ## This is the implementation to format floats.
  39. ##
  40. ## returns the amount of bytes written to `buf` not counting the
  41. ## terminating '\0' character.
  42. var n = c_snprintf(cast[cstring](addr buf), 65, "%.16g", value).int
  43. var hasDot = false
  44. for i in 0..n-1:
  45. if buf[i] == ',':
  46. buf[i] = '.'
  47. hasDot = true
  48. elif buf[i] in {'a'..'z', 'A'..'Z', '.'}:
  49. hasDot = true
  50. if not hasDot:
  51. buf[n] = '.'
  52. buf[n+1] = '0'
  53. buf[n+2] = '\0'
  54. result = n + 2
  55. else:
  56. result = n
  57. # On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN' or 'nan(ind)'
  58. # of '-1.#IND' are produced.
  59. # We want to get rid of these here:
  60. if buf[n-1] in {'n', 'N', 'D', 'd', ')'}:
  61. writeToBuffer(buf, "nan")
  62. result = 3
  63. elif buf[n-1] == 'F':
  64. if buf[0] == '-':
  65. writeToBuffer(buf, "-inf")
  66. result = 4
  67. else:
  68. writeToBuffer(buf, "inf")
  69. result = 3
  70. proc writeFloatToBuffer*(buf: var array[65, char]; value: BiggestFloat | float32): int {.inline.} =
  71. when defined(nimLegacySprintf):
  72. writeFloatToBufferSprintf(buf, value)
  73. else:
  74. writeFloatToBufferRoundtrip(buf, value)
  75. proc addFloatRoundtrip*(result: var string; x: float | float32) =
  76. when nimvm:
  77. raiseAssert "unreachable"
  78. else:
  79. var buffer {.noinit.}: array[65, char]
  80. let n = writeFloatToBufferRoundtrip(buffer, x)
  81. result.addCstringN(cast[cstring](buffer[0].addr), n)
  82. proc addFloatSprintf*(result: var string; x: float) =
  83. when nimvm:
  84. raiseAssert "unreachable"
  85. else:
  86. var buffer {.noinit.}: array[65, char]
  87. let n = writeFloatToBufferSprintf(buffer, x)
  88. result.addCstringN(cast[cstring](buffer[0].addr), n)
  89. when defined(js):
  90. proc nimFloatToString(a: float): cstring =
  91. ## ensures the result doesn't print like an integer, i.e. return 2.0, not 2
  92. # print `-0.0` properly
  93. {.emit: """
  94. function nimOnlyDigitsOrMinus(n) {
  95. return n.toString().match(/^-?\d+$/);
  96. }
  97. if (Number.isSafeInteger(`a`))
  98. `result` = `a` === 0 && 1 / `a` < 0 ? "-0.0" : `a`+".0";
  99. else {
  100. `result` = `a`+"";
  101. if(nimOnlyDigitsOrMinus(`result`)){
  102. `result` = `a`+".0";
  103. }
  104. }
  105. """.}
  106. proc addFloat*(result: var string; x: float | float32) {.inline.} =
  107. ## Converts float to its string representation and appends it to `result`.
  108. runnableExamples:
  109. var
  110. s = "foo:"
  111. b = 45.67
  112. s.addFloat(45.67)
  113. assert s == "foo:45.67"
  114. template impl =
  115. when defined(nimLegacySprintf):
  116. addFloatSprintf(result, x)
  117. else:
  118. addFloatRoundtrip(result, x)
  119. when defined(js):
  120. when nimvm: impl()
  121. else:
  122. result.add nimFloatToString(x)
  123. else: impl()
  124. when defined(nimPreviewSlimSystem):
  125. func `$`*(x: float | float32): string =
  126. ## Outplace version of `addFloat`.
  127. result.addFloat(x)