diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java index c005da4fc7..0d23394200 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java @@ -240,6 +240,15 @@ public enum Feature { * @since 2.5 */ IGNORE_UNKNOWN(false), + + /** + * Feature that determines whether to use standard Java code to write floats/doubles (default) or + * use the Schubfach algorithm which is faster. The latter approach may lead to small + * differences in the precision of the float/double that is written to the JSON output. + * + * @since 2.14 + */ + USE_FAST_DOUBLE_WRITER(false) ; private final boolean _defaultState; diff --git a/src/main/java/com/fasterxml/jackson/core/StreamWriteFeature.java b/src/main/java/com/fasterxml/jackson/core/StreamWriteFeature.java index 4f1998484e..7eda9d6052 100644 --- a/src/main/java/com/fasterxml/jackson/core/StreamWriteFeature.java +++ b/src/main/java/com/fasterxml/jackson/core/StreamWriteFeature.java @@ -110,6 +110,15 @@ public enum StreamWriteFeature * property will result in a {@link JsonProcessingException} */ IGNORE_UNKNOWN(JsonGenerator.Feature.IGNORE_UNKNOWN), + + /** + * Feature that determines whether to use standard Java code to write floats/doubles (default) or + * use the Schubfach algorithm which is faster. The latter approach may lead to small + * differences in the precision of the float/double that is written to the JSON output. + * + * @since 2.14 + */ + USE_FAST_DOUBLE_WRITER(JsonGenerator.Feature.USE_FAST_DOUBLE_WRITER) ; /** diff --git a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java index 187a0b4e4c..f8db6611d5 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java +++ b/src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java @@ -1,5 +1,8 @@ package com.fasterxml.jackson.core.io; +import com.fasterxml.jackson.core.io.schubfach.DoubleToDecimal; +import com.fasterxml.jackson.core.io.schubfach.FloatToDecimal; + public final class NumberOutput { private static int MILLION = 1000000; @@ -273,13 +276,41 @@ public static String toString(long v) { return Long.toString(v); } - public static String toString(double v) { - return Double.toString(v); + /** + * @param v double + * @return double as a string + */ + public static String toString(final double v) { + return toString(v, false); + } + + /** + * @param v double + * @param useFastWriter whether to use Schubfach algorithm to write output (default false) + * @return double as a string + * @since 2.14 + */ + public static String toString(final double v, final boolean useFastWriter) { + return useFastWriter ? DoubleToDecimal.toString(v) : Double.toString(v); } - // @since 2.6 - public static String toString(float v) { - return Float.toString(v); + /** + * @param v float + * @return float as a string + * @since 2.6 + */ + public static String toString(final float v) { + return toString(v, false); + } + + /** + * @param v float + * @param useFastWriter whether to use Schubfach algorithm to write output (default false) + * @return float as a string + * @since 2.14 + */ + public static String toString(final float v, final boolean useFastWriter) { + return useFastWriter ? FloatToDecimal.toString(v) : Float.toString(v); } /* diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java index 29ea90b972..34cf890a0c 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java @@ -1027,12 +1027,12 @@ public void writeNumber(double d) throws IOException if (_cfgNumbersAsStrings || (NumberOutput.notFinite(d) && Feature.QUOTE_NON_NUMERIC_NUMBERS.enabledIn(_features))) { - writeString(String.valueOf(d)); + writeString(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); return; } // What is the max length for doubles? 40 chars? _verifyValueWrite(WRITE_NUMBER); - writeRaw(String.valueOf(d)); + writeRaw(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); } @SuppressWarnings("deprecation") @@ -1042,12 +1042,12 @@ public void writeNumber(float f) throws IOException if (_cfgNumbersAsStrings || (NumberOutput.notFinite(f) && Feature.QUOTE_NON_NUMERIC_NUMBERS.enabledIn(_features))) { - writeString(String.valueOf(f)); + writeString(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); return; } // What is the max length for floats? _verifyValueWrite(WRITE_NUMBER); - writeRaw(String.valueOf(f)); + writeRaw(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); } @Override diff --git a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java index bf8c63eac7..765a7392b1 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java @@ -801,12 +801,12 @@ public void writeNumber(double d) throws IOException { if (_cfgNumbersAsStrings || (NumberOutput.notFinite(d) && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS))) { - writeString(String.valueOf(d)); + writeString(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); return; } // What is the max length for doubles? 40 chars? _verifyValueWrite(WRITE_NUMBER); - writeRaw(String.valueOf(d)); + writeRaw(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); } @SuppressWarnings("deprecation") @@ -815,12 +815,12 @@ public void writeNumber(float f) throws IOException { if (_cfgNumbersAsStrings || (NumberOutput.notFinite(f) && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS))) { - writeString(String.valueOf(f)); + writeString(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); return; } // What is the max length for floats? _verifyValueWrite(WRITE_NUMBER); - writeRaw(String.valueOf(f)); + writeRaw(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER))); } @Override diff --git a/src/test/java/com/fasterxml/jackson/core/write/ArrayGenerationTest.java b/src/test/java/com/fasterxml/jackson/core/write/ArrayGenerationTest.java index f4c7554067..9fc457cf04 100644 --- a/src/test/java/com/fasterxml/jackson/core/write/ArrayGenerationTest.java +++ b/src/test/java/com/fasterxml/jackson/core/write/ArrayGenerationTest.java @@ -13,7 +13,11 @@ public class ArrayGenerationTest extends BaseTest { private final JsonFactory FACTORY = new JsonFactory(); - + + protected JsonFactory jsonFactory() { + return FACTORY; + } + public void testIntArray() throws Exception { _testIntArray(false); @@ -124,8 +128,8 @@ private void _testIntArray(boolean useBytes, int elements, int pre, int post) th StringWriter sw = new StringWriter(); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes) - : FACTORY.createGenerator(sw); + JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes) + : jsonFactory().createGenerator(sw); gen.writeArray(values, pre, elements); gen.close(); @@ -137,8 +141,8 @@ private void _testIntArray(boolean useBytes, int elements, int pre, int post) th json = sw.toString(); } - JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray()) - : FACTORY.createParser(json); + JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray()) + : jsonFactory().createParser(json); assertToken(JsonToken.START_ARRAY, p.nextToken()); for (int i = 0; i < elements; ++i) { if ((i & 1) == 0) { // alternate @@ -165,8 +169,8 @@ private void _testLongArray(boolean useBytes, int elements, int pre, int post) t StringWriter sw = new StringWriter(); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes) - : FACTORY.createGenerator(sw); + JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes) + : jsonFactory().createGenerator(sw); gen.writeArray(values, pre, elements); gen.close(); @@ -178,8 +182,8 @@ private void _testLongArray(boolean useBytes, int elements, int pre, int post) t json = sw.toString(); } - JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray()) - : FACTORY.createParser(json); + JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray()) + : jsonFactory().createParser(json); assertToken(JsonToken.START_ARRAY, p.nextToken()); for (int i = 0; i < elements; ++i) { if ((i & 1) == 0) { // alternate @@ -206,8 +210,8 @@ private void _testDoubleArray(boolean useBytes, int elements, int pre, int post) StringWriter sw = new StringWriter(); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes) - : FACTORY.createGenerator(sw); + JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes) + : jsonFactory().createGenerator(sw); gen.writeArray(values, pre, elements); gen.close(); @@ -219,8 +223,8 @@ private void _testDoubleArray(boolean useBytes, int elements, int pre, int post) json = sw.toString(); } - JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray()) - : FACTORY.createParser(json); + JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray()) + : jsonFactory().createParser(json); assertToken(JsonToken.START_ARRAY, p.nextToken()); for (int i = 0; i < elements; ++i) { JsonToken t = p.nextToken(); @@ -248,8 +252,8 @@ private void _testStringArray(boolean useBytes, int elements, int pre, int post) StringWriter sw = new StringWriter(); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes) - : FACTORY.createGenerator(sw); + JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes) + : jsonFactory().createGenerator(sw); gen.writeArray(values, pre, elements); gen.close(); @@ -261,8 +265,8 @@ private void _testStringArray(boolean useBytes, int elements, int pre, int post) json = sw.toString(); } - JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray()) - : FACTORY.createParser(json); + JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray()) + : jsonFactory().createParser(json); assertToken(JsonToken.START_ARRAY, p.nextToken()); for (int i = 0; i < elements; ++i) { JsonToken t = p.nextToken(); diff --git a/src/test/java/com/fasterxml/jackson/core/write/FastDoubleArrayGenerationTest.java b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleArrayGenerationTest.java new file mode 100644 index 0000000000..b9ecbabd24 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleArrayGenerationTest.java @@ -0,0 +1,12 @@ +package com.fasterxml.jackson.core.write; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.StreamWriteFeature; + +public class FastDoubleArrayGenerationTest extends ArrayGenerationTest { + private final JsonFactory FACTORY = JsonFactory.builder().enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER).build(); + + protected JsonFactory jsonFactory() { + return FACTORY; + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/write/FastDoubleObjectWriteTest.java b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleObjectWriteTest.java new file mode 100644 index 0000000000..4b712307df --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/write/FastDoubleObjectWriteTest.java @@ -0,0 +1,12 @@ +package com.fasterxml.jackson.core.write; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.StreamWriteFeature; + +public class FastDoubleObjectWriteTest extends ObjectWriteTest { + private final JsonFactory FACTORY = JsonFactory.builder().enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER).build(); + + protected JsonFactory jsonFactory() { + return FACTORY; + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/write/ObjectWriteTest.java b/src/test/java/com/fasterxml/jackson/core/write/ObjectWriteTest.java index 15fc5f0e4e..371002149f 100644 --- a/src/test/java/com/fasterxml/jackson/core/write/ObjectWriteTest.java +++ b/src/test/java/com/fasterxml/jackson/core/write/ObjectWriteTest.java @@ -13,11 +13,17 @@ public class ObjectWriteTest extends BaseTest { + private final JsonFactory FACTORY = new JsonFactory(); + + protected JsonFactory jsonFactory() { + return FACTORY; + } + public void testEmptyObjectWrite() throws Exception { StringWriter sw = new StringWriter(); - JsonGenerator gen = new JsonFactory().createGenerator(sw); + JsonGenerator gen = jsonFactory().createGenerator(sw); JsonStreamContext ctxt = gen.getOutputContext(); assertTrue(ctxt.inRoot()); @@ -59,7 +65,7 @@ public void testInvalidObjectWrite() throws Exception { StringWriter sw = new StringWriter(); - JsonGenerator gen = new JsonFactory().createGenerator(sw); + JsonGenerator gen = jsonFactory().createGenerator(sw); gen.writeStartObject(); // Mismatch: try { @@ -75,7 +81,7 @@ public void testSimpleObjectWrite() throws Exception { StringWriter sw = new StringWriter(); - JsonGenerator gen = new JsonFactory().createGenerator(sw); + JsonGenerator gen = jsonFactory().createGenerator(sw); gen.writeStartObject(); gen.writeFieldName("first"); gen.writeNumber(-901); @@ -111,7 +117,7 @@ public void testConvenienceMethods() throws Exception { StringWriter sw = new StringWriter(); - JsonGenerator gen = new JsonFactory().createGenerator(sw); + JsonGenerator gen = jsonFactory().createGenerator(sw); gen.writeStartObject(); final String TEXT = "\"some\nString!\""; @@ -219,7 +225,7 @@ public void testConvenienceMethodsWithNulls() throws Exception { StringWriter sw = new StringWriter(); - JsonGenerator gen = new JsonFactory().createGenerator(sw); + JsonGenerator gen = jsonFactory().createGenerator(sw); gen.writeStartObject(); gen.writeStringField("str", null);