This is an automated email from the ASF dual-hosted git repository.

mattjuntunen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-geometry.git

commit 79e741d806d04a009cd48401d882cb17a5c027cd
Author: Matthew Juntunen <[email protected]>
AuthorDate: Mon Jul 19 22:12:39 2021 -0400

    GEOMETRY-136: removing DoubleFormats utility that has been moved to 
commons-text
---
 .../jmh/io/core/DoubleFormatsPerformance.java      | 183 -----
 .../examples/jmh/io/core/package-info.java         |  23 -
 .../io/core/utils/AbstractTextFormatWriter.java    |   2 +-
 .../geometry/io/core/utils/DoubleFormats.java      | 369 ---------
 .../geometry/io/core/utils/ParsedDouble.java       | 514 ------------
 .../core/utils/AbstractTextFormatWriterTest.java   |   9 +-
 .../geometry/io/core/utils/DoubleFormatsTest.java  | 894 ---------------------
 .../geometry/io/core/utils/ParsedDoubleTest.java   | 464 -----------
 .../threed/obj/ObjBoundaryWriteHandler3D.java      |   3 +-
 .../txt/AbstractTextBoundaryWriteHandler3D.java    |   3 +-
 .../threed/obj/ObjBoundaryWriteHandler3DTest.java  |  30 +-
 .../io/euclidean/threed/obj/ObjWriterTest.java     |  20 +-
 .../io/euclidean/threed/stl/TextStlWriterTest.java |  11 +-
 .../threed/txt/TextBoundaryWriteHandler3DTest.java |  17 +-
 .../threed/txt/TextFacetDefinitionWriterTest.java  |  15 +-
 15 files changed, 81 insertions(+), 2476 deletions(-)

diff --git 
a/commons-geometry-examples/examples-jmh/src/main/java/org/apache/commons/geometry/examples/jmh/io/core/DoubleFormatsPerformance.java
 
b/commons-geometry-examples/examples-jmh/src/main/java/org/apache/commons/geometry/examples/jmh/io/core/DoubleFormatsPerformance.java
deleted file mode 100644
index dc83014..0000000
--- 
a/commons-geometry-examples/examples-jmh/src/main/java/org/apache/commons/geometry/examples/jmh/io/core/DoubleFormatsPerformance.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.examples.jmh.io.core;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.text.DecimalFormat;
-import java.util.concurrent.TimeUnit;
-import java.util.function.DoubleFunction;
-
-import org.apache.commons.geometry.examples.jmh.BenchmarkUtils;
-import org.apache.commons.geometry.io.core.utils.DoubleFormats;
-import org.apache.commons.rng.simple.RandomSource;
-import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.BenchmarkMode;
-import org.openjdk.jmh.annotations.Fork;
-import org.openjdk.jmh.annotations.Level;
-import org.openjdk.jmh.annotations.Measurement;
-import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.annotations.OutputTimeUnit;
-import org.openjdk.jmh.annotations.Param;
-import org.openjdk.jmh.annotations.Scope;
-import org.openjdk.jmh.annotations.Setup;
-import org.openjdk.jmh.annotations.State;
-import org.openjdk.jmh.annotations.Warmup;
-import org.openjdk.jmh.infra.Blackhole;
-
-/** Benchmarks for the {@link DoubleFormats} class.
- */
-@BenchmarkMode(Mode.AverageTime)
-@OutputTimeUnit(TimeUnit.NANOSECONDS)
-@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
-@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
-@Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx512M"})
-public class DoubleFormatsPerformance {
-
-    /** Benchmark input providing a source of random double values. */
-    @State(Scope.Thread)
-    public static class DoubleInput {
-
-        /** The number of doubles in the input array. */
-        @Param({"10000"})
-        private int size;
-
-        /** Minimum base 2 exponent for random input doubles. */
-        @Param("-20")
-        private int minExp;
-
-        /** Maximum base 2 exponent for random input doubles. */
-        @Param("20")
-        private int maxExp;
-
-        /** Double input array. */
-        private double[] input;
-
-        /** Get the input doubles.
-         * @return the input doubles
-         */
-        public double[] getInput() {
-            return input;
-        }
-
-        /** Set up the instance for the benchmark. */
-        @Setup(Level.Iteration)
-        public void setup() {
-            input = BenchmarkUtils.randomDoubleArray(size, minExp, maxExp,
-                    RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP));
-        }
-    }
-
-    /** Run a benchmark test on a function accepting a double argument.
-     * @param <T> function output type
-     * @param input double array
-     * @param bh jmh blackhole for consuming output
-     * @param fn function to call
-     */
-    private static <T> void runDoubleFunction(final DoubleInput input, final 
Blackhole bh,
-            final DoubleFunction<T> fn) {
-        for (final double d : input.getInput()) {
-            bh.consume(fn.apply(d));
-        }
-    }
-
-    /** Benchmark testing just the overhead of the benchmark harness.
-     * @param input benchmark state input
-     * @param bh jmh blackhole for consuming output
-     */
-    @Benchmark
-    public void baseline(final DoubleInput input, final Blackhole bh) {
-        runDoubleFunction(input, bh, d -> "");
-    }
-
-    /** Benchmark testing the {@link Double#toString()} method.
-     * @param input benchmark state input
-     * @param bh jmh blackhole for consuming output
-     */
-    @Benchmark
-    public void doubleToString(final DoubleInput input, final Blackhole bh) {
-        runDoubleFunction(input, bh, Double::toString);
-    }
-
-    /** Benchmark testing the {@link String#format(String, Object...)} method.
-     * @param input benchmark state input
-     * @param bh jmh blackhole for consuming output
-     */
-    @Benchmark
-    public void stringFormat(final DoubleInput input, final Blackhole bh) {
-        runDoubleFunction(input, bh, d -> String.format("%d", d));
-    }
-
-    /** Benchmark testing the BigDecimal formatting performance.
-     * @param input benchmark state input
-     * @param bh jmh blackhole for consuming output
-     */
-    @Benchmark
-    public void bigDecimal(final DoubleInput input, final Blackhole bh) {
-        final DoubleFunction<String> fn = d -> BigDecimal.valueOf(d)
-                .setScale(3, RoundingMode.HALF_EVEN)
-                .stripTrailingZeros()
-                .toString();
-        runDoubleFunction(input, bh, fn);
-    }
-
-    /** Benchmark testing the {@link DecimalFormat} class.
-     * @param input benchmark state input
-     * @param bh jmh blackhole for consuming output
-     */
-    @Benchmark
-    public void decimalFormat(final DoubleInput input, final Blackhole bh) {
-        final DecimalFormat fmt = new DecimalFormat("0.###");
-        runDoubleFunction(input, bh, fmt::format);
-    }
-
-    /** Benchmark testing the {@link DoubleFormats#createDefault(int, int)} 
method.
-     * @param input benchmark state input
-     * @param bh jmh blackhole for consuming output
-     */
-    @Benchmark
-    public void doubleFormatsDefault(final DoubleInput input, final Blackhole 
bh) {
-        runDoubleFunction(input, bh, DoubleFormats.createDefault(0, -3));
-    }
-
-    /** Benchmark testing the {@link DoubleFormats#createPlain(int, int)} 
method.
-     * @param input benchmark state input
-     * @param bh jmh blackhole for consuming output
-     */
-    @Benchmark
-    public void doubleFormatsPlain(final DoubleInput input, final Blackhole 
bh) {
-        runDoubleFunction(input, bh, DoubleFormats.createPlain(0, -3));
-    }
-
-    /** Benchmark testing the {@link DoubleFormats#createScientific(int, int)} 
method.
-     * @param input benchmark state input
-     * @param bh jmh blackhole for consuming output
-     */
-    @Benchmark
-    public void doubleFormatsScientific(final DoubleInput input, final 
Blackhole bh) {
-        runDoubleFunction(input, bh, DoubleFormats.createScientific(0, -3));
-    }
-
-    /** Benchmark testing the {@link DoubleFormats#createEngineering(int, 
int)} method.
-     * @param input benchmark state input
-     * @param bh jmh blackhole for consuming output
-     */
-    @Benchmark
-    public void doubleFormatsEngineering(final DoubleInput input, final 
Blackhole bh) {
-        runDoubleFunction(input, bh, DoubleFormats.createEngineering(0, -3));
-    }
-}
diff --git 
a/commons-geometry-examples/examples-jmh/src/main/java/org/apache/commons/geometry/examples/jmh/io/core/package-info.java
 
b/commons-geometry-examples/examples-jmh/src/main/java/org/apache/commons/geometry/examples/jmh/io/core/package-info.java
deleted file mode 100644
index 6f2beb2..0000000
--- 
a/commons-geometry-examples/examples-jmh/src/main/java/org/apache/commons/geometry/examples/jmh/io/core/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Benchmarks for the components in the {@code 
org.apache.commons.geometry.io.core}
- * package.
- */
-
-package org.apache.commons.geometry.examples.jmh.io.core;
diff --git 
a/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/utils/AbstractTextFormatWriter.java
 
b/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/utils/AbstractTextFormatWriter.java
index fdd94c0..f4eb918 100644
--- 
a/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/utils/AbstractTextFormatWriter.java
+++ 
b/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/utils/AbstractTextFormatWriter.java
@@ -42,7 +42,7 @@ public abstract class AbstractTextFormatWriter implements 
Closeable {
      * @param writer writer instance
      */
     protected AbstractTextFormatWriter(final Writer writer) {
-        this(writer, DoubleFormats.DOUBLE_TO_STRING);
+        this(writer, Double::toString);
     }
 
     /** Construct a new instance that writes content to the given writer and 
uses the
diff --git 
a/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/utils/DoubleFormats.java
 
b/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/utils/DoubleFormats.java
deleted file mode 100644
index 62768b8..0000000
--- 
a/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/utils/DoubleFormats.java
+++ /dev/null
@@ -1,369 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.io.core.utils;
-
-import java.util.function.DoubleFunction;
-
-/** Class containing static utility methods and constants for formatting 
double values
- * as strings. All instances returned by methods in this class are guaranteed 
to be
- * thread-safe.
- */
-public final class DoubleFormats {
-
-    /** Double format function that simply calls {@link 
Double#toString(double)}.
-     */
-    public static final DoubleFunction<String> DOUBLE_TO_STRING = 
Double::toString;
-
-    /** Double format function that converts the argument to a float and calls
-     * {@link Float#toString(float)}.
-     */
-    public static final DoubleFunction<String> FLOAT_TO_STRING = d -> 
Float.toString((float) d);
-
-    /** Minimum possible decimal exponent for double values. */
-    private static final int MIN_DOUBLE_EXPONENT = -325;
-
-    /** Utility class; no instantiation. */
-    private DoubleFormats() {}
-
-    /** Return a double format function that provides similar behavior to 
{@link Double#toString(double)}
-     * but with a configurable max precision. For values with an absolute 
magnitude less than
-     * 10<sup>7</sup> and greater than or equal to 10<sup>-3</sup> (after any 
necessary rounding), the returned
-     * string is in plain, non-scientific format. All other values are in 
scientific format. Rounding is performed
-     * using {@link java.math.RoundingMode#HALF_EVEN half even} rounding.
-     * <table>
-     *  <caption>Format Examples</caption>
-     *  <tr><th>Value</th><th>(maxPrecision= 0)</th><th>(maxPrecision= 
4)</th></tr>
-     *  <tr><td>1.0E-4</td><td>1.0E-4</td><td>1.0E-4</td></tr>
-     *  <tr><td>-0.0635</td><td>-0.0635</td><td>-0.0635</td></tr>
-     *  <tr><td>510.751</td><td>510.751</td><td>510.8</td></tr>
-     *  <tr><td>-123456.0</td><td>-123456.0</td><td>-123500.0</td></tr>
-     *  <tr><td>4.20785E7</td><td>4.20785E7</td><td>4.208E7</td></tr>
-     * </table>
-     * @param maxPrecision Maximum number of significant decimal digits in 
strings produced by the returned formatter.
-     *      Numbers are rounded as necessary so that the number of significant 
digits does not exceed this value. A
-     *      value of {@code 0} indicates no maximum precision.
-     * @return double format function
-     * @throws IllegalArgumentException if {@code maxPrecision} is less than 
zero
-     */
-    public static DoubleFunction<String> createDefault(final int maxPrecision) 
{
-        return createDefault(maxPrecision, MIN_DOUBLE_EXPONENT);
-    }
-
-    /** Return a double format function that provides similar behavior to 
{@link Double#toString(double)}
-     * but with a configurable max precision and min exponent. For values with 
an absolute magnitude less than
-     * 10<sup>7</sup> and greater than or equal to 10<sup>-3</sup> (after any 
necessary rounding), the returned
-     * string is in plain, non-scientific format. All other values are in 
scientific format. Rounding is performed
-     * using {@link java.math.RoundingMode#HALF_EVEN half even} rounding.
-     * <table>
-     *  <caption>Format Examples</caption>
-     *  <tr><th>Value</th><th>(maxPrecision= 0, minExponent= 
-2)</th><th>(maxPrecision= 4, minExponent= -2)</th></tr>
-     *  <tr><td>1.0E-4</td><td>0.0</td><td>0.0</td></tr>
-     *  <tr><td>-0.0635</td><td>-0.06</td><td>-0.06</td></tr>
-     *  <tr><td>510.751</td><td>510.75</td><td>510.8</td></tr>
-     *  <tr><td>-123456.0</td><td>-123456.0</td><td>-123500.0</td></tr>
-     *  <tr><td>4.20785E7</td><td>4.20785E7</td><td>4.208E7</td></tr>
-     * </table>
-     * @param maxPrecision Maximum number of significant decimal digits in 
strings produced by the returned formatter.
-     *      Numbers are rounded as necessary so that the number of significant 
digits does not exceed this value. A
-     *      value of {@code 0} indicates no maximum precision.
-     * @param minExponent Minimum decimal exponent in strings produced by the 
returned formatter.
-     * @return double format function
-     * @throws IllegalArgumentException if {@code maxPrecision} is less than 
zero
-     */
-    public static DoubleFunction<String> createDefault(final int maxPrecision, 
final int minExponent) {
-        return new DefaultFormat(maxPrecision, minExponent);
-    }
-
-    /** Return a double format function that produces strings in plain, 
non-scientific format.
-     * Rounding is performed using {@link java.math.RoundingMode#HALF_EVEN 
half even} rounding.
-     * <table>
-     *  <caption>Format Examples</caption>
-     *  <tr><th>Value</th><th>(maxPrecision= 0)</th><th>(maxPrecision= 
4)</th></tr>
-     *  <tr><td>1.0E-4</td><td>0.0001</td><td>0.0001</td></tr>
-     *  <tr><td>-0.0635</td><td>-0.0635</td><td>-0.0635</td></tr>
-     *  <tr><td>510.751</td><td>510.751</td><td>510.8</td></tr>
-     *  <tr><td>-123456.0</td><td>-123456.0</td><td>-123500.0</td></tr>
-     *  <tr><td>4.20785E7</td><td>42078500.0</td><td>42080000.0</td></tr>
-     * </table>
-     * @param maxPrecision Maximum number of significant decimal digits in 
strings produced by the returned formatter.
-     *      Numbers are rounded as necessary so that the number of significant 
digits does not exceed this value. A
-     *      value of {@code 0} indicates no maximum precision.
-     * @return double format function
-     */
-    public static DoubleFunction<String> createPlain(final int maxPrecision) {
-        return createPlain(maxPrecision, MIN_DOUBLE_EXPONENT);
-    }
-
-    /** Return a double format function that produces strings in plain, 
non-scientific format.
-     * Rounding is performed using {@link java.math.RoundingMode#HALF_EVEN 
half even} rounding.
-     * <table>
-     *  <caption>Format Examples</caption>
-     *  <tr><th>Value</th><th>(maxPrecision= 0, minExponent= 
-2)</th><th>(maxPrecision= 4, minExponent= -2)</th></tr>
-     *  <tr><td>1.0E-4</td><td>0.0</td><td>0.0</td></tr>
-     *  <tr><td>-0.0635</td><td>-0.06</td><td>-0.06</td></tr>
-     *  <tr><td>510.751</td><td>510.75</td><td>510.8</td></tr>
-     *  <tr><td>-123456.0</td><td>-123456.0</td><td>-123500.0</td></tr>
-     *  <tr><td>4.20785E7</td><td>42078500.0</td><td>42080000.0</td></tr>
-     * </table>
-     * @param maxPrecision Maximum number of significant decimal digits in 
strings produced by the returned formatter.
-     *      Numbers are rounded as necessary so that the number of significant 
digits does not exceed this value. A
-     *      value of {@code 0} indicates no maximum precision.
-     * @param minExponent Minimum decimal exponent in strings produced by the 
returned formatter.
-     * @return double format function
-     */
-    public static DoubleFunction<String> createPlain(final int maxPrecision, 
final int minExponent) {
-        return new PlainFormat(maxPrecision, minExponent);
-    }
-
-    /** Return a double format function that produces strings in scientific 
format. Exponents of
-     * zero are not included in formatted strings. Rounding is performed using
-     * {@link java.math.RoundingMode#HALF_EVEN half even} rounding.
-     * <table>
-     *  <caption>Format Examples</caption>
-     *  <tr><th>Value</th><th>(maxPrecision= 0)</th><th>(maxPrecision= 
4)</th></tr>
-     *  <tr><td>1.0E-4</td><td>1.0E-4</td><td>1.0E-4</td></tr>
-     *  <tr><td>-0.0635</td><td>-6.35E-2</td><td>-6.35E-2</td></tr>
-     *  <tr><td>510.751</td><td>5.10751E2</td><td>5.108E2</td></tr>
-     *  <tr><td>-123456.0</td><td>-1.23456E5</td><td>-1.235E5</td></tr>
-     *  <tr><td>4.20785E7</td><td>4.20785E7</td><td>4.208E7</td></tr>
-     * </table>
-     * @param maxPrecision Maximum number of significant decimal digits in 
strings produced by the returned formatter.
-     *      Numbers are rounded as necessary so that the number of significant 
digits does not exceed this value. A
-     *      value of {@code 0} indicates no maximum precision.
-     * @return double format function
-     */
-    public static DoubleFunction<String> createScientific(final int 
maxPrecision) {
-        return createScientific(maxPrecision, MIN_DOUBLE_EXPONENT);
-    }
-
-    /** Return a double format function that produces strings in scientific 
format. Exponents of
-     * zero are not included in formatted strings. Rounding is performed using
-     * {@link java.math.RoundingMode#HALF_EVEN half even} rounding.
-     * <table>
-     *  <caption>Format Examples</caption>
-     *  <tr><th>Value</th><th>(maxPrecision= 0, minExponent= 
-2)</th><th>(maxPrecision= 4, minExponent= -2)</th></tr>
-     *  <tr><td>1.0E-4</td><td>0.0</td><td>0.0</td></tr>
-     *  <tr><td>-0.0635</td><td>-6.0E-2</td><td>-6.0E-2</td></tr>
-     *  <tr><td>510.751</td><td>5.1075E2</td><td>5.108E2</td></tr>
-     *  <tr><td>-123456.0</td><td>-1.23456E5</td><td>-1.235E5</td></tr>
-     *  <tr><td>4.20785E7</td><td>4.20785E7</td><td>4.208E7</td></tr>
-     * </table>
-     * @param maxPrecision Maximum number of significant decimal digits in 
strings produced by the returned formatter.
-     *      Numbers are rounded as necessary so that the number of significant 
digits does not exceed this value. A
-     *      value of {@code 0} indicates no maximum precision.
-     * @param minExponent Minimum decimal exponent in strings produced by the 
returned formatter.
-     * @return double format function
-     */
-    public static DoubleFunction<String> createScientific(final int 
maxPrecision, final int minExponent) {
-        return new ScientificFormat(maxPrecision, minExponent);
-    }
-
-    /** Return a double format function that produces strings in
-     * <a 
href="https://en.wikipedia.org/wiki/Engineering_notation";>engineering 
notation</a> where any exponents
-     * are adjusted to be multiples of 3. Exponents of zero are not included 
in formatted strings. Rounding is
-     * performed using {@link java.math.RoundingMode#HALF_EVEN half even} 
rounding.
-     * <table>
-     *  <caption>Format Examples</caption>
-     *  <tr><th>Value</th><th>(maxPrecision= 0)</th><th>(maxPrecision= 
4)</th></tr>
-     *  <tr><td>1.0E-4</td><td>100.0E-6</td><td>100.0E-6</td></tr>
-     *  <tr><td>-0.0635</td><td>-63.5E-3</td><td>-63.5E-3</td></tr>
-     *  <tr><td>510.751</td><td>510.751</td><td>510.8</td></tr>
-     *  <tr><td>-123456.0</td><td>-123.456E3</td><td>-123.5E3</td></tr>
-     *  <tr><td>4.20785E7</td><td>42.0785E6</td><td>42.08E6</td></tr>
-     * </table>
-     * @param maxPrecision Maximum number of significant decimal digits in 
strings produced by the returned formatter.
-     *      Numbers are rounded as necessary so that the number of significant 
digits does not exceed this value. A
-     *      value of {@code 0} indicates no maximum precision.
-     * @return double format function
-     */
-    public static DoubleFunction<String> createEngineering(final int 
maxPrecision) {
-        return createEngineering(maxPrecision, MIN_DOUBLE_EXPONENT);
-    }
-
-    /** Return a double format function that produces strings in
-     * <a 
href="https://en.wikipedia.org/wiki/Engineering_notation";>engineering 
notation</a>, where exponents
-     * are adjusted to be multiples of 3. Exponents of zero are not included 
in formatted strings. Rounding is
-     * performed using {@link java.math.RoundingMode#HALF_EVEN half even} 
rounding.
-     * <table>
-     *  <caption>Format Examples</caption>
-     *  <tr><th>Value</th><th>(maxPrecision= 0, minExponent= 
-2)</th><th>(maxPrecision= 4, minExponent= -2)</th></tr>
-     *  <tr><td>1.0E-4</td><td>0.0</td><td>0.0</td></tr>
-     *  <tr><td>-0.0635</td><td>-60.0E-3</td><td>-60.0E-3</td></tr>
-     *  <tr><td>510.751</td><td>510.75</td><td>510.8</td></tr>
-     *  <tr><td>-123456.0</td><td>-123.456E3</td><td>-123.5E3</td></tr>
-     *  <tr><td>4.20785E7</td><td>42.0785E6</td><td>42.08E6</td></tr>
-     * </table>
-     * @param maxPrecision Maximum number of significant decimal digits in 
strings produced by the returned formatter.
-     *      Numbers are rounded as necessary so that the number of significant 
digits does not exceed this value. A
-     *      value of {@code 0} indicates no maximum precision.
-     * @param minExponent Minimum decimal exponent in strings produced by the 
returned formatter.
-     * @return double format function
-     */
-    public static DoubleFunction<String> createEngineering(final int 
maxPrecision, final int minExponent) {
-        return new EngineeringFormat(maxPrecision, minExponent);
-    }
-
-    /** Base class for standard double formatting classes.
-     */
-    private abstract static class AbstractFormat implements 
DoubleFunction<String> {
-
-        /** Maximum precision to use when formatting values. */
-        private final int maxPrecision;
-
-        /** The minimum exponent to allow in the result. Value with exponents 
less than this are
-         * rounded to positive zero.
-         */
-        private final int minExponent;
-
-        /** Construct a new instance with the given maximum precision and 
minimum exponent.
-         * @param maxPrecision maximum number of significant decimal digits
-         * @param minExponent minimum decimal exponent; values less than this 
that do not round up
-         *      are considered to be zero
-         * @throws IllegalArgumentException if {@code maxPrecision} is less 
than zero
-         */
-        AbstractFormat(final int maxPrecision, final int minExponent) {
-            if (maxPrecision < 0) {
-                throw new IllegalArgumentException(
-                        "Max precision must be greater than or equal to zero; 
was " + maxPrecision);
-            }
-
-            this.maxPrecision = maxPrecision;
-            this.minExponent = minExponent;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public String apply(final double d) {
-            if (Double.isFinite(d)) {
-                final ParsedDouble n = ParsedDouble.from(d);
-
-                int roundExponent = Math.max(n.getExponent(), minExponent);
-                if (maxPrecision > 0) {
-                    roundExponent = Math.max(n.getScientificExponent() - 
maxPrecision + 1, roundExponent);
-                }
-
-                final ParsedDouble rounded = n.round(roundExponent);
-
-                return formatInternal(rounded);
-            }
-
-            return Double.toString(d); // NaN or infinite; use default Double 
toString() method
-        }
-
-        /** Format the given parsed double value.
-         * @param val value to format
-         * @return formatted double value
-         */
-        protected abstract String formatInternal(ParsedDouble val);
-    }
-
-    /** Format class that produces plain decimal strings that do not use
-     * scientific notation.
-     */
-    private static class PlainFormat extends AbstractFormat {
-
-        /** Construct a new instance with the given maximum precision and 
minimum exponent.
-         * @param maxPrecision maximum number of significant decimal digits
-         * @param minExponent minimum decimal exponent; values less than this 
that do not round up
-         *      are considered to be zero
-         * @throws IllegalArgumentException if {@code maxPrecision} is less 
than zero
-         */
-        PlainFormat(final int maxPrecision, final int minExponent) {
-            super(maxPrecision, minExponent);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        protected String formatInternal(final ParsedDouble val) {
-            return val.toPlainString(true);
-        }
-    }
-
-    /** Format class producing results similar to {@link Double#toString()}, 
with
-     * plain decimal notation for small numbers relatively close to zero and 
scientific
-     * notation otherwise.
-     */
-    private static class DefaultFormat extends AbstractFormat {
-
-        /** Decimal exponent upper bound for use of plain formatted strings. */
-        private static final int UPPER_PLAIN_EXP = 7;
-
-        /** Decimal exponent lower bound for use of plain formatted strings. */
-        private static final int LOWER_PLAIN_EXP = -4;
-
-        /** Construct a new instance with the given maximum precision and 
minimum exponent.
-         * @param maxPrecision maximum number of significant decimal digits
-         * @param minExponent minimum decimal exponent; values less than this 
that do not round up
-         *      are considered to be zero
-         * @throws IllegalArgumentException if {@code maxPrecision} is less 
than zero
-         */
-        DefaultFormat(final int maxPrecision, final int minExponent) {
-            super(maxPrecision, minExponent);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        protected String formatInternal(final ParsedDouble val) {
-            final int sciExp = val.getScientificExponent();
-            return sciExp < UPPER_PLAIN_EXP && sciExp > LOWER_PLAIN_EXP ?
-                    val.toPlainString(true) :
-                    val.toScientificString(true);
-        }
-    }
-
-    /** Format class that uses scientific notation for all values.
-     */
-    private static class ScientificFormat extends AbstractFormat {
-
-        /** Construct a new instance with the given maximum precision and 
minimum exponent.
-         * @param maxPrecision maximum number of significant decimal digits
-         * @param minExponent minimum decimal exponent; values less than this 
that do not round up
-         *      are considered to be zero
-         * @throws IllegalArgumentException if {@code maxPrecision} is less 
than zero
-         */
-        ScientificFormat(final int maxPrecision, final int minExponent) {
-            super(maxPrecision, minExponent);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public String formatInternal(final ParsedDouble val) {
-            return val.toScientificString(true);
-        }
-    }
-
-    /** Format class that uses engineering notation for all values.
-     */
-    private static class EngineeringFormat extends AbstractFormat {
-
-        /** Construct a new instance with the given maximum precision and 
minimum exponent.
-         * @param maxPrecision maximum number of significant decimal digits
-         * @param minExponent minimum decimal exponent; values less than this 
that do not round up
-         *      are considered to be zero
-         * @throws IllegalArgumentException if {@code maxPrecision} is less 
than zero
-         */
-        EngineeringFormat(final int maxPrecision, final int minExponent) {
-            super(maxPrecision, minExponent);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public String formatInternal(final ParsedDouble val) {
-            return val.toEngineeringString(true);
-        }
-    }
-}
diff --git 
a/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/utils/ParsedDouble.java
 
b/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/utils/ParsedDouble.java
deleted file mode 100644
index d6d4057..0000000
--- 
a/commons-geometry-io-core/src/main/java/org/apache/commons/geometry/io/core/utils/ParsedDouble.java
+++ /dev/null
@@ -1,514 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.io.core.utils;
-
-/** Simple class representing a double value parsed into separate decimal 
components. Each double
- * is represented with
- * <ul>
- *  <li>a boolean flag for the sign,</li>
- *  <li> a sequence of the digits '0' - '10' representing an unsigned integer 
with leading and trailing zeros
- *      removed, and</li>
- *  <li>an exponent value that when applied to the base 10 digits produces a 
floating point value with the
- *      correct magnitude.</li>
- * </ul>
- * <p><strong>Examples</strong></p>
- * <table>
- *  <tr><th>Double</th><th>Negative</th><th>Digits</th><th>Exponent</th></tr>
- *  <tr><td>0.0</td><td>false</td><td>"0"</td><td>0</td></tr>
- *  <tr><td>1.2</td><td>false</td><td>"12"</td><td>-1</td></tr>
- *  <tr><td>-0.00971</td><td>true</td><td>"971"</td><td>-5</td></tr>
- *  <tr><td>56300</td><td>true</td><td>"563"</td><td>2</td></tr>
- * </table>
- */
-final class ParsedDouble {
-
-    /** Minus sign character. */
-    private static final char MINUS_CHAR = '-';
-
-    /** Plus sign character. */
-    private static final char PLUS_CHAR = '+';
-
-    /** Decimal separator character. */
-    private static final char DECIMAL_SEP_CHAR = '.';
-
-    /** Exponent character. */
-    private static final char EXPONENT_CHAR = 'E';
-
-    /** Zero digit character. */
-    private static final char ZERO_CHAR = '0';
-
-    /** One digit character. */
-    private static final char ONE_CHAR = '1';
-
-    /** String containing the decimal digits '0' - '9' in sequence. */
-    private static final String DECIMAL_DIGITS = "0123456789";
-
-    /** Initial size to use for string builder instances. */
-    private static final int INITIAL_STR_BUILDER_SIZE = 32;
-
-    /** Shared instance representing the positive zero double value. */
-    private static final ParsedDouble POS_ZERO = new ParsedDouble(false, 
String.valueOf(ZERO_CHAR), 0);
-
-    /** Shared instance representing the negative zero double value. */
-    private static final ParsedDouble NEG_ZERO = new ParsedDouble(true, 
String.valueOf(ZERO_CHAR), 0);
-
-    /** True if the value is negative. */
-    private final boolean negative;
-
-    /** String containing the significant base-10 digits for the value. */
-    private final String digits;
-
-    /** Exponent for the value. */
-    private final int exponent;
-
-    /** Construct a new instance from its parts.
-     * @param negative true if the value is negative
-     * @param digits string containing significant digits
-     * @param exponent exponent value
-     */
-    ParsedDouble(final boolean negative, final String digits, final int 
exponent) {
-        this.negative = negative;
-        this.digits = digits;
-        this.exponent = exponent;
-    }
-
-    /** Return true if the value is negative.
-     * @return true if the value is negative
-     */
-    public boolean isNegative() {
-        return negative;
-    }
-
-    /** Get a string containing the significant digits of the value. If the 
value is
-     * {@code 0}, then the returned string is {@code "0"}. Otherwise, the 
string contains
-     * one or more characters with the first and last characters not equal to 
{@code '0'}.
-     * @return string containing the significant digits of the value
-     */
-    public String getDigits() {
-        return digits;
-    }
-
-    /** Get the exponent value. This exponent produces a floating point value 
with the
-     * correct magnitude when applied to the unsigned integer represented by 
the {@link #getDigits() digit}
-     * string.
-     * @return exponent value
-     */
-    public int getExponent() {
-        return exponent;
-    }
-
-    /** Return true if the value is equal to zero. The sign field is ignored,
-     * meaning that this method will return true for both {@code +0} and 
{@code -0}.
-     * @return true if the value is equal to zero
-     */
-    public boolean isZero() {
-        return getPrecision() == 1 && digits.charAt(0) == ZERO_CHAR;
-    }
-
-    /** Return the precision of this instance, meaning the number of 
significant decimal
-     * digits in the representation.
-     * @return the precision of this instance
-     */
-    public int getPrecision() {
-        return digits.length();
-    }
-
-    /** Get the exponent that would be used when representing this number in 
scientific
-     * notation (i.e., with a single non-zero digit in front of the decimal 
point.
-     * @return the exponent that would be used when representing this number 
in scientific
-     *      notation
-     */
-    public int getScientificExponent() {
-        return getPrecision() + exponent - 1;
-    }
-
-    /** Round the instance to the given decimal exponent position using
-     * {@link java.math.RoundingMode#HALF_EVEN half-even rounding}. For 
example, a value of {@code -2}
-     * will round the instance to the digit at the position 10<sup>-2</sup> 
(i.e. to the closest multiple of 0.01).
-     * A new instance is returned if the rounding operation results in a new 
value.
-     * @param roundExponent exponent defining the decimal place to round to
-     * @return result of the rounding operation
-     */
-    public ParsedDouble round(final int roundExponent) {
-        if (roundExponent > exponent) {
-            final int precision = getPrecision();
-            final int max = precision + exponent;
-
-            if (roundExponent < max) {
-                return maxPrecision(max - roundExponent);
-            } else if (roundExponent == max && shouldRoundUp(0)) {
-                return new ParsedDouble(negative, "1", roundExponent);
-            }
-
-            return POS_ZERO;
-        }
-
-        return this;
-    }
-
-    /** Return the value as close as possible to this instance with <em>at 
most</em> the given number
-     * of significant digits (i.e. precision). If this instance already has a 
precision less than or equal
-     * to the argument, it is returned directly. If the given precision 
requires a reduction in the number
-     * of digits, then the value is rounded using {@link 
java.math.RoundingMode#HALF_EVEN half-even rounding}
-     * and a new instance is returned with the rounded value.
-     * @param precision maximum number of significant digits to include
-     * @return the instance as close as possible to this value with at most 
the given number of
-     *      significant digits
-     * @throws IllegalArgumentException if {@code precision} is less than 1
-     */
-    public ParsedDouble maxPrecision(final int precision) {
-        if (precision < 1) {
-            throw new IllegalArgumentException("Precision must be greater than 
zero; was " + precision);
-        }
-
-        final int currentPrecision = getPrecision();
-        if (currentPrecision > precision) {
-            // we need to round to reduce the number of digits
-            String resultDigits = shouldRoundUp(precision) ?
-                    addOne(digits, precision) :
-                    digits.substring(0, precision);
-
-            // compute the initial result exponent
-            int resultExponent = exponent + (currentPrecision - precision);
-
-            // remove zeros from the end of the integer if present, adjusting 
the
-            // exponent as needed
-            final int lastNonZeroIdx = findLastNonZero(resultDigits);
-            if (lastNonZeroIdx < resultDigits.length() - 1) {
-                resultExponent += resultDigits.length() - 1 - lastNonZeroIdx;
-                resultDigits = resultDigits.substring(0, lastNonZeroIdx + 1);
-            }
-
-            return new ParsedDouble(negative, resultDigits, resultExponent);
-        }
-
-        return this; // no change needed
-    }
-
-    /** Return a string representation of the value with no exponent field. Ex:
-     * <pre>
-     * 10 = "10.0"
-     * 1e-6 = "0.000001"
-     * 1e11 = "100000000000.0"
-     * </pre>
-     * @param includeDecimalPlaceholder if true, then the returned string will 
contain
-     *      the decimal placeholder ".0" when no fractional value is present, 
similar
-     *      to {@link Double#toString(double)}
-     * @return a string representation of the value with no exponent field
-     */
-    public String toPlainString(final boolean includeDecimalPlaceholder) {
-        final int precision = getPrecision();
-
-        final StringBuilder sb = new StringBuilder(INITIAL_STR_BUILDER_SIZE);
-        if (negative) {
-            sb.append(MINUS_CHAR);
-        }
-
-        if (exponent < 0) {
-            final int diff = precision + exponent;
-
-            // add whole digits, using a beginning placeholder zero if
-            // needed
-            int i;
-            for (i = 0; i < diff; ++i) {
-                sb.append(digits.charAt(i));
-            }
-            if (i == 0) {
-                sb.append(ZERO_CHAR);
-            }
-
-            // decimal separator
-            sb.append(DECIMAL_SEP_CHAR);
-
-            // add placeholder fraction zeros if needed
-            for (int j = 0; j > diff; --j) {
-                sb.append(ZERO_CHAR);
-            }
-
-            // fraction digits
-            sb.append(digits, i, precision);
-        } else {
-            sb.append(digits);
-
-            for (int i = 0; i < exponent; ++i) {
-                sb.append(ZERO_CHAR);
-            }
-
-            if (includeDecimalPlaceholder) {
-                sb.append(DECIMAL_SEP_CHAR)
-                    .append(ZERO_CHAR);
-            }
-        }
-
-        return sb.toString();
-    }
-
-    /** Return a string representation of the value in scientific notation. If 
the exponent field
-     * is equal to zero, it is not included in the result. Ex:
-     * <pre>
-     * 0 = "0.0"
-     * 10 = "1.0E1"
-     * 1e-6 = "1.0E-6"
-     * 1e11 = "1.0E11"
-     * </pre>
-     * @param includeDecimalPlaceholder if true, then the returned string will 
contain
-     *      the decimal placeholder ".0" when no fractional value is present, 
similar
-     *      to {@link Double#toString(double)}
-     * @return a string representation of the value in scientific notation
-     */
-    public String toScientificString(final boolean includeDecimalPlaceholder) {
-        return toScientificString(1, includeDecimalPlaceholder);
-    }
-
-    /** Return a string representation of the value in engineering notation. 
This is similar
-     * to {@link #toScientificString(boolean) scientific notation} but with 
the exponent forced
-     * to be a multiple of 3, allowing easier alignment with SI prefixes. If 
the exponent field
-     * is equal to zero, it is not included in the result.
-     * <pre>
-     * 0 = "0.0"
-     * 10 = "10.0"
-     * 1e-6 = "1.0E-6"
-     * 1e11 = "100.0E9"
-     * </pre>
-     * @param includeDecimalPlaceholder if true, then the returned string will 
contain
-     *      the decimal placeholder ".0" when no fractional value is present, 
similar
-     *      to {@link Double#toString(double)}
-     * @return a string representation of the value in engineering notation
-     */
-    public String toEngineeringString(final boolean includeDecimalPlaceholder) 
{
-        final int wholeDigits = 1 + Math.floorMod(getPrecision() + exponent - 
1, 3);
-        return toScientificString(wholeDigits, includeDecimalPlaceholder);
-    }
-
-    /** Return a string representation of the value in scientific notation 
using the
-     * given number of whole digits. If the exponent field of the result is 
zero, it
-     * is not included in the returned string.
-     * @param wholeDigits number of whole digits to use in the output
-     * @param includeDecimalPlaceholder if true, then the returned string will 
contain
-     *      the decimal placeholder ".0" when no fractional value is present, 
similar
-     *      to {@link Double#toString(double)}
-     * @return a string representation of the value in scientific notation 
using the
-     *      given number of whole digits
-     */
-    private String toScientificString(final int wholeDigits, final boolean 
includeDecimalPlaceholder) {
-        final int precision = getPrecision();
-
-        final StringBuilder sb = new StringBuilder(INITIAL_STR_BUILDER_SIZE);
-        if (negative) {
-            sb.append(MINUS_CHAR);
-        }
-
-        if (precision <= wholeDigits) {
-            // not enough digits to meet the requested number of whole digits;
-            // we'll need to pad with zeros
-            sb.append(digits);
-
-            for (int i = precision; i < wholeDigits; ++i) {
-                sb.append(ZERO_CHAR);
-            }
-
-            if (includeDecimalPlaceholder) {
-                sb.append(DECIMAL_SEP_CHAR)
-                    .append(ZERO_CHAR);
-            }
-        } else {
-            // we'll need a fractional portion
-            sb.append(digits, 0, wholeDigits)
-                .append(DECIMAL_SEP_CHAR)
-                .append(digits, wholeDigits, precision);
-        }
-
-        // add the exponent but only if non-zero
-        final int resultExponent = exponent + precision - wholeDigits;
-        if (resultExponent != 0) {
-            sb.append(EXPONENT_CHAR)
-                .append(resultExponent);
-        }
-
-        return sb.toString();
-    }
-
-    /** Return true if a rounding operation at the given index should round up.
-     * @param idx index of the digit to round; must be a valid index into 
{@code digits}
-     * @return true if a rounding operation at the given index should round up
-     */
-    private boolean shouldRoundUp(final int idx) {
-        // Round up in the following cases:
-        // 1. The digit at the index is greater than 5.
-        // 2. The digit at the index is 5 and there are additional (non-zero)
-        //      digits after it.
-        // 3. The digit is 5, there are no additional digits afterward,
-        //      and the digit before it is odd (half-even rounding).
-        final int precision = getPrecision();
-        final int roundValue = digitValue(digits.charAt(idx));
-
-        return roundValue > 5 || (roundValue == 5 &&
-                (idx < precision - 1 || (idx > 0 && 
digitValue(digits.charAt(idx - 1)) % 2 != 0)));
-    }
-
-    /** Construct a new instance from the given double value.
-     * @param d double value
-     * @return a new instance containing the parsed components of the given 
double value
-     * @throws IllegalArgumentException if {@code d} is {@code NaN} or infinite
-     */
-    public static ParsedDouble from(final double d) {
-        if (!Double.isFinite(d)) {
-            throw new IllegalArgumentException("Double is not finite");
-        }
-
-        final String str = Double.toString(d);
-        final char[] strChars = str.toCharArray();
-
-        // extract the different portions of the string representation
-        // (since double is finite, str is guaranteed to not be empty and to 
contain a
-        // single decimal point according to the Double.toString() API)
-        final boolean negative = strChars[0] == MINUS_CHAR;
-        final int digitStartIdx = negative ? 1 : 0;
-
-        final char[] digitChars = new char[strChars.length];
-
-        int decimalSepIdx = -1;
-        int exponentIdx = -1;
-        int digitCount = 0;
-        int firstNonZeroDigitIdx = -1;
-        int lastNonZeroDigitIdx = -1;
-
-        for (int i = digitStartIdx; i < strChars.length; ++i) {
-            final char ch = strChars[i];
-
-            if (ch == DECIMAL_SEP_CHAR) {
-                decimalSepIdx = i;
-            } else if (ch == EXPONENT_CHAR) {
-                exponentIdx = i;
-            } else if (exponentIdx < 0) {
-                // this is a significand digit
-                if (ch != ZERO_CHAR) {
-                    if (firstNonZeroDigitIdx < 0) {
-                        firstNonZeroDigitIdx = digitCount;
-                    }
-                    lastNonZeroDigitIdx = digitCount;
-                }
-
-                digitChars[digitCount++] = ch;
-            }
-        }
-
-        if (firstNonZeroDigitIdx > -1) {
-            // determine the exponent
-            final int explicitExponent = exponentIdx > -1 ?
-                    parseExponent(str, exponentIdx + 1) :
-                    0;
-            final int exponent = explicitExponent + decimalSepIdx - 
digitStartIdx - lastNonZeroDigitIdx - 1;
-
-            // get the digit string without any leading or trailing zeros
-            final String digits = String.valueOf(
-                    digitChars,
-                    firstNonZeroDigitIdx,
-                    lastNonZeroDigitIdx - firstNonZeroDigitIdx + 1);
-
-            return new ParsedDouble(negative, digits, exponent);
-        }
-
-        // no non-zero digits, so value is zero
-        return negative ?
-                NEG_ZERO :
-                POS_ZERO;
-    }
-
-    /** Parse a double exponent value from {@code seq}, starting at the {@code 
start}
-     * index and continuing through the end of the sequence.
-     * @param seq sequence to part a double exponent value from
-     * @param start start index
-     * @return parsed exponent value
-     */
-    private static int parseExponent(final CharSequence seq, final int start) {
-        int exp = 0;
-        boolean neg = false;
-
-        final int len = seq.length();
-        for (int i = start; i < len; ++i) {
-            final char ch = seq.charAt(i);
-            if (ch == MINUS_CHAR) {
-                neg = !neg;
-            } else if (ch != PLUS_CHAR) {
-                exp = (exp * 10) + digitValue(ch);
-            }
-        }
-
-        return neg ? -exp : exp;
-    }
-
-    /** Return the index of the last character in the argument not equal
-     * to {@code '0'} or {@code -1} if no such character can be found.
-     * @param seq sequence to search
-     * @return the index of the last non-zero character or {@code -1} if not 
found
-     */
-    private static int findLastNonZero(final CharSequence seq) {
-        int i;
-        char ch;
-        for (i = seq.length() - 1; i >= 0; --i) {
-            ch = seq.charAt(i);
-            if (ch != ZERO_CHAR) {
-                break;
-            }
-        }
-
-        return i;
-    }
-
-    /** Get the numeric value of the given digit character. No validation of 
the
-     * character type is performed.
-     * @param ch digit character
-     * @return numeric value of the digit character, ex: '1' = 1
-     */
-    private static int digitValue(final char ch) {
-        return ch - ZERO_CHAR;
-    }
-
-    /** Add one to the value of the integer represented by the substring of 
length {@code len}
-     * starting at index {@code 0}, returning the result as another string. 
The input is assumed
-     * to contain only digit characters
-     * (i.e. '0' - '9'). No validation is performed.
-     * @param digitStr string containing a representation of an integer
-     * @param len number of characters to use from {@code str}
-     * @return string representation of the result of adding 1 to the integer 
represented
-     *      by the input substring
-     */
-    private static String addOne(final String digitStr, final int len) {
-        final char[] resultChars = new char[len + 1];
-
-        boolean carrying = true;
-        for (int i = len - 1; i >= 0; --i) {
-            final char inChar = digitStr.charAt(i);
-            final char outChar = carrying ?
-                    DECIMAL_DIGITS.charAt((digitValue(inChar) + 1) % 
DECIMAL_DIGITS.length()) :
-                    inChar;
-            resultChars[i + 1] = outChar;
-
-            if (carrying && outChar != ZERO_CHAR) {
-                carrying = false;
-            }
-        }
-
-        if (carrying) {
-            resultChars[0] = ONE_CHAR;
-            return String.valueOf(resultChars);
-        }
-
-        return String.valueOf(resultChars, 1, len);
-    }
-}
diff --git 
a/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/utils/AbstractTextFormatWriterTest.java
 
b/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/utils/AbstractTextFormatWriterTest.java
index 316804a..5f9516f 100644
--- 
a/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/utils/AbstractTextFormatWriterTest.java
+++ 
b/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/utils/AbstractTextFormatWriterTest.java
@@ -19,6 +19,9 @@ package org.apache.commons.geometry.io.core.utils;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
 import java.util.function.DoubleFunction;
 
 import org.apache.commons.geometry.io.core.test.CloseCountWriter;
@@ -35,7 +38,7 @@ class AbstractTextFormatWriterTest {
         try (TestWriter writer = new TestWriter(out)) {
             // assert
             Assertions.assertEquals("\n", writer.getLineSeparator());
-            Assertions.assertSame(DoubleFormats.DOUBLE_TO_STRING, 
writer.getDoubleFormat());
+            Assertions.assertNotNull(writer.getDoubleFormat());
             Assertions.assertSame(out, writer.getWriter());
         }
     }
@@ -71,7 +74,9 @@ class AbstractTextFormatWriterTest {
 
             writer.setLineSeparator("\r\n");
 
-            final DoubleFunction<String> df = DoubleFormats.createPlain(0, -2);
+            final DecimalFormat fmt = new DecimalFormat("0.00", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+
+            final DoubleFunction<String> df = fmt::format;
             writer.setDoubleFormat(df);
 
             // act
diff --git 
a/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/utils/DoubleFormatsTest.java
 
b/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/utils/DoubleFormatsTest.java
deleted file mode 100644
index ce1ad2d..0000000
--- 
a/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/utils/DoubleFormatsTest.java
+++ /dev/null
@@ -1,894 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.io.core.utils;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.BiFunction;
-import java.util.function.DoubleFunction;
-import java.util.function.IntFunction;
-
-import org.apache.commons.geometry.core.GeometryTestUtils;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-class DoubleFormatsTest {
-
-    private static final double[] EXAMPLE_VALUES = {
-        0.0001, -0.0635, 510.751, -123456.0, 42078500.0
-    };
-
-    @Test
-    void testDoubleToString() {
-        // arrange
-        final DoubleFunction<String> fmt = DoubleFormats.DOUBLE_TO_STRING;
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "1.0E-5");
-        checkFormat(fmt, -0.0001, "-1.0E-4");
-        checkFormat(fmt, 0.001, "0.001");
-        checkFormat(fmt, -0.01, "-0.01");
-        checkFormat(fmt, 0.1, "0.1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1000.0");
-        checkFormat(fmt, -10000.0, "-10000.0");
-        checkFormat(fmt, 100000.0, "100000.0");
-        checkFormat(fmt, -1000000.0, "-1000000.0");
-        checkFormat(fmt, 10000000.0, "1.0E7");
-        checkFormat(fmt, -100000000.0, "-1.0E8");
-
-        checkFormat(fmt, 0.5 * Float.MAX_VALUE, "1.7014117331926443E38");
-        checkFormat(fmt, -1.0 / 1.9175e20, "-5.2151238591916555E-21");
-
-        checkFormat(fmt, Double.MAX_VALUE, "1.7976931348623157E308");
-        checkFormat(fmt, Double.MIN_VALUE, "4.9E-324");
-        checkFormat(fmt, Double.MIN_NORMAL, "2.2250738585072014E-308");
-        checkFormat(fmt, Math.PI, "3.141592653589793");
-        checkFormat(fmt, Math.E, "2.718281828459045");
-    }
-
-    @Test
-    void testFloatToString() {
-        // arrange
-        final DoubleFunction<String> fmt = DoubleFormats.FLOAT_TO_STRING;
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "1.0E-5");
-        checkFormat(fmt, -0.0001, "-1.0E-4");
-        checkFormat(fmt, 0.001, "0.001");
-        checkFormat(fmt, -0.01, "-0.01");
-        checkFormat(fmt, 0.1, "0.1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1000.0");
-        checkFormat(fmt, -10000.0, "-10000.0");
-        checkFormat(fmt, 100000.0, "100000.0");
-        checkFormat(fmt, -1000000.0, "-1000000.0");
-        checkFormat(fmt, 10000000.0, "1.0E7");
-        checkFormat(fmt, -100000000.0, "-1.0E8");
-
-        checkFormat(fmt, 0.5 * Float.MAX_VALUE, "1.7014117E38");
-        checkFormat(fmt, -1.0 / 1.9175e20, "-5.2151238E-21");
-
-        checkFormat(fmt, Double.MAX_VALUE, "Infinity");
-        checkFormat(fmt, Double.MIN_VALUE, "0.0");
-        checkFormat(fmt, Double.MIN_NORMAL, "0.0");
-        checkFormat(fmt, Math.PI, "3.1415927");
-        checkFormat(fmt, Math.E, "2.7182817");
-    }
-
-    @Test
-    void testDefault_noPrecisionLimit_noMinExponent() {
-        // arrange
-        final int maxPrecision = 0;
-
-        // act
-        final DoubleFunction<String> fmt = 
DoubleFormats.createDefault(maxPrecision);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "1.0E-5");
-        checkFormat(fmt, -0.0001, "-1.0E-4");
-        checkFormat(fmt, 0.001, "0.001");
-        checkFormat(fmt, -0.01, "-0.01");
-        checkFormat(fmt, 0.1, "0.1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1000.0");
-        checkFormat(fmt, -10000.0, "-10000.0");
-        checkFormat(fmt, 100000.0, "100000.0");
-        checkFormat(fmt, -1000000.0, "-1000000.0");
-        checkFormat(fmt, 10000000.0, "1.0E7");
-        checkFormat(fmt, -100000000.0, "-1.0E8");
-
-        checkFormat(fmt, 1.25e-3, "0.00125");
-        checkFormat(fmt, -9.975e-4, "-9.975E-4");
-        checkFormat(fmt, -9_999_999, "-9999999.0");
-        checkFormat(fmt, 1.00001e7, "1.00001E7");
-
-        checkFormat(fmt, Double.MAX_VALUE, "1.7976931348623157E308");
-        checkFormat(fmt, Double.MIN_VALUE, "4.9E-324");
-        checkFormat(fmt, Double.MIN_NORMAL, "2.2250738585072014E-308");
-        checkFormat(fmt, Math.PI, "3.141592653589793");
-        checkFormat(fmt, Math.E, "2.718281828459045");
-    }
-
-    @Test
-    void testDefault_noPrecisionLimit_withMinExponent() {
-        // arrange
-        final int maxPrecision = 0;
-        final int minExponent = -3;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createDefault(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "0.0");
-        checkFormat(fmt, -0.0001, "0.0");
-        checkFormat(fmt, 0.001, "0.001");
-        checkFormat(fmt, -0.01, "-0.01");
-        checkFormat(fmt, 0.1, "0.1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1000.0");
-        checkFormat(fmt, -10000.0, "-10000.0");
-        checkFormat(fmt, 100000.0, "100000.0");
-        checkFormat(fmt, -1000000.0, "-1000000.0");
-        checkFormat(fmt, 10000000.0, "1.0E7");
-        checkFormat(fmt, -100000000.0, "-1.0E8");
-
-        checkFormat(fmt, 1.25e-3, "0.001");
-        checkFormat(fmt, -9.975e-4, "-0.001");
-        checkFormat(fmt, -9_999_999, "-9999999.0");
-        checkFormat(fmt, 1.00001e7, "1.00001E7");
-
-        checkFormat(fmt, Double.MAX_VALUE, "1.7976931348623157E308");
-        checkFormat(fmt, Double.MIN_VALUE, "0.0");
-        checkFormat(fmt, Double.MIN_NORMAL, "0.0");
-        checkFormat(fmt, Math.PI, "3.142");
-        checkFormat(fmt, Math.E, "2.718");
-    }
-
-    @Test
-    void testDefault_withPrecisionLimit_noMinExponent() {
-        // arrange
-        final int maxPrecision = 4;
-        final int minExponent = Integer.MIN_VALUE;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createDefault(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "1.0E-5");
-        checkFormat(fmt, -0.0001, "-1.0E-4");
-        checkFormat(fmt, 0.001, "0.001");
-        checkFormat(fmt, -0.01, "-0.01");
-        checkFormat(fmt, 0.1, "0.1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1000.0");
-        checkFormat(fmt, -10000.0, "-10000.0");
-        checkFormat(fmt, 100000.0, "100000.0");
-        checkFormat(fmt, -1000000.0, "-1000000.0");
-        checkFormat(fmt, 10000000.0, "1.0E7");
-        checkFormat(fmt, -100000000.0, "-1.0E8");
-
-        checkFormat(fmt, 12345.01, "12350.0");
-        checkFormat(fmt, 1.2345, "1.234");
-
-        checkFormat(fmt, 1.25e-3, "0.00125");
-        checkFormat(fmt, -9.975e-4, "-9.975E-4");
-        checkFormat(fmt, -9_999_999, "-1.0E7");
-        checkFormat(fmt, 1.00001e7, "1.0E7");
-
-        checkFormat(fmt, Double.MAX_VALUE, "1.798E308");
-        checkFormat(fmt, Double.MIN_VALUE, "4.9E-324");
-        checkFormat(fmt, Double.MIN_NORMAL, "2.225E-308");
-        checkFormat(fmt, Math.PI, "3.142");
-        checkFormat(fmt, Math.E, "2.718");
-    }
-
-    @Test
-    void testDefault_withPrecisionLimit_withMinExponent() {
-        // arrange
-        final int maxPrecision = 3;
-        final int minExponent = -3;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createDefault(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "0.0");
-        checkFormat(fmt, -0.0001, "0.0");
-        checkFormat(fmt, 0.001, "0.001");
-        checkFormat(fmt, -0.01, "-0.01");
-        checkFormat(fmt, 0.1, "0.1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1000.0");
-        checkFormat(fmt, -10000.0, "-10000.0");
-        checkFormat(fmt, 100000.0, "100000.0");
-        checkFormat(fmt, -1000000.0, "-1000000.0");
-        checkFormat(fmt, 10000000.0, "1.0E7");
-        checkFormat(fmt, -100000000.0, "-1.0E8");
-
-        checkFormat(fmt, 1.25e-3, "0.001");
-        checkFormat(fmt, -9.975e-4, "-0.001");
-        checkFormat(fmt, -9_999_999, "-1.0E7");
-        checkFormat(fmt, 1.00001e7, "1.0E7");
-
-        checkFormat(fmt, Double.MAX_VALUE, "1.8E308");
-        checkFormat(fmt, Double.MIN_VALUE, "0.0");
-        checkFormat(fmt, Double.MIN_NORMAL, "0.0");
-        checkFormat(fmt, Math.PI, "3.14");
-        checkFormat(fmt, Math.E, "2.72");
-    }
-
-    @Test
-    void testPlain_noPrecisionLimit_noMinExponent() {
-        // arrange
-        final int maxPrecision = 0;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createPlain(maxPrecision);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "0.00001");
-        checkFormat(fmt, -0.0001, "-0.0001");
-        checkFormat(fmt, 0.001, "0.001");
-        checkFormat(fmt, -0.01, "-0.01");
-        checkFormat(fmt, 0.1, "0.1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1000.0");
-        checkFormat(fmt, -10000.0, "-10000.0");
-        checkFormat(fmt, 100000.0, "100000.0");
-        checkFormat(fmt, -1000000.0, "-1000000.0");
-        checkFormat(fmt, 10000000.0, "10000000.0");
-        checkFormat(fmt, -100000000.0, "-100000000.0");
-
-        checkFormat(fmt, 1.25e-3, "0.00125");
-        checkFormat(fmt, -9.975e-4, "-0.0009975");
-        checkFormat(fmt, -9_999_999, "-9999999.0");
-        checkFormat(fmt, 1.00001e7, "10000100.0");
-
-        checkFormat(fmt, Float.MAX_VALUE, 
"340282346638528860000000000000000000000.0");
-        checkFormat(fmt, Float.MIN_VALUE, 
"0.000000000000000000000000000000000000000000001401298464324817");
-        checkFormat(fmt, Float.MIN_NORMAL, 
"0.000000000000000000000000000000000000011754943508222875");
-        checkFormat(fmt, Math.PI, "3.141592653589793");
-        checkFormat(fmt, Math.E, "2.718281828459045");
-    }
-
-    @Test
-    void testPlain_noPrecisionLimit_withMinExponent() {
-        // arrange
-        final int maxPrecision = 0;
-        final int minExponent = -2;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createPlain(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "0.0");
-        checkFormat(fmt, -0.0001, "0.0");
-        checkFormat(fmt, 0.001, "0.0");
-        checkFormat(fmt, -0.01, "-0.01");
-        checkFormat(fmt, 0.1, "0.1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1000.0");
-        checkFormat(fmt, -10000.0, "-10000.0");
-        checkFormat(fmt, 100000.0, "100000.0");
-        checkFormat(fmt, -1000000.0, "-1000000.0");
-        checkFormat(fmt, 10000000.0, "10000000.0");
-        checkFormat(fmt, -100000000.0, "-100000000.0");
-
-        checkFormat(fmt, 1.25e-3, "0.0");
-        checkFormat(fmt, -9.975e-4, "0.0");
-        checkFormat(fmt, -9_999_999, "-9999999.0");
-        checkFormat(fmt, 1.00001e7, "10000100.0");
-
-        checkFormat(fmt, Float.MAX_VALUE, 
"340282346638528860000000000000000000000.0");
-        checkFormat(fmt, Float.MIN_VALUE, "0.0");
-        checkFormat(fmt, Float.MIN_NORMAL, "0.0");
-        checkFormat(fmt, Math.PI, "3.14");
-        checkFormat(fmt, Math.E, "2.72");
-    }
-
-    @Test
-    void testPlain_withPrecisionLimit_noMinExponent() {
-        // arrange
-        final int maxPrecision = 3;
-        final int minExponent = Integer.MIN_VALUE;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createPlain(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "0.00001");
-        checkFormat(fmt, -0.0001, "-0.0001");
-        checkFormat(fmt, 0.001, "0.001");
-        checkFormat(fmt, -0.01, "-0.01");
-        checkFormat(fmt, 0.1, "0.1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1000.0");
-        checkFormat(fmt, -10000.0, "-10000.0");
-        checkFormat(fmt, 100000.0, "100000.0");
-        checkFormat(fmt, -1000000.0, "-1000000.0");
-        checkFormat(fmt, 10000000.0, "10000000.0");
-        checkFormat(fmt, -100000000.0, "-100000000.0");
-
-        checkFormat(fmt, 1.25e-3, "0.00125");
-        checkFormat(fmt, -9.975e-4, "-0.000998");
-        checkFormat(fmt, -9_999_999, "-10000000.0");
-        checkFormat(fmt, 1.00001e7, "10000000.0");
-
-        checkFormat(fmt, Float.MAX_VALUE, 
"340000000000000000000000000000000000000.0");
-        checkFormat(fmt, Float.MIN_VALUE, 
"0.0000000000000000000000000000000000000000000014");
-        checkFormat(fmt, Float.MIN_NORMAL, 
"0.0000000000000000000000000000000000000118");
-        checkFormat(fmt, Math.PI, "3.14");
-        checkFormat(fmt, Math.E, "2.72");
-    }
-
-    @Test
-    void testPlain_withPrecisionLimit_withMinExponent() {
-        // arrange
-        final int maxPrecision = 4;
-        final int minExponent = -2;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createPlain(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "0.0");
-        checkFormat(fmt, -0.0001, "0.0");
-        checkFormat(fmt, 0.001, "0.0");
-        checkFormat(fmt, -0.01, "-0.01");
-        checkFormat(fmt, 0.1, "0.1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1000.0");
-        checkFormat(fmt, -10000.0, "-10000.0");
-        checkFormat(fmt, 100000.0, "100000.0");
-        checkFormat(fmt, -1000000.0, "-1000000.0");
-        checkFormat(fmt, 10000000.0, "10000000.0");
-        checkFormat(fmt, -100000000.0, "-100000000.0");
-
-        checkFormat(fmt, 1.25e-3, "0.0");
-        checkFormat(fmt, -9.975e-4, "0.0");
-        checkFormat(fmt, -9_999_999, "-10000000.0");
-        checkFormat(fmt, 1.00001e7, "10000000.0");
-
-        checkFormat(fmt, Float.MAX_VALUE, 
"340300000000000000000000000000000000000.0");
-        checkFormat(fmt, Float.MIN_VALUE, "0.0");
-        checkFormat(fmt, Float.MIN_NORMAL, "0.0");
-        checkFormat(fmt, Math.PI, "3.14");
-        checkFormat(fmt, Math.E, "2.72");
-    }
-
-    @Test
-    void testScientific_noPrecisionLimit_noMinExponent() {
-        // arrange
-        final int maxPrecision = 0;
-
-        // act
-        final DoubleFunction<String> fmt = 
DoubleFormats.createScientific(maxPrecision);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "1.0E-5");
-        checkFormat(fmt, -0.0001, "-1.0E-4");
-        checkFormat(fmt, 0.001, "1.0E-3");
-        checkFormat(fmt, -0.01, "-1.0E-2");
-        checkFormat(fmt, 0.1, "1.0E-1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "1.0E1");
-        checkFormat(fmt, -100.0, "-1.0E2");
-        checkFormat(fmt, 1000.0, "1.0E3");
-        checkFormat(fmt, -10000.0, "-1.0E4");
-        checkFormat(fmt, 100000.0, "1.0E5");
-        checkFormat(fmt, -1000000.0, "-1.0E6");
-        checkFormat(fmt, 10000000.0, "1.0E7");
-        checkFormat(fmt, -100000000.0, "-1.0E8");
-
-        checkFormat(fmt, 1.25e-3, "1.25E-3");
-        checkFormat(fmt, -9.975e-4, "-9.975E-4");
-        checkFormat(fmt, -9_999_999, "-9.999999E6");
-        checkFormat(fmt, 1.00001e7, "1.00001E7");
-
-        checkFormat(fmt, Double.MAX_VALUE, "1.7976931348623157E308");
-        checkFormat(fmt, Double.MIN_VALUE, "4.9E-324");
-        checkFormat(fmt, Double.MIN_NORMAL, "2.2250738585072014E-308");
-        checkFormat(fmt, Math.PI, "3.141592653589793");
-        checkFormat(fmt, Math.E, "2.718281828459045");
-    }
-
-    @Test
-    void testScientific_noPrecisionLimit_withMinExponent() {
-        // arrange
-        final int maxPrecision = 0;
-        final int minExponent = -3;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createScientific(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "0.0");
-        checkFormat(fmt, -0.0001, "0.0");
-        checkFormat(fmt, 0.001, "1.0E-3");
-        checkFormat(fmt, -0.01, "-1.0E-2");
-        checkFormat(fmt, 0.1, "1.0E-1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "1.0E1");
-        checkFormat(fmt, -100.0, "-1.0E2");
-        checkFormat(fmt, 1000.0, "1.0E3");
-        checkFormat(fmt, -10000.0, "-1.0E4");
-        checkFormat(fmt, 100000.0, "1.0E5");
-        checkFormat(fmt, -1000000.0, "-1.0E6");
-        checkFormat(fmt, 10000000.0, "1.0E7");
-        checkFormat(fmt, -100000000.0, "-1.0E8");
-
-        checkFormat(fmt, 1.25e-3, "1.0E-3");
-        checkFormat(fmt, -9.975e-4, "-1.0E-3");
-        checkFormat(fmt, -9_999_999, "-9.999999E6");
-        checkFormat(fmt, 1.00001e7, "1.00001E7");
-
-        checkFormat(fmt, Double.MAX_VALUE, "1.7976931348623157E308");
-        checkFormat(fmt, Double.MIN_VALUE, "0.0");
-        checkFormat(fmt, Double.MIN_NORMAL, "0.0");
-        checkFormat(fmt, Math.PI, "3.142");
-        checkFormat(fmt, Math.E, "2.718");
-    }
-
-    @Test
-    void testScientific_withPrecisionLimit_noMinExponent() {
-        // arrange
-        final int maxPrecision = 3;
-        final int minExponent = Integer.MIN_VALUE;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createScientific(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "1.0E-5");
-        checkFormat(fmt, -0.0001, "-1.0E-4");
-        checkFormat(fmt, 0.001, "1.0E-3");
-        checkFormat(fmt, -0.01, "-1.0E-2");
-        checkFormat(fmt, 0.1, "1.0E-1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "1.0E1");
-        checkFormat(fmt, -100.0, "-1.0E2");
-        checkFormat(fmt, 1000.0, "1.0E3");
-        checkFormat(fmt, -10000.0, "-1.0E4");
-        checkFormat(fmt, 100000.0, "1.0E5");
-        checkFormat(fmt, -1000000.0, "-1.0E6");
-        checkFormat(fmt, 10000000.0, "1.0E7");
-        checkFormat(fmt, -100000000.0, "-1.0E8");
-
-        checkFormat(fmt, 12345.01, "1.23E4");
-        checkFormat(fmt, 1.2345, "1.23");
-
-        checkFormat(fmt, 1.25e-3, "1.25E-3");
-        checkFormat(fmt, -9.975e-4, "-9.98E-4");
-        checkFormat(fmt, -9_999_999, "-1.0E7");
-        checkFormat(fmt, 1.00001e7, "1.0E7");
-
-        checkFormat(fmt, Double.MAX_VALUE, "1.8E308");
-        checkFormat(fmt, Double.MIN_VALUE, "4.9E-324");
-        checkFormat(fmt, Double.MIN_NORMAL, "2.23E-308");
-        checkFormat(fmt, Math.PI, "3.14");
-        checkFormat(fmt, Math.E, "2.72");
-    }
-
-    @Test
-    void testScientific_withPrecisionLimit_withMinExponent() {
-        // arrange
-        final int maxPrecision = 3;
-        final int minExponent = -3;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createScientific(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "0.0");
-        checkFormat(fmt, -0.0001, "0.0");
-        checkFormat(fmt, 0.001, "1.0E-3");
-        checkFormat(fmt, -0.01, "-1.0E-2");
-        checkFormat(fmt, 0.1, "1.0E-1");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "1.0E1");
-        checkFormat(fmt, -100.0, "-1.0E2");
-        checkFormat(fmt, 1000.0, "1.0E3");
-        checkFormat(fmt, -10000.0, "-1.0E4");
-        checkFormat(fmt, 100000.0, "1.0E5");
-        checkFormat(fmt, -1000000.0, "-1.0E6");
-        checkFormat(fmt, 10000000.0, "1.0E7");
-        checkFormat(fmt, -100000000.0, "-1.0E8");
-
-        checkFormat(fmt, 1.25e-3, "1.0E-3");
-        checkFormat(fmt, -9.975e-4, "-1.0E-3");
-        checkFormat(fmt, -9_999_999, "-1.0E7");
-        checkFormat(fmt, 1.00001e7, "1.0E7");
-
-        checkFormat(fmt, Double.MAX_VALUE, "1.8E308");
-        checkFormat(fmt, Double.MIN_VALUE, "0.0");
-        checkFormat(fmt, Double.MIN_NORMAL, "0.0");
-        checkFormat(fmt, Math.PI, "3.14");
-        checkFormat(fmt, Math.E, "2.72");
-    }
-
-    @Test
-    void testEngineering_noPrecisionLimit_noMinExponent() {
-        // arrange
-        final int maxPrecision = 0;
-
-        // act
-        final DoubleFunction<String> fmt = 
DoubleFormats.createEngineering(maxPrecision);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "10.0E-6");
-        checkFormat(fmt, -0.0001, "-100.0E-6");
-        checkFormat(fmt, 0.001, "1.0E-3");
-        checkFormat(fmt, -0.01, "-10.0E-3");
-        checkFormat(fmt, 0.1, "100.0E-3");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1.0E3");
-        checkFormat(fmt, -10000.0, "-10.0E3");
-        checkFormat(fmt, 100000.0, "100.0E3");
-        checkFormat(fmt, -1000000.0, "-1.0E6");
-        checkFormat(fmt, 10000000.0, "10.0E6");
-        checkFormat(fmt, -100000000.0, "-100.0E6");
-
-        checkFormat(fmt, 1.25e-3, "1.25E-3");
-        checkFormat(fmt, -9.975e-4, "-997.5E-6");
-        checkFormat(fmt, -9_999_999, "-9.999999E6");
-        checkFormat(fmt, 1.00001e7, "10.0001E6");
-
-        checkFormat(fmt, Double.MAX_VALUE, "179.76931348623157E306");
-        checkFormat(fmt, Double.MIN_VALUE, "4.9E-324");
-        checkFormat(fmt, Double.MIN_NORMAL, "22.250738585072014E-309");
-        checkFormat(fmt, Math.PI, "3.141592653589793");
-        checkFormat(fmt, Math.E, "2.718281828459045");
-    }
-
-    @Test
-    void testEngineering_noPrecisionLimit_withMinExponent() {
-        // arrange
-        final int maxPrecision = 0;
-        final int minExponent = -3;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createEngineering(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "0.0");
-        checkFormat(fmt, -0.0001, "0.0");
-        checkFormat(fmt, 0.001, "1.0E-3");
-        checkFormat(fmt, -0.01, "-10.0E-3");
-        checkFormat(fmt, 0.1, "100.0E-3");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1.0E3");
-        checkFormat(fmt, -10000.0, "-10.0E3");
-        checkFormat(fmt, 100000.0, "100.0E3");
-        checkFormat(fmt, -1000000.0, "-1.0E6");
-        checkFormat(fmt, 10000000.0, "10.0E6");
-        checkFormat(fmt, -100000000.0, "-100.0E6");
-
-        checkFormat(fmt, 1.25e-3, "1.0E-3");
-        checkFormat(fmt, -9.975e-4, "-1.0E-3");
-        checkFormat(fmt, -9_999_999, "-9.999999E6");
-        checkFormat(fmt, 1.00001e7, "10.0001E6");
-
-        checkFormat(fmt, Double.MAX_VALUE, "179.76931348623157E306");
-        checkFormat(fmt, Double.MIN_VALUE, "0.0");
-        checkFormat(fmt, Double.MIN_NORMAL, "0.0");
-        checkFormat(fmt, Math.PI, "3.142");
-        checkFormat(fmt, Math.E, "2.718");
-    }
-
-    @Test
-    void testEngineering_withPrecisionLimit_noMinExponent() {
-        // arrange
-        final int maxPrecision = 3;
-        final int minExponent = Integer.MIN_VALUE;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createEngineering(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "10.0E-6");
-        checkFormat(fmt, -0.0001, "-100.0E-6");
-        checkFormat(fmt, 0.001, "1.0E-3");
-        checkFormat(fmt, -0.01, "-10.0E-3");
-        checkFormat(fmt, 0.1, "100.0E-3");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1.0E3");
-        checkFormat(fmt, -10000.0, "-10.0E3");
-        checkFormat(fmt, 100000.0, "100.0E3");
-        checkFormat(fmt, -1000000.0, "-1.0E6");
-        checkFormat(fmt, 10000000.0, "10.0E6");
-        checkFormat(fmt, -100000000.0, "-100.0E6");
-
-        checkFormat(fmt, 1.25e-3, "1.25E-3");
-        checkFormat(fmt, -9.975e-4, "-998.0E-6");
-        checkFormat(fmt, -9_999_999, "-10.0E6");
-        checkFormat(fmt, 1.00001e7, "10.0E6");
-
-        checkFormat(fmt, Double.MAX_VALUE, "180.0E306");
-        checkFormat(fmt, Double.MIN_VALUE, "4.9E-324");
-        checkFormat(fmt, Double.MIN_NORMAL, "22.3E-309");
-        checkFormat(fmt, Math.PI, "3.14");
-        checkFormat(fmt, Math.E, "2.72");
-    }
-
-    @Test
-    void testEngineering_withPrecisionLimit_withMinExponent() {
-        // arrange
-        final int maxPrecision = 3;
-        final int minExponent = -3;
-
-        final DoubleFunction<String> fmt = 
DoubleFormats.createEngineering(maxPrecision, minExponent);
-
-        // act/assert
-        checkFormatSpecial(fmt);
-
-        checkFormat(fmt, 0.00001, "0.0");
-        checkFormat(fmt, -0.0001, "0.0");
-        checkFormat(fmt, 0.001, "1.0E-3");
-        checkFormat(fmt, -0.01, "-10.0E-3");
-        checkFormat(fmt, 0.1, "100.0E-3");
-        checkFormat(fmt, -0.0, "-0.0");
-        checkFormat(fmt, 0.0, "0.0");
-        checkFormat(fmt, -1.0, "-1.0");
-        checkFormat(fmt, 10.0, "10.0");
-        checkFormat(fmt, -100.0, "-100.0");
-        checkFormat(fmt, 1000.0, "1.0E3");
-        checkFormat(fmt, -10000.0, "-10.0E3");
-        checkFormat(fmt, 100000.0, "100.0E3");
-        checkFormat(fmt, -1000000.0, "-1.0E6");
-        checkFormat(fmt, 10000000.0, "10.0E6");
-        checkFormat(fmt, -100000000.0, "-100.0E6");
-
-        checkFormat(fmt, 1.25e-3, "1.0E-3");
-        checkFormat(fmt, -9.975e-4, "-1.0E-3");
-        checkFormat(fmt, -9_999_999, "-10.0E6");
-        checkFormat(fmt, 1.00001e7, "10.0E6");
-
-        checkFormat(fmt, Double.MAX_VALUE, "180.0E306");
-        checkFormat(fmt, Double.MIN_VALUE, "0.0");
-        checkFormat(fmt, Double.MIN_NORMAL, "0.0");
-        checkFormat(fmt, Math.PI, "3.14");
-        checkFormat(fmt, Math.E, "2.72");
-    }
-
-    @Test
-    void testPrecisionValidation() {
-        // arrange
-        final List<IntFunction<DoubleFunction<String>>> fns = Arrays.asList(
-                    DoubleFormats::createDefault,
-                    p -> DoubleFormats.createDefault(p, Integer.MIN_VALUE),
-                    DoubleFormats::createPlain,
-                    p -> DoubleFormats.createPlain(p, Integer.MIN_VALUE),
-                    DoubleFormats::createScientific,
-                    p -> DoubleFormats.createScientific(p, Integer.MIN_VALUE),
-                    DoubleFormats::createEngineering,
-                    p -> DoubleFormats.createEngineering(p, Integer.MIN_VALUE)
-                );
-
-        final String msg = "Max precision must be greater than or equal to 
zero; was -1";
-
-        // act/assert
-        for (final IntFunction<DoubleFunction<String>> fn : fns) {
-            GeometryTestUtils.assertThrowsWithMessage(
-                    () -> fn.apply(-1),
-                    IllegalArgumentException.class, msg);
-        }
-    }
-
-    /** Utility method used to generate the tables of format examples in the 
Javadocs.
-     * This helps to ensure accuracy and consistency in the documentation. The 
HTML tables
-     * are printed to a file and can then be copied into the correct locations 
in the source.
-     * @throws IOException
-     */
-    // @Test
-    public void generateExampleTables() throws IOException {
-        final Path output = Paths.get("target/format-examples.txt");
-
-        final List<String> lines = new ArrayList<>();
-
-        lines.add("Default - one arg");
-        lines.add(generateOneArgExamplesTable(DoubleFormats::createDefault));
-
-        lines.add("Default - two arg");
-        lines.add(generateTwoArgExamplesTable(DoubleFormats::createDefault));
-
-        lines.add("Plain - one arg");
-        lines.add(generateOneArgExamplesTable(DoubleFormats::createPlain));
-
-        lines.add("Plain - two arg");
-        lines.add(generateTwoArgExamplesTable(DoubleFormats::createPlain));
-
-        lines.add("Scientific - one arg");
-        
lines.add(generateOneArgExamplesTable(DoubleFormats::createScientific));
-
-        lines.add("Scientific - two arg");
-        
lines.add(generateTwoArgExamplesTable(DoubleFormats::createScientific));
-
-        lines.add("Engineering - one arg");
-        
lines.add(generateOneArgExamplesTable(DoubleFormats::createEngineering));
-
-        lines.add("Engineering - two arg");
-        
lines.add(generateTwoArgExamplesTable(DoubleFormats::createEngineering));
-
-        Files.write(output, lines);
-    }
-
-    private static String generateOneArgExamplesTable(final 
IntFunction<DoubleFunction<String>> fn) {
-        final int aMaxPrecision = 0;
-        final int bMaxPrecision = 4;
-
-        final DoubleFunction<String> aFmt = fn.apply(aMaxPrecision);
-        final DoubleFunction<String> bFmt = fn.apply(bMaxPrecision);
-
-        final String descTemplate = "(maxPrecision= %d)";
-
-        return generateExamplesTable(
-                    Arrays.asList(aFmt, bFmt),
-                    Arrays.asList(String.format(descTemplate, aMaxPrecision), 
String.format(descTemplate, bMaxPrecision))
-                );
-    }
-
-    private static String generateTwoArgExamplesTable(final 
BiFunction<Integer, Integer, DoubleFunction<String>> fn) {
-        final int aMaxPrecision = 0;
-        final int aMinExponent = -2;
-
-        final int bMaxPrecision = 4;
-        final int bMinExponent = -2;
-
-        final DoubleFunction<String> aFmt = fn.apply(aMaxPrecision, 
aMinExponent);
-        final DoubleFunction<String> bFmt = fn.apply(bMaxPrecision, 
bMinExponent);
-
-        final String descTemplate = "(maxPrecision= %d, minExponent= %d)";
-
-        return generateExamplesTable(
-                    Arrays.asList(aFmt, bFmt),
-                    Arrays.asList(
-                            String.format(descTemplate, aMaxPrecision, 
aMinExponent),
-                            String.format(descTemplate, bMaxPrecision, 
bMinExponent))
-                );
-    }
-
-    private static String generateExamplesTable(final 
List<DoubleFunction<String>> fmts,
-            final List<String> fmtDescriptions) {
-        final StringBuilder sb = new StringBuilder();
-
-        sb.append("<table>\n")
-            .append("  <tr><th>Value</th>");
-
-        for (String desc : fmtDescriptions) {
-            sb.append("<th>")
-                .append(desc)
-                .append("</th>");
-        }
-        sb.append("</tr>\n");
-
-        for (double value : EXAMPLE_VALUES) {
-            sb.append("  <tr><td>")
-                .append(value)
-                .append("</td>");
-
-            for (DoubleFunction<String> fmt : fmts) {
-                sb.append("<td>")
-                    .append(fmt.apply(value))
-                    .append("</td>");
-            }
-
-            sb.append("</tr>\n");
-        }
-
-        sb.append("</table>");
-
-        return sb.toString();
-    }
-
-    private static void checkFormat(final DoubleFunction<String> fmt, final 
double d, final String str) {
-        Assertions.assertEquals(str, fmt.apply(d));
-    }
-
-    private static void checkFormatSpecial(final DoubleFunction<String> fmt) {
-        checkFormat(fmt, Double.NaN, "NaN");
-        checkFormat(fmt, Double.POSITIVE_INFINITY, "Infinity");
-        checkFormat(fmt, Double.NEGATIVE_INFINITY, "-Infinity");
-    }
-}
diff --git 
a/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/utils/ParsedDoubleTest.java
 
b/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/utils/ParsedDoubleTest.java
deleted file mode 100644
index 975c09f..0000000
--- 
a/commons-geometry-io-core/src/test/java/org/apache/commons/geometry/io/core/utils/ParsedDoubleTest.java
+++ /dev/null
@@ -1,464 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.io.core.utils;
-
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.math.RoundingMode;
-import java.util.function.BiFunction;
-
-import org.apache.commons.geometry.core.GeometryTestUtils;
-import org.apache.commons.rng.UniformRandomProvider;
-import org.apache.commons.rng.simple.RandomSource;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-class ParsedDoubleTest {
-
-    @Test
-    void testFrom() {
-        // act/assert
-        checkFrom(0.0, "0", 0);
-
-        checkFrom(1.0, "1", 0);
-        checkFrom(10.0, "1", 1);
-        checkFrom(100.0, "1", 2);
-        checkFrom(1000.0, "1", 3);
-        checkFrom(10000.0, "1", 4);
-
-        checkFrom(0.1, "1", -1);
-        checkFrom(0.01, "1", -2);
-        checkFrom(0.001, "1", -3);
-        checkFrom(0.0001, "1", -4);
-        checkFrom(0.00001, "1", -5);
-
-        checkFrom(1.2, "12", -1);
-        checkFrom(0.00971, "971", -5);
-        checkFrom(56300, "563", 2);
-
-        checkFrom(123.0, "123", 0);
-        checkFrom(1230.0, "123", 1);
-        checkFrom(12300.0, "123", 2);
-        checkFrom(123000.0, "123", 3);
-
-        checkFrom(12.3, "123", -1);
-        checkFrom(1.23, "123", -2);
-        checkFrom(0.123, "123", -3);
-        checkFrom(0.0123, "123", -4);
-
-        checkFrom(1.987654321e270, "1987654321", 261);
-        checkFrom(1.987654321e-270, "1987654321", -279);
-
-        checkFrom(Math.PI, "3141592653589793", -15);
-        checkFrom(Math.E, "2718281828459045", -15);
-
-        checkFrom(Double.MAX_VALUE, "17976931348623157", 292);
-        checkFrom(Double.MIN_VALUE, "49", -325);
-        checkFrom(Double.MIN_NORMAL, "22250738585072014", -324);
-    }
-
-    @Test
-    void testFrom_notFinite() {
-        // arrange
-        final String msg = "Double is not finite";
-
-        // act/assert
-        GeometryTestUtils.assertThrowsWithMessage(() -> 
ParsedDouble.from(Double.NaN),
-                IllegalArgumentException.class, msg);
-        GeometryTestUtils.assertThrowsWithMessage(() -> 
ParsedDouble.from(Double.NEGATIVE_INFINITY),
-                IllegalArgumentException.class, msg);
-        GeometryTestUtils.assertThrowsWithMessage(() -> 
ParsedDouble.from(Double.POSITIVE_INFINITY),
-                IllegalArgumentException.class, msg);
-    }
-
-    @Test
-    void testIsZero() {
-        // act/assert
-        Assertions.assertTrue(ParsedDouble.from(0.0).isZero());
-        Assertions.assertTrue(ParsedDouble.from(-0.0).isZero());
-
-        Assertions.assertFalse(ParsedDouble.from(1.0).isZero());
-        Assertions.assertFalse(ParsedDouble.from(-1.0).isZero());
-
-        Assertions.assertFalse(ParsedDouble.from(Double.MIN_NORMAL).isZero());
-        Assertions.assertFalse(ParsedDouble.from(-Double.MIN_NORMAL).isZero());
-
-        Assertions.assertFalse(ParsedDouble.from(Double.MAX_VALUE).isZero());
-        Assertions.assertFalse(ParsedDouble.from(-Double.MIN_VALUE).isZero());
-    }
-
-    @Test
-    void testRound_one() {
-        // arrange
-        final ParsedDouble a = ParsedDouble.from(1e-10);
-        final ParsedDouble b = ParsedDouble.from(-1);
-        final ParsedDouble c = ParsedDouble.from(1e10);
-
-        // act/assert
-        assertParsedDouble(a.round(-11), false, "1", -10);
-        assertParsedDouble(a.round(-10), false, "1", -10);
-        assertParsedDouble(a.round(-9), false, "0", 0);
-
-        assertParsedDouble(b.round(-1), true, "1", 0);
-        assertParsedDouble(b.round(0), true, "1", 0);
-        assertParsedDouble(b.round(1), false, "0", 0);
-
-        assertParsedDouble(c.round(9), false, "1", 10);
-        assertParsedDouble(c.round(10), false, "1", 10);
-        assertParsedDouble(c.round(11), false, "0", 0);
-    }
-
-    @Test
-    void testRound_nine() {
-        // arrange
-        final ParsedDouble a = ParsedDouble.from(9e-10);
-        final ParsedDouble b = ParsedDouble.from(-9);
-        final ParsedDouble c = ParsedDouble.from(9e10);
-
-        // act/assert
-        assertParsedDouble(a.round(-11), false, "9", -10);
-        assertParsedDouble(a.round(-10), false, "9", -10);
-        assertParsedDouble(a.round(-9), false, "1", -9);
-
-        assertParsedDouble(b.round(-1), true, "9", 0);
-        assertParsedDouble(b.round(0), true, "9", 0);
-        assertParsedDouble(b.round(1), true, "1", 1);
-
-        assertParsedDouble(c.round(9), false, "9", 10);
-        assertParsedDouble(c.round(10), false, "9", 10);
-        assertParsedDouble(c.round(11), false, "1", 11);
-    }
-
-    @Test
-    void testRound_mixed() {
-        // arrange
-        final ParsedDouble a = ParsedDouble.from(9.94e-10);
-        final ParsedDouble b = ParsedDouble.from(-3.1415);
-        final ParsedDouble c = ParsedDouble.from(5.55e10);
-
-        // act/assert
-        assertParsedDouble(a.round(-13), false, "994", -12);
-        assertParsedDouble(a.round(-12), false, "994", -12);
-        assertParsedDouble(a.round(-11), false, "99", -11);
-        assertParsedDouble(a.round(-10), false, "1", -9);
-        assertParsedDouble(a.round(-9), false, "1", -9);
-        assertParsedDouble(a.round(-8), false, "0", 0);
-
-        assertParsedDouble(b.round(-5), true, "31415", -4);
-        assertParsedDouble(b.round(-4), true, "31415", -4);
-        assertParsedDouble(b.round(-3), true, "3142", -3);
-        assertParsedDouble(b.round(-2), true, "314", -2);
-        assertParsedDouble(b.round(-1), true, "31", -1);
-        assertParsedDouble(b.round(0), true, "3", 0);
-        assertParsedDouble(b.round(1), false, "0", 0);
-        assertParsedDouble(b.round(2), false, "0", 0);
-
-        assertParsedDouble(c.round(7), false, "555", 8);
-        assertParsedDouble(c.round(8), false, "555", 8);
-        assertParsedDouble(c.round(9), false, "56", 9);
-        assertParsedDouble(c.round(10), false, "6", 10);
-        assertParsedDouble(c.round(11), false, "1", 11);
-        assertParsedDouble(c.round(12), false, "0", 0);
-    }
-
-    @Test
-    void testMaxPrecision() {
-        // arrange
-        final ParsedDouble d = ParsedDouble.from(1.02576552);
-
-        // act
-        assertParsedDouble(d.maxPrecision(10), false, "102576552", -8);
-        assertParsedDouble(d.maxPrecision(9), false, "102576552", -8);
-        assertParsedDouble(d.maxPrecision(8), false, "10257655", -7);
-        assertParsedDouble(d.maxPrecision(7), false, "1025766", -6);
-        assertParsedDouble(d.maxPrecision(6), false, "102577", -5);
-        assertParsedDouble(d.maxPrecision(5), false, "10258", -4);
-        assertParsedDouble(d.maxPrecision(4), false, "1026", -3);
-        assertParsedDouble(d.maxPrecision(3), false, "103", -2);
-        assertParsedDouble(d.maxPrecision(2), false, "1", 0);
-        assertParsedDouble(d.maxPrecision(1), false, "1", 0);
-    }
-
-    @Test
-    void testMaxPrecision_carry() {
-        // arrange
-        final ParsedDouble d = ParsedDouble.from(-999.0999e50);
-
-        // act
-        assertParsedDouble(d.maxPrecision(8), true, "9990999", 46);
-        assertParsedDouble(d.maxPrecision(7), true, "9990999", 46);
-        assertParsedDouble(d.maxPrecision(6), true, "9991", 49);
-        assertParsedDouble(d.maxPrecision(5), true, "9991", 49);
-        assertParsedDouble(d.maxPrecision(4), true, "9991", 49);
-        assertParsedDouble(d.maxPrecision(3), true, "999", 50);
-        assertParsedDouble(d.maxPrecision(2), true, "1", 53);
-        assertParsedDouble(d.maxPrecision(1), true, "1", 53);
-    }
-
-    @Test
-    void testMaxPrecision_halfEvenRounding() {
-        // act/assert
-        // Test values taken from RoundingMode.HALF_EVEN javadocs
-        assertParsedDouble(ParsedDouble.from(5.5).maxPrecision(1), false, "6", 
0);
-        assertParsedDouble(ParsedDouble.from(2.5).maxPrecision(1), false, "2", 
0);
-        assertParsedDouble(ParsedDouble.from(1.6).maxPrecision(1), false, "2", 
0);
-        assertParsedDouble(ParsedDouble.from(1.1).maxPrecision(1), false, "1", 
0);
-        assertParsedDouble(ParsedDouble.from(1.0).maxPrecision(1), false, "1", 
0);
-
-        assertParsedDouble(ParsedDouble.from(-1.0).maxPrecision(1), true, "1", 
0);
-        assertParsedDouble(ParsedDouble.from(-1.1).maxPrecision(1), true, "1", 
0);
-        assertParsedDouble(ParsedDouble.from(-1.6).maxPrecision(1), true, "2", 
0);
-        assertParsedDouble(ParsedDouble.from(-2.5).maxPrecision(1), true, "2", 
0);
-        assertParsedDouble(ParsedDouble.from(-5.5).maxPrecision(1), true, "6", 
0);
-    }
-
-    @Test
-    void testMaxPrecision_singleDigits() {
-        // act
-        assertParsedDouble(ParsedDouble.from(9.0).maxPrecision(1), false, "9", 
0);
-        assertParsedDouble(ParsedDouble.from(1.0).maxPrecision(1), false, "1", 
0);
-        assertParsedDouble(ParsedDouble.from(0.0).maxPrecision(1), false, "0", 
0);
-        assertParsedDouble(ParsedDouble.from(-0.0).maxPrecision(1), true, "0", 
0);
-        assertParsedDouble(ParsedDouble.from(-1.0).maxPrecision(1), true, "1", 
0);
-        assertParsedDouble(ParsedDouble.from(-9.0).maxPrecision(1), true, "9", 
0);
-    }
-
-    @Test
-    void testMaxPrecision_random() {
-        // arrange
-        final UniformRandomProvider rand = 
RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP, 0L);
-
-        double d;
-        int precision;
-        ParsedDouble result;
-        MathContext ctx;
-        for (int i = 0; i < 10_000; ++i) {
-            d = createRandomDouble(rand);
-            precision = rand.nextInt(20) + 1;
-            ctx = new MathContext(precision, RoundingMode.HALF_EVEN);
-
-            // act
-            result = ParsedDouble.from(d).maxPrecision(precision);
-
-            // assert
-            Assertions.assertEquals(new BigDecimal(Double.toString(d), 
ctx).doubleValue(),
-                    Double.parseDouble(result.toScientificString(true)));
-        }
-    }
-
-    @Test
-    void testMaxPrecision_invalidArg() {
-        // arrange
-        final ParsedDouble d = ParsedDouble.from(10);
-        final String baseMsg = "Precision must be greater than zero; was ";
-
-        // act/assert
-        GeometryTestUtils.assertThrowsWithMessage(
-                () -> d.maxPrecision(0), IllegalArgumentException.class, 
baseMsg + "0");
-        GeometryTestUtils.assertThrowsWithMessage(
-                () -> d.maxPrecision(-1), IllegalArgumentException.class, 
baseMsg + "-1");
-    }
-
-    @Test
-    void testToPlainString() {
-        // act/assert
-        checkToPlainString(0.0, "0.0", "0");
-        checkToPlainString(1.0, "1.0", "1");
-        checkToPlainString(1.5, "1.5");
-
-        checkToPlainString(0.000123, "0.000123");
-        checkToPlainString(12300, "12300.0", "12300");
-
-        checkToPlainString(Math.PI, "3.141592653589793");
-        checkToPlainString(Math.E, "2.718281828459045");
-
-        checkToPlainString(12345.6789, "12345.6789");
-        checkToPlainString(1.23e12, "1230000000000.0", "1230000000000");
-        checkToPlainString(1.23e-12, "0.00000000000123");
-    }
-
-    @Test
-    void testToScientificString() {
-        // act/assert
-        checkToScientificString(0.0, "0.0", "0");
-        checkToScientificString(1.0, "1.0", "1");
-        checkToScientificString(1.5, "1.5");
-
-        checkToScientificString(0.000123, "1.23E-4");
-        checkToScientificString(12300, "1.23E4");
-
-        checkToScientificString(Math.PI, "3.141592653589793");
-        checkToScientificString(Math.E, "2.718281828459045");
-
-        checkToScientificString(Double.MAX_VALUE, "1.7976931348623157E308");
-        checkToScientificString(Double.MIN_VALUE, "4.9E-324");
-        checkToScientificString(Double.MIN_NORMAL, "2.2250738585072014E-308");
-    }
-
-    @Test
-    void testToEngineeringString() {
-        // act/assert
-        checkToEngineeringString(0.0, "0.0", "0");
-        checkToEngineeringString(1.0, "1.0", "1");
-        checkToEngineeringString(1.5, "1.5");
-
-        checkToEngineeringString(10, "10.0", "10");
-
-        checkToEngineeringString(0.000000123, "123.0E-9", "123E-9");
-        checkToEngineeringString(12300000, "12.3E6");
-
-        checkToEngineeringString(Math.PI, "3.141592653589793");
-        checkToEngineeringString(Math.E, "2.718281828459045");
-
-        checkToEngineeringString(Double.MAX_VALUE, "179.76931348623157E306");
-        checkToEngineeringString(Double.MIN_VALUE, "4.9E-324");
-        checkToEngineeringString(Double.MIN_NORMAL, "22.250738585072014E-309");
-    }
-
-    @Test
-    void testStringMethodAccuracy_sequence() {
-        // arrange
-        final double min = -1000;
-        final double max = 1000;
-        final double delta = 0.1;
-
-        Assertions.assertEquals(10.0, 
Double.parseDouble(ParsedDouble.from(10.0).toEngineeringString(true)));
-
-        for (double d = min; d <= max; d += delta) {
-            // act/assert
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toScientificString(true)));
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toScientificString(false)));
-
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toEngineeringString(true)));
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toEngineeringString(false)));
-
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toPlainString(true)));
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toPlainString(false)));
-        }
-    }
-
-    @Test
-    void testStringMethodAccuracy_random() {
-        // arrange
-        final UniformRandomProvider rand = 
RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP, 0L);
-
-        double d;
-        for (int i = 0; i < 10_000; ++i) {
-            d = createRandomDouble(rand);
-
-            // act/assert
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toScientificString(true)));
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toScientificString(false)));
-
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toEngineeringString(true)));
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toEngineeringString(false)));
-
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toPlainString(true)));
-            Assertions.assertEquals(d, 
Double.parseDouble(ParsedDouble.from(d).toPlainString(false)));
-        }
-    }
-
-    private static void checkFrom(final double d, final String digits, final 
int exponent) {
-        final boolean negative = Math.signum(d) < 0;
-
-        assertParsedDouble(ParsedDouble.from(d), negative, digits, exponent);
-        assertParsedDouble(ParsedDouble.from(-d), !negative, digits, exponent);
-    }
-
-    private static void checkToPlainString(final double d, final String 
expected) {
-        checkToPlainString(d, expected, expected);
-    }
-
-    private static void checkToPlainString(final double d, final String 
withPlaceholder,
-            final String withoutPlaceholder) {
-        checkToStringMethod(d, withPlaceholder, withoutPlaceholder, 
ParsedDouble::toPlainString);
-    }
-
-    private static void checkToScientificString(final double d, final String 
expected) {
-        checkToScientificString(d, expected, expected);
-    }
-
-    private static void checkToScientificString(final double d, final String 
withPlaceholder,
-            final String withoutPlaceholder) {
-        checkToStringMethod(d, withPlaceholder, withoutPlaceholder, 
ParsedDouble::toScientificString);
-    }
-
-    private static void checkToEngineeringString(final double d, final String 
expected) {
-        checkToEngineeringString(d, expected, expected);
-    }
-
-    private static void checkToEngineeringString(final double d, final String 
withPlaceholder,
-            final String withoutPlaceholder) {
-        checkToStringMethod(d, withPlaceholder, withoutPlaceholder, 
ParsedDouble::toEngineeringString);
-
-        // check the exponent value to make sure it is a multiple of 3
-        final String pos = ParsedDouble.from(d).toEngineeringString(true);
-        final int posEIdx = pos.indexOf('E');
-        if (posEIdx > -1) {
-            Assertions.assertEquals(0, Integer.parseInt(pos.substring(posEIdx 
+ 1)) % 3);
-        }
-
-        final String neg = ParsedDouble.from(-d).toEngineeringString(true);
-        final int negEIdx = neg.indexOf('E');
-        if (negEIdx > -1) {
-            Assertions.assertEquals(0, Integer.parseInt(neg.substring(negEIdx 
+ 1)) % 3);
-        }
-    }
-
-    private static void checkToStringMethod(final double d, final String 
withPlaceholder,
-            final String withoutPlaceholder, final BiFunction<ParsedDouble, 
Boolean, String> fn) {
-        final ParsedDouble pos = ParsedDouble.from(d);
-
-        final String posWith = fn.apply(pos, true);
-        Assertions.assertEquals(withPlaceholder, posWith);
-        assertDoubleString(d, posWith);
-
-        final String posWithout = fn.apply(pos, false);
-        Assertions.assertEquals(withoutPlaceholder, posWithout);
-        assertDoubleString(d, posWithout);
-
-        final ParsedDouble neg = ParsedDouble.from(-d);
-
-        final String negWith = fn.apply(neg, true);
-        Assertions.assertEquals("-" + withPlaceholder, negWith);
-        assertDoubleString(-d, negWith);
-
-        final String negWithout = fn.apply(neg, false);
-        Assertions.assertEquals("-" + withoutPlaceholder, negWithout);
-        assertDoubleString(-d, negWithout);
-    }
-
-    private static void assertDoubleString(final double d, final String str) {
-        Assertions.assertEquals(d, Double.parseDouble(str), 0.0);
-    }
-
-    private static void assertParsedDouble(final ParsedDouble parsed, final 
boolean negative, final String digits,
-            final int exponent) {
-        Assertions.assertEquals(negative, parsed.isNegative());
-        Assertions.assertEquals(digits, parsed.getDigits());
-        Assertions.assertEquals(exponent, parsed.getExponent());
-        Assertions.assertEquals(digits.length(), parsed.getPrecision());
-        Assertions.assertEquals(exponent, parsed.getScientificExponent() - 
digits.length() + 1);
-    }
-
-    private static double createRandomDouble(final UniformRandomProvider rng) {
-        final long mask = ((1L << 52) - 1) | 1L << 63;
-        final long bits = rng.nextLong() & mask;
-        final long exp = rng.nextInt(2045) + 1;
-        return Double.longBitsToDouble(bits | (exp << 52));
-    }
-}
diff --git 
a/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjBoundaryWriteHandler3D.java
 
b/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjBoundaryWriteHandler3D.java
index 0d7218f..b9d3f60 100644
--- 
a/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjBoundaryWriteHandler3D.java
+++ 
b/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjBoundaryWriteHandler3D.java
@@ -29,7 +29,6 @@ import 
org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
 import org.apache.commons.geometry.euclidean.threed.mesh.Mesh;
 import org.apache.commons.geometry.io.core.GeometryFormat;
 import org.apache.commons.geometry.io.core.output.GeometryOutput;
-import org.apache.commons.geometry.io.core.utils.DoubleFormats;
 import 
org.apache.commons.geometry.io.euclidean.threed.AbstractBoundaryWriteHandler3D;
 import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
 import org.apache.commons.geometry.io.euclidean.threed.GeometryFormat3D;
@@ -52,7 +51,7 @@ public class ObjBoundaryWriteHandler3D extends 
AbstractBoundaryWriteHandler3D {
     private String lineSeparator = DEFAULT_LINE_SEPARATOR;
 
     /** Double format function. */
-    private DoubleFunction<String> doubleFormat = 
DoubleFormats.DOUBLE_TO_STRING;
+    private DoubleFunction<String> doubleFormat = Double::toString;
 
     /** Batch size used for mesh buffer creation. */
     private int meshBufferBatchSize = DEFAULT_MESH_BUFFER_BATCH_SIZE;
diff --git 
a/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/txt/AbstractTextBoundaryWriteHandler3D.java
 
b/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/txt/AbstractTextBoundaryWriteHandler3D.java
index a3f424b..58408b4 100644
--- 
a/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/txt/AbstractTextBoundaryWriteHandler3D.java
+++ 
b/commons-geometry-io-euclidean/src/main/java/org/apache/commons/geometry/io/euclidean/threed/txt/AbstractTextBoundaryWriteHandler3D.java
@@ -27,7 +27,6 @@ import java.util.stream.Stream;
 
 import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
 import org.apache.commons.geometry.io.core.output.GeometryOutput;
-import org.apache.commons.geometry.io.core.utils.DoubleFormats;
 import 
org.apache.commons.geometry.io.euclidean.threed.AbstractBoundaryWriteHandler3D;
 import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
 
@@ -47,7 +46,7 @@ public abstract class AbstractTextBoundaryWriteHandler3D 
extends AbstractBoundar
     private String lineSeparator = DEFAULT_LINE_SEPARATOR;
 
     /** Double format function. */
-    private DoubleFunction<String> doubleFormat = 
DoubleFormats.DOUBLE_TO_STRING;
+    private DoubleFunction<String> doubleFormat = Double::toString;
 
     /** Get the text output default charset, used if the output does not
      * specify a charset.
diff --git 
a/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjBoundaryWriteHandler3DTest.java
 
b/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjBoundaryWriteHandler3DTest.java
index 2c54d7e..b3b474f 100644
--- 
a/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjBoundaryWriteHandler3DTest.java
+++ 
b/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjBoundaryWriteHandler3DTest.java
@@ -19,15 +19,17 @@ package org.apache.commons.geometry.io.euclidean.threed.obj;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 import java.util.stream.Collectors;
 
 import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.geometry.euclidean.threed.mesh.SimpleTriangleMesh;
 import org.apache.commons.geometry.io.core.output.StreamGeometryOutput;
-import org.apache.commons.geometry.io.core.utils.DoubleFormats;
 import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
 import org.apache.commons.geometry.io.euclidean.threed.FacetDefinitions;
 import org.apache.commons.geometry.io.euclidean.threed.GeometryFormat3D;
@@ -57,14 +59,18 @@ class ObjBoundaryWriteHandler3DTest {
         Assertions.assertEquals(GeometryFormat3D.OBJ, handler.getFormat());
         Assertions.assertEquals(StandardCharsets.UTF_8, 
handler.getDefaultCharset());
         Assertions.assertEquals("\n", handler.getLineSeparator());
-        Assertions.assertSame(DoubleFormats.DOUBLE_TO_STRING, 
handler.getDoubleFormat());
+        Assertions.assertNotNull(handler.getDoubleFormat());
         Assertions.assertEquals(-1, handler.getMeshBufferBatchSize());
     }
 
     @Test
     void testWriteFacets() throws IOException {
+        // arrange
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0#####", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+
         // act
-        handler.setDoubleFormat(DoubleFormats.createDefault(0, -6));
+        handler.setDoubleFormat(fmt::format);
         handler.writeFacets(FACETS, new StreamGeometryOutput(out));
 
         // assert
@@ -80,8 +86,12 @@ class ObjBoundaryWriteHandler3DTest {
 
     @Test
     void testWriteFacets_usesOutputCharset() throws IOException {
+        // arrange
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0#####", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+
         // act
-        handler.setDoubleFormat(DoubleFormats.createDefault(0, -6));
+        handler.setDoubleFormat(fmt::format);
         handler.writeFacets(FACETS, new StreamGeometryOutput(out, null, 
StandardCharsets.UTF_16));
 
         // assert
@@ -98,9 +108,13 @@ class ObjBoundaryWriteHandler3DTest {
     @Test
     void testWriteFacets_customConfig() throws IOException {
         // arrange
+        // arrange
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+
         handler.setDefaultCharset(StandardCharsets.UTF_16);
         handler.setLineSeparator("\r\n");
-        handler.setDoubleFormat(DoubleFormats.createDefault(0, -1));
+        handler.setDoubleFormat(fmt::format);
         handler.setMeshBufferBatchSize(1);
 
         // act
@@ -147,9 +161,13 @@ class ObjBoundaryWriteHandler3DTest {
                 .map(f -> FacetDefinitions.toPolygon(f, TEST_PRECISION))
                 .collect(Collectors.toList()));
 
+        // arrange
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+
         handler.setDefaultCharset(StandardCharsets.UTF_16);
         handler.setLineSeparator("\r\n");
-        handler.setDoubleFormat(DoubleFormats.createDefault(0, -1));
+        handler.setDoubleFormat(fmt::format);
         handler.setMeshBufferBatchSize(1);
 
         // act
diff --git 
a/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjWriterTest.java
 
b/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjWriterTest.java
index 5e32a16..61d58cc 100644
--- 
a/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjWriterTest.java
+++ 
b/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/obj/ObjWriterTest.java
@@ -19,7 +19,10 @@ package org.apache.commons.geometry.io.euclidean.threed.obj;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.UncheckedIOException;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
 import java.util.Arrays;
+import java.util.Locale;
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.GeometryTestUtils;
@@ -27,7 +30,6 @@ import 
org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
 import org.apache.commons.geometry.euclidean.threed.Planes;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.geometry.euclidean.threed.mesh.SimpleTriangleMesh;
-import org.apache.commons.geometry.io.core.utils.DoubleFormats;
 import org.apache.commons.geometry.io.euclidean.threed.SimpleFacetDefinition;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.jupiter.api.Assertions;
@@ -48,7 +50,7 @@ class ObjWriterTest {
         // act/assert
         try (ObjWriter objWriter = new ObjWriter(writer)) {
             Assertions.assertEquals("\n", objWriter.getLineSeparator());
-            Assertions.assertSame(DoubleFormats.DOUBLE_TO_STRING, 
objWriter.getDoubleFormat());
+            Assertions.assertNotNull(objWriter.getDoubleFormat());
             Assertions.assertEquals(0, objWriter.getVertexCount());
             Assertions.assertEquals(0, objWriter.getVertexNormalCount());
         }
@@ -92,10 +94,12 @@ class ObjWriterTest {
     void testSetDecimalFormat() throws IOException {
         // arrange
         final StringWriter writer = new StringWriter();
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
 
         // act
         try (ObjWriter objWriter = new ObjWriter(writer)) {
-            objWriter.setDoubleFormat(DoubleFormats.createDefault(0, -1));
+            objWriter.setDoubleFormat(fmt::format);
 
             objWriter.writeVertex(Vector3D.of(1.09, 2.05, 3.06));
         }
@@ -156,12 +160,16 @@ class ObjWriterTest {
         // arrange
         final StringWriter writer = new StringWriter();
 
+        // arrange
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+
         // act
         final int index1;
         final int index2;
         final int count;
         try (ObjWriter objWriter = new ObjWriter(writer)) {
-            objWriter.setDoubleFormat(DoubleFormats.createDefault(0, -1));
+            objWriter.setDoubleFormat(fmt::format);
 
             index1 = objWriter.writeVertex(Vector3D.of(1.09, 2.1, 3.005));
             index2 = objWriter.writeVertex(Vector3D.of(0.06, 10, 12));
@@ -182,13 +190,15 @@ class ObjWriterTest {
     void testWriteNormal() throws IOException {
         // arrange
         final StringWriter writer = new StringWriter();
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
 
         // act
         final int index1;
         final int index2;
         final int count;
         try (ObjWriter objWriter = new ObjWriter(writer)) {
-            objWriter.setDoubleFormat(DoubleFormats.createDefault(0, -1));
+            objWriter.setDoubleFormat(fmt::format);
 
             index1 = objWriter.writeVertexNormal(Vector3D.of(1.09, 2.1, 
3.005));
             index2 = objWriter.writeVertexNormal(Vector3D.of(0.06, 10, 12));
diff --git 
a/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/stl/TextStlWriterTest.java
 
b/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/stl/TextStlWriterTest.java
index 4f9eba3..8cf53d9 100644
--- 
a/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/stl/TextStlWriterTest.java
+++ 
b/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/stl/TextStlWriterTest.java
@@ -18,16 +18,18 @@ package org.apache.commons.geometry.io.euclidean.threed.stl;
 
 import java.io.IOException;
 import java.io.StringWriter;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 
 import org.apache.commons.geometry.core.GeometryTestUtils;
 import org.apache.commons.geometry.euclidean.threed.ConvexPolygon3D;
 import org.apache.commons.geometry.euclidean.threed.Planes;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.geometry.io.core.test.CloseCountWriter;
-import org.apache.commons.geometry.io.core.utils.DoubleFormats;
 import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
 import org.apache.commons.geometry.io.euclidean.threed.SimpleFacetDefinition;
 import org.apache.commons.numbers.core.Precision;
@@ -47,7 +49,7 @@ class TextStlWriterTest {
     void testDefaultProperties() throws IOException {
         // act/assert
         try (TextStlWriter writer = new TextStlWriter(out)) {
-            Assertions.assertSame(DoubleFormats.DOUBLE_TO_STRING, 
writer.getDoubleFormat());
+            Assertions.assertNotNull(writer.getDoubleFormat());
             Assertions.assertEquals("\n", writer.getLineSeparator());
         }
     }
@@ -540,9 +542,12 @@ class TextStlWriterTest {
                 Vector3D.ZERO, Vector3D.of(1.0 / 3.0, 0, 0), Vector3D.of(0, 
1.0 / 3.0, 0));
         final Vector3D normal = Vector3D.Unit.PLUS_Z;
 
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0##", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+
         try (TextStlWriter writer = new TextStlWriter(out)) {
 
-            writer.setDoubleFormat(DoubleFormats.createDefault(0, -3));
+            writer.setDoubleFormat(fmt::format);
             writer.setLineSeparator("\r\n");
 
             // act
diff --git 
a/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/txt/TextBoundaryWriteHandler3DTest.java
 
b/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/txt/TextBoundaryWriteHandler3DTest.java
index 575e39f..8b6e21f 100644
--- 
a/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/txt/TextBoundaryWriteHandler3DTest.java
+++ 
b/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/txt/TextBoundaryWriteHandler3DTest.java
@@ -19,15 +19,17 @@ package org.apache.commons.geometry.io.euclidean.threed.txt;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 
 import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
 import org.apache.commons.geometry.euclidean.threed.Planes;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.geometry.io.core.output.StreamGeometryOutput;
 import org.apache.commons.geometry.io.core.test.CloseCountOutputStream;
-import org.apache.commons.geometry.io.core.utils.DoubleFormats;
 import org.apache.commons.geometry.io.euclidean.threed.FacetDefinition;
 import org.apache.commons.geometry.io.euclidean.threed.GeometryFormat3D;
 import org.apache.commons.geometry.io.euclidean.threed.SimpleFacetDefinition;
@@ -66,7 +68,7 @@ class TextBoundaryWriteHandler3DTest {
         Assertions.assertEquals("\n", handler.getLineSeparator());
         Assertions.assertEquals(" ", handler.getVertexComponentSeparator());
         Assertions.assertEquals("; ", handler.getVertexSeparator());
-        Assertions.assertSame(DoubleFormats.DOUBLE_TO_STRING, 
handler.getDoubleFormat());
+        Assertions.assertNotNull(handler.getDoubleFormat());
         Assertions.assertEquals(-1, handler.getFacetVertexCount());
     }
 
@@ -103,10 +105,13 @@ class TextBoundaryWriteHandler3DTest {
     @Test
     void testWriteFacets_customConfiguration() throws IOException {
         // arrange
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+
         final TextBoundaryWriteHandler3D handler = new 
TextBoundaryWriteHandler3D();
         handler.setDefaultCharset(StandardCharsets.UTF_16);
         handler.setLineSeparator("\r\n");
-        handler.setDoubleFormat(DoubleFormats.createDefault(0, -1));
+        handler.setDoubleFormat(fmt::format);
         handler.setVertexComponentSeparator("|");
         handler.setVertexSeparator(" | ");
         handler.setFacetVertexCount(4);
@@ -140,10 +145,14 @@ class TextBoundaryWriteHandler3DTest {
     @Test
     void testWriteBoundarySource_customConfiguration() throws IOException {
         // arrange
+        // arrange
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+
         final TextBoundaryWriteHandler3D handler = new 
TextBoundaryWriteHandler3D();
         handler.setDefaultCharset(StandardCharsets.UTF_16);
         handler.setLineSeparator("\r\n");
-        handler.setDoubleFormat(DoubleFormats.createDefault(0, -1));
+        handler.setDoubleFormat(fmt::format);
         handler.setVertexComponentSeparator("|");
         handler.setVertexSeparator(" | ");
         handler.setFacetVertexCount(4);
diff --git 
a/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/txt/TextFacetDefinitionWriterTest.java
 
b/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/txt/TextFacetDefinitionWriterTest.java
index 9329e37..8d0b516 100644
--- 
a/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/txt/TextFacetDefinitionWriterTest.java
+++ 
b/commons-geometry-io-euclidean/src/test/java/org/apache/commons/geometry/io/euclidean/threed/txt/TextFacetDefinitionWriterTest.java
@@ -18,9 +18,12 @@ package org.apache.commons.geometry.io.euclidean.threed.txt;
 
 import java.io.IOException;
 import java.io.StringWriter;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 
 import org.apache.commons.geometry.core.GeometryTestUtils;
 import org.apache.commons.geometry.euclidean.threed.BoundarySource3D;
@@ -28,7 +31,6 @@ import 
org.apache.commons.geometry.euclidean.threed.ConvexPolygon3D;
 import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
 import org.apache.commons.geometry.euclidean.threed.Planes;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
-import org.apache.commons.geometry.io.core.utils.DoubleFormats;
 import org.apache.commons.geometry.io.euclidean.threed.SimpleFacetDefinition;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.jupiter.api.Assertions;
@@ -56,7 +58,7 @@ class TextFacetDefinitionWriterTest {
     void testPropertyDefaults() {
         // act/assert
         Assertions.assertEquals("\n", fdWriter.getLineSeparator());
-        Assertions.assertSame(DoubleFormats.DOUBLE_TO_STRING, 
fdWriter.getDoubleFormat());
+        Assertions.assertNotNull(fdWriter.getDoubleFormat());
         Assertions.assertEquals(" ", fdWriter.getVertexComponentSeparator());
         Assertions.assertEquals("; ", fdWriter.getVertexSeparator());
         Assertions.assertEquals(-1, fdWriter.getFacetVertexCount());
@@ -188,12 +190,15 @@ class TextFacetDefinitionWriterTest {
     @Test
     void testWriteFacetDefinition() throws IOException {
         // arrange
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0##", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+
         final SimpleFacetDefinition f1 = new 
SimpleFacetDefinition(Arrays.asList(
                 Vector3D.ZERO, Vector3D.of(0.5, 0, 0), Vector3D.of(0, -0.5, 
0)));
         final SimpleFacetDefinition f2 = new 
SimpleFacetDefinition(Arrays.asList(
                 Vector3D.of(0.5, 0.7, 1.2), Vector3D.of(10.01, -4, 2), 
Vector3D.of(-10.0 / 3.0, 0, 0), Vector3D.ZERO));
 
-        fdWriter.setDoubleFormat(DoubleFormats.createDefault(0, -3));
+        fdWriter.setDoubleFormat(fmt::format);
 
         // act
         fdWriter.write(f1);
@@ -299,6 +304,8 @@ class TextFacetDefinitionWriterTest {
     @Test
     void testWriteBoundarySource_alternativeFormatting() throws IOException {
         // arrange
+        final DecimalFormat fmt =
+                new DecimalFormat("0.0", 
DecimalFormatSymbols.getInstance(Locale.ENGLISH));
         final ConvexPolygon3D poly1 = 
Planes.convexPolygonFromVertices(Arrays.asList(
                 Vector3D.ZERO, Vector3D.of(0, 0, -0.5901), Vector3D.of(0, 
-0.501, 0)
             ), TEST_PRECISION);
@@ -306,7 +313,7 @@ class TextFacetDefinitionWriterTest {
                 Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 0), 
Vector3D.of(0, 1, 0)
             ), TEST_PRECISION);
 
-        fdWriter.setDoubleFormat(DoubleFormats.createDefault(0, -1));
+        fdWriter.setDoubleFormat(fmt::format);
 
         fdWriter.setFacetVertexCount(3);
         fdWriter.setLineSeparator("\r\n");

Reply via email to