ftoa_test.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. // Copyright 2009 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package strconv_test
  5. import (
  6. "math"
  7. "math/rand"
  8. . "strconv"
  9. "testing"
  10. )
  11. type ftoaTest struct {
  12. f float64
  13. fmt byte
  14. prec int
  15. s string
  16. }
  17. func fdiv(a, b float64) float64 { return a / b }
  18. const (
  19. below1e23 = 99999999999999974834176
  20. above1e23 = 100000000000000008388608
  21. )
  22. var ftoatests = []ftoaTest{
  23. {1, 'e', 5, "1.00000e+00"},
  24. {1, 'f', 5, "1.00000"},
  25. {1, 'g', 5, "1"},
  26. {1, 'g', -1, "1"},
  27. {1, 'x', -1, "0x1p+00"},
  28. {1, 'x', 5, "0x1.00000p+00"},
  29. {20, 'g', -1, "20"},
  30. {20, 'x', -1, "0x1.4p+04"},
  31. {1234567.8, 'g', -1, "1.2345678e+06"},
  32. {1234567.8, 'x', -1, "0x1.2d687cccccccdp+20"},
  33. {200000, 'g', -1, "200000"},
  34. {200000, 'x', -1, "0x1.86ap+17"},
  35. {200000, 'X', -1, "0X1.86AP+17"},
  36. {2000000, 'g', -1, "2e+06"},
  37. {1e10, 'g', -1, "1e+10"},
  38. // g conversion and zero suppression
  39. {400, 'g', 2, "4e+02"},
  40. {40, 'g', 2, "40"},
  41. {4, 'g', 2, "4"},
  42. {.4, 'g', 2, "0.4"},
  43. {.04, 'g', 2, "0.04"},
  44. {.004, 'g', 2, "0.004"},
  45. {.0004, 'g', 2, "0.0004"},
  46. {.00004, 'g', 2, "4e-05"},
  47. {.000004, 'g', 2, "4e-06"},
  48. {0, 'e', 5, "0.00000e+00"},
  49. {0, 'f', 5, "0.00000"},
  50. {0, 'g', 5, "0"},
  51. {0, 'g', -1, "0"},
  52. {0, 'x', 5, "0x0.00000p+00"},
  53. {-1, 'e', 5, "-1.00000e+00"},
  54. {-1, 'f', 5, "-1.00000"},
  55. {-1, 'g', 5, "-1"},
  56. {-1, 'g', -1, "-1"},
  57. {12, 'e', 5, "1.20000e+01"},
  58. {12, 'f', 5, "12.00000"},
  59. {12, 'g', 5, "12"},
  60. {12, 'g', -1, "12"},
  61. {123456700, 'e', 5, "1.23457e+08"},
  62. {123456700, 'f', 5, "123456700.00000"},
  63. {123456700, 'g', 5, "1.2346e+08"},
  64. {123456700, 'g', -1, "1.234567e+08"},
  65. {1.2345e6, 'e', 5, "1.23450e+06"},
  66. {1.2345e6, 'f', 5, "1234500.00000"},
  67. {1.2345e6, 'g', 5, "1.2345e+06"},
  68. // Round to even
  69. {1.2345e6, 'e', 3, "1.234e+06"},
  70. {1.2355e6, 'e', 3, "1.236e+06"},
  71. {1.2345, 'f', 3, "1.234"},
  72. {1.2355, 'f', 3, "1.236"},
  73. {1234567890123456.5, 'e', 15, "1.234567890123456e+15"},
  74. {1234567890123457.5, 'e', 15, "1.234567890123458e+15"},
  75. {108678236358137.625, 'g', -1, "1.0867823635813762e+14"},
  76. {1e23, 'e', 17, "9.99999999999999916e+22"},
  77. {1e23, 'f', 17, "99999999999999991611392.00000000000000000"},
  78. {1e23, 'g', 17, "9.9999999999999992e+22"},
  79. {1e23, 'e', -1, "1e+23"},
  80. {1e23, 'f', -1, "100000000000000000000000"},
  81. {1e23, 'g', -1, "1e+23"},
  82. {below1e23, 'e', 17, "9.99999999999999748e+22"},
  83. {below1e23, 'f', 17, "99999999999999974834176.00000000000000000"},
  84. {below1e23, 'g', 17, "9.9999999999999975e+22"},
  85. {below1e23, 'e', -1, "9.999999999999997e+22"},
  86. {below1e23, 'f', -1, "99999999999999970000000"},
  87. {below1e23, 'g', -1, "9.999999999999997e+22"},
  88. {above1e23, 'e', 17, "1.00000000000000008e+23"},
  89. {above1e23, 'f', 17, "100000000000000008388608.00000000000000000"},
  90. {above1e23, 'g', 17, "1.0000000000000001e+23"},
  91. {above1e23, 'e', -1, "1.0000000000000001e+23"},
  92. {above1e23, 'f', -1, "100000000000000010000000"},
  93. {above1e23, 'g', -1, "1.0000000000000001e+23"},
  94. {fdiv(5e-304, 1e20), 'g', -1, "5e-324"}, // avoid constant arithmetic
  95. {fdiv(-5e-304, 1e20), 'g', -1, "-5e-324"}, // avoid constant arithmetic
  96. {32, 'g', -1, "32"},
  97. {32, 'g', 0, "3e+01"},
  98. {100, 'x', -1, "0x1.9p+06"},
  99. {100, 'y', -1, "%y"},
  100. {math.NaN(), 'g', -1, "NaN"},
  101. {-math.NaN(), 'g', -1, "NaN"},
  102. {math.Inf(0), 'g', -1, "+Inf"},
  103. {math.Inf(-1), 'g', -1, "-Inf"},
  104. {-math.Inf(0), 'g', -1, "-Inf"},
  105. {-1, 'b', -1, "-4503599627370496p-52"},
  106. // fixed bugs
  107. {0.9, 'f', 1, "0.9"},
  108. {0.09, 'f', 1, "0.1"},
  109. {0.0999, 'f', 1, "0.1"},
  110. {0.05, 'f', 1, "0.1"},
  111. {0.05, 'f', 0, "0"},
  112. {0.5, 'f', 1, "0.5"},
  113. {0.5, 'f', 0, "0"},
  114. {1.5, 'f', 0, "2"},
  115. // https://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/
  116. {2.2250738585072012e-308, 'g', -1, "2.2250738585072014e-308"},
  117. // https://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/
  118. {2.2250738585072011e-308, 'g', -1, "2.225073858507201e-308"},
  119. // Issue 2625.
  120. {383260575764816448, 'f', 0, "383260575764816448"},
  121. {383260575764816448, 'g', -1, "3.8326057576481645e+17"},
  122. // Issue 29491.
  123. {498484681984085570, 'f', -1, "498484681984085570"},
  124. {-5.8339553793802237e+23, 'g', -1, "-5.8339553793802237e+23"},
  125. // rounding
  126. {2.275555555555555, 'x', -1, "0x1.23456789abcdep+01"},
  127. {2.275555555555555, 'x', 0, "0x1p+01"},
  128. {2.275555555555555, 'x', 2, "0x1.23p+01"},
  129. {2.275555555555555, 'x', 16, "0x1.23456789abcde000p+01"},
  130. {2.275555555555555, 'x', 21, "0x1.23456789abcde00000000p+01"},
  131. {2.2755555510520935, 'x', -1, "0x1.2345678p+01"},
  132. {2.2755555510520935, 'x', 6, "0x1.234568p+01"},
  133. {2.275555431842804, 'x', -1, "0x1.2345668p+01"},
  134. {2.275555431842804, 'x', 6, "0x1.234566p+01"},
  135. {3.999969482421875, 'x', -1, "0x1.ffffp+01"},
  136. {3.999969482421875, 'x', 4, "0x1.ffffp+01"},
  137. {3.999969482421875, 'x', 3, "0x1.000p+02"},
  138. {3.999969482421875, 'x', 2, "0x1.00p+02"},
  139. {3.999969482421875, 'x', 1, "0x1.0p+02"},
  140. {3.999969482421875, 'x', 0, "0x1p+02"},
  141. }
  142. func TestFtoa(t *testing.T) {
  143. for i := 0; i < len(ftoatests); i++ {
  144. test := &ftoatests[i]
  145. s := FormatFloat(test.f, test.fmt, test.prec, 64)
  146. if s != test.s {
  147. t.Error("testN=64", test.f, string(test.fmt), test.prec, "want", test.s, "got", s)
  148. }
  149. x := AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 64)
  150. if string(x) != "abc"+test.s {
  151. t.Error("AppendFloat testN=64", test.f, string(test.fmt), test.prec, "want", "abc"+test.s, "got", string(x))
  152. }
  153. if float64(float32(test.f)) == test.f && test.fmt != 'b' {
  154. s := FormatFloat(test.f, test.fmt, test.prec, 32)
  155. if s != test.s {
  156. t.Error("testN=32", test.f, string(test.fmt), test.prec, "want", test.s, "got", s)
  157. }
  158. x := AppendFloat([]byte("abc"), test.f, test.fmt, test.prec, 32)
  159. if string(x) != "abc"+test.s {
  160. t.Error("AppendFloat testN=32", test.f, string(test.fmt), test.prec, "want", "abc"+test.s, "got", string(x))
  161. }
  162. }
  163. }
  164. }
  165. func TestFtoaPowersOfTwo(t *testing.T) {
  166. for exp := -2048; exp <= 2048; exp++ {
  167. f := math.Ldexp(1, exp)
  168. if !math.IsInf(f, 0) {
  169. s := FormatFloat(f, 'e', -1, 64)
  170. if x, _ := ParseFloat(s, 64); x != f {
  171. t.Errorf("failed roundtrip %v => %s => %v", f, s, x)
  172. }
  173. }
  174. f32 := float32(f)
  175. if !math.IsInf(float64(f32), 0) {
  176. s := FormatFloat(float64(f32), 'e', -1, 32)
  177. if x, _ := ParseFloat(s, 32); float32(x) != f32 {
  178. t.Errorf("failed roundtrip %v => %s => %v", f32, s, float32(x))
  179. }
  180. }
  181. }
  182. }
  183. func TestFtoaRandom(t *testing.T) {
  184. N := int(1e4)
  185. if testing.Short() {
  186. N = 100
  187. }
  188. t.Logf("testing %d random numbers with fast and slow FormatFloat", N)
  189. for i := 0; i < N; i++ {
  190. bits := uint64(rand.Uint32())<<32 | uint64(rand.Uint32())
  191. x := math.Float64frombits(bits)
  192. shortFast := FormatFloat(x, 'g', -1, 64)
  193. SetOptimize(false)
  194. shortSlow := FormatFloat(x, 'g', -1, 64)
  195. SetOptimize(true)
  196. if shortSlow != shortFast {
  197. t.Errorf("%b printed as %s, want %s", x, shortFast, shortSlow)
  198. }
  199. prec := rand.Intn(12) + 5
  200. shortFast = FormatFloat(x, 'e', prec, 64)
  201. SetOptimize(false)
  202. shortSlow = FormatFloat(x, 'e', prec, 64)
  203. SetOptimize(true)
  204. if shortSlow != shortFast {
  205. t.Errorf("%b printed as %s, want %s", x, shortFast, shortSlow)
  206. }
  207. }
  208. }
  209. func TestFormatFloatInvalidBitSize(t *testing.T) {
  210. defer func() {
  211. if r := recover(); r == nil {
  212. t.Fatalf("expected panic due to invalid bitSize")
  213. }
  214. }()
  215. _ = FormatFloat(3.14, 'g', -1, 100)
  216. }
  217. var ftoaBenches = []struct {
  218. name string
  219. float float64
  220. fmt byte
  221. prec int
  222. bitSize int
  223. }{
  224. {"Decimal", 33909, 'g', -1, 64},
  225. {"Float", 339.7784, 'g', -1, 64},
  226. {"Exp", -5.09e75, 'g', -1, 64},
  227. {"NegExp", -5.11e-95, 'g', -1, 64},
  228. {"LongExp", 1.234567890123456e-78, 'g', -1, 64},
  229. {"Big", 123456789123456789123456789, 'g', -1, 64},
  230. {"BinaryExp", -1, 'b', -1, 64},
  231. {"32Integer", 33909, 'g', -1, 32},
  232. {"32ExactFraction", 3.375, 'g', -1, 32},
  233. {"32Point", 339.7784, 'g', -1, 32},
  234. {"32Exp", -5.09e25, 'g', -1, 32},
  235. {"32NegExp", -5.11e-25, 'g', -1, 32},
  236. {"32Shortest", 1.234567e-8, 'g', -1, 32},
  237. {"32Fixed8Hard", math.Ldexp(15961084, -125), 'e', 8, 32},
  238. {"32Fixed9Hard", math.Ldexp(14855922, -83), 'e', 9, 32},
  239. {"64Fixed1", 123456, 'e', 3, 64},
  240. {"64Fixed2", 123.456, 'e', 3, 64},
  241. {"64Fixed3", 1.23456e+78, 'e', 3, 64},
  242. {"64Fixed4", 1.23456e-78, 'e', 3, 64},
  243. {"64Fixed12", 1.23456e-78, 'e', 12, 64},
  244. {"64Fixed16", 1.23456e-78, 'e', 16, 64},
  245. // From testdata/testfp.txt
  246. {"64Fixed12Hard", math.Ldexp(6965949469487146, -249), 'e', 12, 64},
  247. {"64Fixed17Hard", math.Ldexp(8887055249355788, 665), 'e', 17, 64},
  248. {"64Fixed18Hard", math.Ldexp(6994187472632449, 690), 'e', 18, 64},
  249. // Trigger slow path (see issue #15672).
  250. // The shortest is: 8.034137530808823e+43
  251. {"Slowpath64", 8.03413753080882349e+43, 'e', -1, 64},
  252. // This denormal is pathological because the lower/upper
  253. // halfways to neighboring floats are:
  254. // 622666234635.321003e-320 ~= 622666234635.321e-320
  255. // 622666234635.321497e-320 ~= 622666234635.3215e-320
  256. // making it hard to find the 3rd digit
  257. {"SlowpathDenormal64", 622666234635.3213e-320, 'e', -1, 64},
  258. }
  259. func BenchmarkFormatFloat(b *testing.B) {
  260. for _, c := range ftoaBenches {
  261. b.Run(c.name, func(b *testing.B) {
  262. for i := 0; i < b.N; i++ {
  263. FormatFloat(c.float, c.fmt, c.prec, c.bitSize)
  264. }
  265. })
  266. }
  267. }
  268. func BenchmarkAppendFloat(b *testing.B) {
  269. dst := make([]byte, 30)
  270. for _, c := range ftoaBenches {
  271. b.Run(c.name, func(b *testing.B) {
  272. for i := 0; i < b.N; i++ {
  273. AppendFloat(dst[:0], c.float, c.fmt, c.prec, c.bitSize)
  274. }
  275. })
  276. }
  277. }