NumberOutputFloatDoubleTest.java
package tools.jackson.core.unittest.io;
import java.util.Random;
import org.junit.jupiter.api.Test;
import tools.jackson.core.io.NumberOutput;
import tools.jackson.core.io.xjb.XJBWriter;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for {@link NumberOutput#outputFloat(float, char[], int)} and
* {@link NumberOutput#outputDouble(double, char[], int)}.
*
* Verifies that the NumberOutput char[] methods produce the same output as
* XJBWriter.toString, and that the returned position is correct.
*/
public class NumberOutputFloatDoubleTest
{
// ------------------------------------------------------------------
// Float
// ------------------------------------------------------------------
@Test
public void floatSimpleCases() {
assertFloatOutputMatchesToString(0f);
assertFloatOutputMatchesToString(-0f);
assertFloatOutputMatchesToString(1f);
assertFloatOutputMatchesToString(-1f);
// NaN and Infinity are now handled directly
assertFloatOutputMatchesToString(Float.NaN);
assertFloatOutputMatchesToString(Float.POSITIVE_INFINITY);
assertFloatOutputMatchesToString(Float.NEGATIVE_INFINITY);
}
@Test
public void floatBoundaryConditions() {
assertFloatOutputMatchesToString(1.0E7f);
assertFloatOutputMatchesToString(9999999.0f);
assertFloatOutputMatchesToString(0.001f);
assertFloatOutputMatchesToString(0.0009999999f);
}
@Test
public void floatMinMax() {
assertFloatOutputMatchesToString(Float.MAX_VALUE);
assertFloatOutputMatchesToString(Float.MIN_VALUE);
assertFloatOutputMatchesToString(Float.MIN_NORMAL);
assertFloatOutputMatchesToString(-Float.MAX_VALUE);
assertFloatOutputMatchesToString(-Float.MIN_VALUE);
// longest output
assertFloatOutputMatchesToString(-Float.MIN_NORMAL);
}
@Test
public void floatSubnormals() {
assertFloatOutputMatchesToString(Float.intBitsToFloat(0x00800000));
assertFloatOutputMatchesToString(Float.intBitsToFloat(0x00000001));
assertFloatOutputMatchesToString(Float.intBitsToFloat(0x00000002));
assertFloatOutputMatchesToString(Float.intBitsToFloat(0x007fffff));
}
@Test
public void floatRegression() {
assertFloatOutputMatchesToString(4.7223665E21f);
assertFloatOutputMatchesToString(8388608.0f);
assertFloatOutputMatchesToString(1.6777216E7f);
assertFloatOutputMatchesToString(3.3554436E7f);
assertFloatOutputMatchesToString(6.7131496E7f);
assertFloatOutputMatchesToString(1.9310392E-38f);
assertFloatOutputMatchesToString(-2.47E-43f);
assertFloatOutputMatchesToString(1.993244E-38f);
assertFloatOutputMatchesToString(4103.9004f);
assertFloatOutputMatchesToString(5.3399997E9f);
assertFloatOutputMatchesToString(6.0898E-39f);
assertFloatOutputMatchesToString(0.0010310042f);
assertFloatOutputMatchesToString(2.8823261E17f);
assertFloatOutputMatchesToString(7.038531E-26f);
assertFloatOutputMatchesToString(9.2234038E17f);
assertFloatOutputMatchesToString(6.7108872E7f);
assertFloatOutputMatchesToString(1.0E-44f);
assertFloatOutputMatchesToString(2.816025E14f);
assertFloatOutputMatchesToString(9.223372E18f);
assertFloatOutputMatchesToString(1.5846085E29f);
assertFloatOutputMatchesToString(1.1811161E19f);
assertFloatOutputMatchesToString(5.368709E18f);
assertFloatOutputMatchesToString(4.6143165E18f);
assertFloatOutputMatchesToString(0.007812537f);
assertFloatOutputMatchesToString(1.4E-45f);
assertFloatOutputMatchesToString(1.18697724E20f);
assertFloatOutputMatchesToString(1.00014165E-36f);
assertFloatOutputMatchesToString(200f);
assertFloatOutputMatchesToString(3.3554432E7f);
}
@Test
public void floatRounding() {
assertFloatOutputMatchesToString(3.3554448E7f);
assertFloatOutputMatchesToString(8.999999E9f);
assertFloatOutputMatchesToString(3.4366717E10f);
assertFloatOutputMatchesToString(0.33007812f);
}
@Test
public void floatLooksLikePow5() {
assertFloatOutputMatchesToString(Float.intBitsToFloat(0x5D1502F9));
assertFloatOutputMatchesToString(Float.intBitsToFloat(0x5D9502F9));
assertFloatOutputMatchesToString(Float.intBitsToFloat(0x5E1502F9));
}
@Test
public void floatWithOffset() {
float[] values = {0f, -0f, 1f, -1f, 3.14f, -3.14f, 1.0E7f, 0.001f,
Float.MAX_VALUE, Float.MIN_VALUE};
for (float f : values) {
for (int offset = 0; offset <= 4; offset++) {
assertFloatOutputWithOffset(f, offset);
}
}
// NaN and Infinity are now handled directly
assertFloatOutputWithOffset(Float.NaN, 4);
assertFloatOutputWithOffset(Float.POSITIVE_INFINITY, 4);
assertFloatOutputWithOffset(Float.NEGATIVE_INFINITY, 4);
}
@Test
public void floatRandom() {
// Round-trip correctness is covered by XJBRoundTripTest; here just verify char[] == toString
Random r = new Random(42);
for (int i = 0; i < 100_000; i++) {
assertFloatOutputMatchesToString(Float.intBitsToFloat(r.nextInt()));
}
}
// ------------------------------------------------------------------
// Double
// ------------------------------------------------------------------
@Test
public void doubleSimpleCases() {
assertDoubleOutputMatchesToString(0d);
assertDoubleOutputMatchesToString(-0d);
assertDoubleOutputMatchesToString(1d);
assertDoubleOutputMatchesToString(-1d);
// NaN and Infinity are now handled directly
assertDoubleOutputMatchesToString(Double.NaN);
assertDoubleOutputMatchesToString(Double.POSITIVE_INFINITY);
assertDoubleOutputMatchesToString(Double.NEGATIVE_INFINITY);
}
@Test
public void doubleBoundaryConditions() {
assertDoubleOutputMatchesToString(1.0E7d);
assertDoubleOutputMatchesToString(9999999.999999998d);
assertDoubleOutputMatchesToString(0.001d);
assertDoubleOutputMatchesToString(0.0009999999999999998d);
}
@Test
public void doubleMinMax() {
assertDoubleOutputMatchesToString(Double.MAX_VALUE);
assertDoubleOutputMatchesToString(Double.MIN_VALUE);
assertDoubleOutputMatchesToString(Double.MIN_NORMAL);
assertDoubleOutputMatchesToString(-Double.MAX_VALUE);
assertDoubleOutputMatchesToString(-Double.MIN_VALUE);
// longest output
assertDoubleOutputMatchesToString(-Double.MIN_NORMAL);
}
@Test
public void doubleSubnormals() {
assertDoubleOutputMatchesToString(Double.longBitsToDouble(0x0010000000000000L));
assertDoubleOutputMatchesToString(Double.longBitsToDouble(1L));
assertDoubleOutputMatchesToString(Double.longBitsToDouble(2L));
assertDoubleOutputMatchesToString(Double.longBitsToDouble(0x000FFFFFFFFFFFFFL));
}
@Test
public void doubleRegression() {
assertDoubleOutputMatchesToString(4.940656E-318d);
assertDoubleOutputMatchesToString(1.18575755E-316d);
assertDoubleOutputMatchesToString(2.989102097996E-312d);
assertDoubleOutputMatchesToString(9.0608011534336E15d);
assertDoubleOutputMatchesToString(4.708356024711512E18);
assertDoubleOutputMatchesToString(9.409340012568248E18);
assertDoubleOutputMatchesToString(1.8531501765868567E21);
assertDoubleOutputMatchesToString(-3.347727380279489E33);
assertDoubleOutputMatchesToString(1.9430376160308388E16);
assertDoubleOutputMatchesToString(-6.9741824662760956E19);
assertDoubleOutputMatchesToString(4.3816050601147837E18);
}
@Test
public void doubleRounding() {
assertDoubleOutputMatchesToString(-2.109808898695963E16);
}
@Test
public void doubleWithOffset() {
double[] values = {0d, -0d, 1d, -1d, 3.14d, -3.14d, 1.0E7d, 0.001d,
Double.MAX_VALUE, Double.MIN_VALUE, 4.9E-324, 2.2250738585072014E-308};
for (double d : values) {
for (int offset = 0; offset <= 4; offset++) {
assertDoubleOutputWithOffset(d, offset);
}
}
// NaN and Infinity are now handled directly
assertDoubleOutputWithOffset(Double.NaN, 4);
assertDoubleOutputWithOffset(Double.POSITIVE_INFINITY, 4);
assertDoubleOutputWithOffset(Double.NEGATIVE_INFINITY, 4);
}
@Test
public void doubleRandom() {
// Round-trip correctness is covered by XJBRoundTripTest; here just verify char[] == toString
Random r = new Random(42);
for (int i = 0; i < 100_000; i++) {
assertDoubleOutputMatchesToString(Double.longBitsToDouble(r.nextLong()));
assertDoubleOutputMatchesToString((r.nextDouble() - 0.5) * Math.pow(10, r.nextInt(60) - 30));
}
}
// ------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------
@Test
public void maxFloatCharsConstant() {
// Verify MAX_FLOAT_CHARS is large enough for all float outputs
float[] extremes = {Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_NORMAL,
-Float.MAX_VALUE, -Float.MIN_VALUE, 0f, -0f,
Float.intBitsToFloat(0x00000001), Float.intBitsToFloat(0x7f7fffff),
Float.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY,
1.0E7f, 0.001f, 9999999.0f};
for (float f : extremes) {
String s = XJBWriter.toString(f);
assertTrue(s.length() <= NumberOutput.MAX_FLOAT_CHARS,
() -> String.format("float %s produced '%s' (len=%d) but MAX_FLOAT_CHARS=%d",
f, s, s.length(), NumberOutput.MAX_FLOAT_CHARS));
}
}
@Test
public void maxDoubleCharsConstant() {
// Verify MAX_DOUBLE_CHARS is large enough for all double outputs
double[] extremes = {Double.MAX_VALUE, Double.MIN_VALUE, Double.MIN_NORMAL,
-Double.MAX_VALUE, -Double.MIN_VALUE, 0d, -0d,
Double.longBitsToDouble(1L), Double.longBitsToDouble(0x7fefffffffffffffL),
Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY,
1.0E7d, 0.001d, 9999999.999999998d, 4.9E-324};
for (double d : extremes) {
String s = XJBWriter.toString(d);
assertTrue(s.length() <= NumberOutput.MAX_DOUBLE_CHARS,
() -> String.format("double %s produced '%s' (len=%d) but MAX_DOUBLE_CHARS=%d",
d, s, s.length(), NumberOutput.MAX_DOUBLE_CHARS));
}
}
// ------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------
private void assertFloatOutputMatchesToString(float f) {
String expected = XJBWriter.toString(f);
char[] buf = new char[NumberOutput.MAX_FLOAT_CHARS];
int pos = NumberOutput.outputFloat(f, buf, 0);
String actual = new String(buf, 0, pos);
assertEquals(expected, actual,
() -> String.format("float %s (bits=0x%08X): toString='%s', outputFloat='%s'",
f, Float.floatToRawIntBits(f), expected, actual));
}
private void assertDoubleOutputMatchesToString(double d) {
String expected = XJBWriter.toString(d);
char[] buf = new char[NumberOutput.MAX_DOUBLE_CHARS];
int pos = NumberOutput.outputDouble(d, buf, 0);
String actual = new String(buf, 0, pos);
assertEquals(expected, actual,
() -> String.format("double %s (bits=0x%016X): toString='%s', outputDouble='%s'",
d, Double.doubleToRawLongBits(d), expected, actual));
}
private void assertFloatOutputWithOffset(float f, int offset) {
String expected = XJBWriter.toString(f);
char[] buf = new char[offset + NumberOutput.MAX_FLOAT_CHARS];
for (int i = 0; i < offset; i++) {
buf[i] = '$';
}
int pos = NumberOutput.outputFloat(f, buf, offset);
String actual = new String(buf, offset, pos - offset);
assertEquals(expected, actual,
() -> String.format("float %s with offset %d: expected='%s', got='%s'", f, offset, expected, actual));
for (int i = 0; i < offset; i++) {
assertEquals('$', buf[i], "Offset area corrupted at position " + i);
}
}
private void assertDoubleOutputWithOffset(double d, int offset) {
String expected = XJBWriter.toString(d);
char[] buf = new char[offset + NumberOutput.MAX_DOUBLE_CHARS];
for (int i = 0; i < offset; i++) {
buf[i] = '$';
}
int pos = NumberOutput.outputDouble(d, buf, offset);
String actual = new String(buf, offset, pos - offset);
assertEquals(expected, actual,
() -> String.format("double %s with offset %d: expected='%s', got='%s'", d, offset, expected, actual));
for (int i = 0; i < offset; i++) {
assertEquals('$', buf[i], "Offset area corrupted at position " + i);
}
}
}