Skip to content

Commit 84dd12a

Browse files
authored
fast float/double writer option (#749)
1 parent 011e31f commit 84dd12a

File tree

9 files changed

+118
-35
lines changed

9 files changed

+118
-35
lines changed

src/main/java/com/fasterxml/jackson/core/JsonGenerator.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,15 @@ public enum Feature {
240240
* @since 2.5
241241
*/
242242
IGNORE_UNKNOWN(false),
243+
244+
/**
245+
* Feature that determines whether to use standard Java code to write floats/doubles (default) or
246+
* use the Schubfach algorithm which is faster. The latter approach may lead to small
247+
* differences in the precision of the float/double that is written to the JSON output.
248+
*
249+
* @since 2.14
250+
*/
251+
USE_FAST_DOUBLE_WRITER(false)
243252
;
244253

245254
private final boolean _defaultState;

src/main/java/com/fasterxml/jackson/core/StreamWriteFeature.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ public enum StreamWriteFeature
110110
* property will result in a {@link JsonProcessingException}
111111
*/
112112
IGNORE_UNKNOWN(JsonGenerator.Feature.IGNORE_UNKNOWN),
113+
114+
/**
115+
* Feature that determines whether to use standard Java code to write floats/doubles (default) or
116+
* use the Schubfach algorithm which is faster. The latter approach may lead to small
117+
* differences in the precision of the float/double that is written to the JSON output.
118+
*
119+
* @since 2.14
120+
*/
121+
USE_FAST_DOUBLE_WRITER(JsonGenerator.Feature.USE_FAST_DOUBLE_WRITER)
113122
;
114123

115124
/**

src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.fasterxml.jackson.core.io;
22

3+
import com.fasterxml.jackson.core.io.schubfach.DoubleToDecimal;
4+
import com.fasterxml.jackson.core.io.schubfach.FloatToDecimal;
5+
36
public final class NumberOutput
47
{
58
private static int MILLION = 1000000;
@@ -273,13 +276,41 @@ public static String toString(long v) {
273276
return Long.toString(v);
274277
}
275278

276-
public static String toString(double v) {
277-
return Double.toString(v);
279+
/**
280+
* @param v double
281+
* @return double as a string
282+
*/
283+
public static String toString(final double v) {
284+
return toString(v, false);
285+
}
286+
287+
/**
288+
* @param v double
289+
* @param useFastWriter whether to use Schubfach algorithm to write output (default false)
290+
* @return double as a string
291+
* @since 2.14
292+
*/
293+
public static String toString(final double v, final boolean useFastWriter) {
294+
return useFastWriter ? DoubleToDecimal.toString(v) : Double.toString(v);
278295
}
279296

280-
// @since 2.6
281-
public static String toString(float v) {
282-
return Float.toString(v);
297+
/**
298+
* @param v float
299+
* @return float as a string
300+
* @since 2.6
301+
*/
302+
public static String toString(final float v) {
303+
return toString(v, false);
304+
}
305+
306+
/**
307+
* @param v float
308+
* @param useFastWriter whether to use Schubfach algorithm to write output (default false)
309+
* @return float as a string
310+
* @since 2.14
311+
*/
312+
public static String toString(final float v, final boolean useFastWriter) {
313+
return useFastWriter ? FloatToDecimal.toString(v) : Float.toString(v);
283314
}
284315

285316
/*

src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,12 +1027,12 @@ public void writeNumber(double d) throws IOException
10271027
if (_cfgNumbersAsStrings ||
10281028
(NumberOutput.notFinite(d)
10291029
&& Feature.QUOTE_NON_NUMERIC_NUMBERS.enabledIn(_features))) {
1030-
writeString(String.valueOf(d));
1030+
writeString(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
10311031
return;
10321032
}
10331033
// What is the max length for doubles? 40 chars?
10341034
_verifyValueWrite(WRITE_NUMBER);
1035-
writeRaw(String.valueOf(d));
1035+
writeRaw(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
10361036
}
10371037

10381038
@SuppressWarnings("deprecation")
@@ -1042,12 +1042,12 @@ public void writeNumber(float f) throws IOException
10421042
if (_cfgNumbersAsStrings ||
10431043
(NumberOutput.notFinite(f)
10441044
&& Feature.QUOTE_NON_NUMERIC_NUMBERS.enabledIn(_features))) {
1045-
writeString(String.valueOf(f));
1045+
writeString(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
10461046
return;
10471047
}
10481048
// What is the max length for floats?
10491049
_verifyValueWrite(WRITE_NUMBER);
1050-
writeRaw(String.valueOf(f));
1050+
writeRaw(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
10511051
}
10521052

10531053
@Override

src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -801,12 +801,12 @@ public void writeNumber(double d) throws IOException
801801
{
802802
if (_cfgNumbersAsStrings ||
803803
(NumberOutput.notFinite(d) && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS))) {
804-
writeString(String.valueOf(d));
804+
writeString(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
805805
return;
806806
}
807807
// What is the max length for doubles? 40 chars?
808808
_verifyValueWrite(WRITE_NUMBER);
809-
writeRaw(String.valueOf(d));
809+
writeRaw(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
810810
}
811811

812812
@SuppressWarnings("deprecation")
@@ -815,12 +815,12 @@ public void writeNumber(float f) throws IOException
815815
{
816816
if (_cfgNumbersAsStrings ||
817817
(NumberOutput.notFinite(f) && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS))) {
818-
writeString(String.valueOf(f));
818+
writeString(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
819819
return;
820820
}
821821
// What is the max length for floats?
822822
_verifyValueWrite(WRITE_NUMBER);
823-
writeRaw(String.valueOf(f));
823+
writeRaw(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
824824
}
825825

826826
@Override

src/test/java/com/fasterxml/jackson/core/write/ArrayGenerationTest.java

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
public class ArrayGenerationTest extends BaseTest
1414
{
1515
private final JsonFactory FACTORY = new JsonFactory();
16-
16+
17+
protected JsonFactory jsonFactory() {
18+
return FACTORY;
19+
}
20+
1721
public void testIntArray() throws Exception
1822
{
1923
_testIntArray(false);
@@ -124,8 +128,8 @@ private void _testIntArray(boolean useBytes, int elements, int pre, int post) th
124128
StringWriter sw = new StringWriter();
125129
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
126130

127-
JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes)
128-
: FACTORY.createGenerator(sw);
131+
JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes)
132+
: jsonFactory().createGenerator(sw);
129133

130134
gen.writeArray(values, pre, elements);
131135
gen.close();
@@ -137,8 +141,8 @@ private void _testIntArray(boolean useBytes, int elements, int pre, int post) th
137141
json = sw.toString();
138142
}
139143

140-
JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray())
141-
: FACTORY.createParser(json);
144+
JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray())
145+
: jsonFactory().createParser(json);
142146
assertToken(JsonToken.START_ARRAY, p.nextToken());
143147
for (int i = 0; i < elements; ++i) {
144148
if ((i & 1) == 0) { // alternate
@@ -165,8 +169,8 @@ private void _testLongArray(boolean useBytes, int elements, int pre, int post) t
165169
StringWriter sw = new StringWriter();
166170
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
167171

168-
JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes)
169-
: FACTORY.createGenerator(sw);
172+
JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes)
173+
: jsonFactory().createGenerator(sw);
170174

171175
gen.writeArray(values, pre, elements);
172176
gen.close();
@@ -178,8 +182,8 @@ private void _testLongArray(boolean useBytes, int elements, int pre, int post) t
178182
json = sw.toString();
179183
}
180184

181-
JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray())
182-
: FACTORY.createParser(json);
185+
JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray())
186+
: jsonFactory().createParser(json);
183187
assertToken(JsonToken.START_ARRAY, p.nextToken());
184188
for (int i = 0; i < elements; ++i) {
185189
if ((i & 1) == 0) { // alternate
@@ -206,8 +210,8 @@ private void _testDoubleArray(boolean useBytes, int elements, int pre, int post)
206210
StringWriter sw = new StringWriter();
207211
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
208212

209-
JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes)
210-
: FACTORY.createGenerator(sw);
213+
JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes)
214+
: jsonFactory().createGenerator(sw);
211215

212216
gen.writeArray(values, pre, elements);
213217
gen.close();
@@ -219,8 +223,8 @@ private void _testDoubleArray(boolean useBytes, int elements, int pre, int post)
219223
json = sw.toString();
220224
}
221225

222-
JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray())
223-
: FACTORY.createParser(json);
226+
JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray())
227+
: jsonFactory().createParser(json);
224228
assertToken(JsonToken.START_ARRAY, p.nextToken());
225229
for (int i = 0; i < elements; ++i) {
226230
JsonToken t = p.nextToken();
@@ -248,8 +252,8 @@ private void _testStringArray(boolean useBytes, int elements, int pre, int post)
248252
StringWriter sw = new StringWriter();
249253
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
250254

251-
JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes)
252-
: FACTORY.createGenerator(sw);
255+
JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes)
256+
: jsonFactory().createGenerator(sw);
253257

254258
gen.writeArray(values, pre, elements);
255259
gen.close();
@@ -261,8 +265,8 @@ private void _testStringArray(boolean useBytes, int elements, int pre, int post)
261265
json = sw.toString();
262266
}
263267

264-
JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray())
265-
: FACTORY.createParser(json);
268+
JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray())
269+
: jsonFactory().createParser(json);
266270
assertToken(JsonToken.START_ARRAY, p.nextToken());
267271
for (int i = 0; i < elements; ++i) {
268272
JsonToken t = p.nextToken();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.fasterxml.jackson.core.write;
2+
3+
import com.fasterxml.jackson.core.JsonFactory;
4+
import com.fasterxml.jackson.core.StreamWriteFeature;
5+
6+
public class FastDoubleArrayGenerationTest extends ArrayGenerationTest {
7+
private final JsonFactory FACTORY = JsonFactory.builder().enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER).build();
8+
9+
protected JsonFactory jsonFactory() {
10+
return FACTORY;
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.fasterxml.jackson.core.write;
2+
3+
import com.fasterxml.jackson.core.JsonFactory;
4+
import com.fasterxml.jackson.core.StreamWriteFeature;
5+
6+
public class FastDoubleObjectWriteTest extends ObjectWriteTest {
7+
private final JsonFactory FACTORY = JsonFactory.builder().enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER).build();
8+
9+
protected JsonFactory jsonFactory() {
10+
return FACTORY;
11+
}
12+
}

src/test/java/com/fasterxml/jackson/core/write/ObjectWriteTest.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@
1313
public class ObjectWriteTest
1414
extends BaseTest
1515
{
16+
private final JsonFactory FACTORY = new JsonFactory();
17+
18+
protected JsonFactory jsonFactory() {
19+
return FACTORY;
20+
}
21+
1622
public void testEmptyObjectWrite()
1723
throws Exception
1824
{
1925
StringWriter sw = new StringWriter();
20-
JsonGenerator gen = new JsonFactory().createGenerator(sw);
26+
JsonGenerator gen = jsonFactory().createGenerator(sw);
2127

2228
JsonStreamContext ctxt = gen.getOutputContext();
2329
assertTrue(ctxt.inRoot());
@@ -59,7 +65,7 @@ public void testInvalidObjectWrite()
5965
throws Exception
6066
{
6167
StringWriter sw = new StringWriter();
62-
JsonGenerator gen = new JsonFactory().createGenerator(sw);
68+
JsonGenerator gen = jsonFactory().createGenerator(sw);
6369
gen.writeStartObject();
6470
// Mismatch:
6571
try {
@@ -75,7 +81,7 @@ public void testSimpleObjectWrite()
7581
throws Exception
7682
{
7783
StringWriter sw = new StringWriter();
78-
JsonGenerator gen = new JsonFactory().createGenerator(sw);
84+
JsonGenerator gen = jsonFactory().createGenerator(sw);
7985
gen.writeStartObject();
8086
gen.writeFieldName("first");
8187
gen.writeNumber(-901);
@@ -111,7 +117,7 @@ public void testConvenienceMethods()
111117
throws Exception
112118
{
113119
StringWriter sw = new StringWriter();
114-
JsonGenerator gen = new JsonFactory().createGenerator(sw);
120+
JsonGenerator gen = jsonFactory().createGenerator(sw);
115121
gen.writeStartObject();
116122

117123
final String TEXT = "\"some\nString!\"";
@@ -219,7 +225,7 @@ public void testConvenienceMethodsWithNulls()
219225
throws Exception
220226
{
221227
StringWriter sw = new StringWriter();
222-
JsonGenerator gen = new JsonFactory().createGenerator(sw);
228+
JsonGenerator gen = jsonFactory().createGenerator(sw);
223229
gen.writeStartObject();
224230

225231
gen.writeStringField("str", null);

0 commit comments

Comments
 (0)