XJBRoundTripTest.java

package tools.jackson.core.unittest.io.xjb;

import org.junit.jupiter.api.Test;
import tools.jackson.core.io.xjb.XJBWriter;

import java.util.Random;

import static org.junit.jupiter.api.Assertions.*;

/**
 * Round-trip correctness tests for XJB float/double to string conversion.
 * Verifies that parsing the string back yields the exact same bit pattern.
 */
public class XJBRoundTripTest {

  @Test
  public void floatSpecials() {
    float[] floatSpecials = {
        0f, -0f, 1f, -1f, 0.1f, -0.1f, 100f, 123.45f, 1000f, 123000f,
        1.2e23f, 1e10f, 1e-10f, 0.0001f, 0.001f, 0.0123f, 0.000123f,
        Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_NORMAL,
        -Float.MIN_VALUE, -Float.MAX_VALUE,
        123456789f, 1.23456789E10f, 9999999f, 99999990f,
        Float.intBitsToFloat(1), Float.intBitsToFloat(2),
        Float.intBitsToFloat(0x7f7fffff),
    };
    for (float f : floatSpecials) {
      assertFloatRoundTrip(f);
    }
  }

  @Test
  public void doubleSpecials() {
    double[] doubleSpecials = {
        0d, -0d, 1d, -1d, 0.1d, -0.1d, 100d, 123.45d, 1000d, 123000d,
        1.2e23d, 1e10d, 1e-10d, 0.0001d, 0.001d, 0.0123d, 0.000123d,
        Double.MIN_VALUE, Double.MAX_VALUE, Double.MIN_NORMAL,
        -Double.MIN_VALUE, -Double.MAX_VALUE,
        123456789012345d, 1.23456789012345E100d, 9999999999999999d,
        Double.longBitsToDouble(1L), Double.longBitsToDouble(2L),
        Double.longBitsToDouble(0x7fefffffffffffffL),
        4.9E-324, 2.2250738585072014E-308,
    };
    for (double d : doubleSpecials) {
      assertDoubleRoundTrip(d);
    }
  }

  @Test
  public void floatRandomFinite() {
    Random r = new Random(42);
    for (int i = 0; i < 2_000_000; i++) {
      int bits = r.nextInt();
      float f = Float.intBitsToFloat(bits);
      if (Float.isFinite(f)) {
        assertFloatRoundTrip(f);
      }
    }
  }

  @Test
  public void doubleRandomFinite() {
    Random r = new Random(42);
    for (int i = 0; i < 2_000_000; i++) {
      long bits = r.nextLong();
      double d = Double.longBitsToDouble(bits);
      if (Double.isFinite(d)) {
        assertDoubleRoundTrip(d);
      }
    }
  }

  @Test
  public void randomNiceDecimals() {
    Random r = new Random(42);
    for (int i = 0; i < 500_000; i++) {
      double d = (r.nextDouble() - 0.5) * Math.pow(10, r.nextInt(60) - 30);
      assertDoubleRoundTrip(d);
      float f = (float) ((r.nextDouble() - 0.5) * Math.pow(10, r.nextInt(60) - 30));
      if (Float.isFinite(f)) {
        assertFloatRoundTrip(f);
      }
    }
  }

  private void assertFloatRoundTrip(float f) {
    String s = XJBWriter.toString(f);
    float roundTripped = Float.parseFloat(s);
    assertEquals(Float.floatToRawIntBits(f), Float.floatToRawIntBits(roundTripped),
        () -> "Round-trip failed for float bits=" + Integer.toHexString(Float.floatToRawIntBits(f))
            + " value=" + f + " string=\"" + s + "\"");
  }

  private void assertDoubleRoundTrip(double d) {
    String s = XJBWriter.toString(d);
    double roundTripped = Double.parseDouble(s);
    assertEquals(Double.doubleToRawLongBits(d), Double.doubleToRawLongBits(roundTripped),
        () -> "Round-trip failed for double bits=" + Long.toHexString(Double.doubleToRawLongBits(d))
            + " value=" + d + " string=\"" + s + "\"");
  }
}