TestNumberFormatUtil.java
/*
* Copyright 2016 The Apache Software Foundation.
*
* Licensed 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.pdfbox.util;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.regex.Pattern;
import org.junit.jupiter.api.Test;
/**
*
* @author Michael Doswald
*/
class TestNumberFormatUtil
{
private final byte[] buffer = new byte[64];
@Test
void testFormatOfIntegerValues()
{
assertEquals(2, NumberFormatUtil.formatFloatFast(51, 5, buffer));
assertArrayEquals(new byte[]{'5', '1'}, Arrays.copyOfRange(buffer, 0, 2));
assertEquals(3, NumberFormatUtil.formatFloatFast(-51, 5, buffer));
assertArrayEquals(new byte[]{'-', '5', '1'}, Arrays.copyOfRange(buffer, 0, 3));
assertEquals(1, NumberFormatUtil.formatFloatFast(0, 5, buffer));
assertArrayEquals(new byte[]{'0'}, Arrays.copyOfRange(buffer, 0, 1));
assertEquals(19, NumberFormatUtil.formatFloatFast(Long.MAX_VALUE, 5, buffer));
assertArrayEquals(new byte[]{'9', '2', '2', '3', '3', '7', '2', '0', '3', '6', '8', '5',
'4', '7', '7', '5', '8', '0', '7'},
Arrays.copyOfRange(buffer, 0, 19));
//Note: Integer.MAX_VALUE would be 2147483647, but when converting to float, we have
// precision errors. NumberFormat.getIntegerInstance() does also print 2147483648 for
// such a float
assertEquals(10, NumberFormatUtil.formatFloatFast(Integer.MAX_VALUE, 5, buffer));
assertArrayEquals(new byte[]{'2', '1', '4', '7', '4', '8', '3', '6', '4', '8'},
Arrays.copyOfRange(buffer, 0, 10));
assertEquals(11, NumberFormatUtil.formatFloatFast(Integer.MIN_VALUE, 5, buffer));
assertArrayEquals(new byte[]{'-', '2', '1', '4', '7', '4', '8', '3', '6', '4', '8'},
Arrays.copyOfRange(buffer, 0, 11));
}
@Test
void testFormatOfRealValues()
{
assertEquals(3, NumberFormatUtil.formatFloatFast(0.7f, 5, buffer));
assertArrayEquals(new byte[]{'0', '.', '7'}, Arrays.copyOfRange(buffer, 0, 3));
assertEquals(4, NumberFormatUtil.formatFloatFast(-0.7f, 5, buffer));
assertArrayEquals(new byte[]{'-', '0', '.', '7'}, Arrays.copyOfRange(buffer, 0, 4));
assertEquals(5, NumberFormatUtil.formatFloatFast(0.003f, 5, buffer));
assertArrayEquals(new byte[]{'0', '.', '0', '0', '3'}, Arrays.copyOfRange(buffer, 0, 5));
assertEquals(6, NumberFormatUtil.formatFloatFast(-0.003f, 5, buffer));
assertArrayEquals(new byte[]{'-', '0', '.', '0', '0', '3'},
Arrays.copyOfRange(buffer, 0, 6));
}
@Test
void testFormatOfRealValuesReturnsMinusOneIfItCannotBeFormatted()
{
assertEquals(-1, NumberFormatUtil.formatFloatFast(Float.NaN, 5, buffer),
"NaN should not be formattable");
assertEquals(-1, NumberFormatUtil.formatFloatFast(Float.POSITIVE_INFINITY, 5, buffer),
"+Infinity should not be formattable");
assertEquals(-1, NumberFormatUtil.formatFloatFast(Float.NEGATIVE_INFINITY, 5, buffer),
"-Infinity should not be formattable");
assertEquals(-1, NumberFormatUtil.formatFloatFast(((float) Long.MAX_VALUE) + 1000000000000f,
5, buffer), "Too big number should not be formattable");
assertEquals(-1, NumberFormatUtil.formatFloatFast(Long.MIN_VALUE, 5, buffer),
"Too big negative number should not be formattable");
}
@Test
void testRoundingUp()
{
assertEquals(1, NumberFormatUtil.formatFloatFast(0.999999f, 5, buffer));
assertArrayEquals(new byte[]{'1'}, Arrays.copyOfRange(buffer, 0, 1));
assertEquals(4, NumberFormatUtil.formatFloatFast(0.125f, 2, buffer));
assertArrayEquals(new byte[]{'0','.','1','3'}, Arrays.copyOfRange(buffer, 0, 4));
assertEquals(2, NumberFormatUtil.formatFloatFast(-0.999999f, 5, buffer));
assertArrayEquals(new byte[]{'-','1'}, Arrays.copyOfRange(buffer, 0, 2));
}
@Test
void testRoundingDown()
{
assertEquals(4, NumberFormatUtil.formatFloatFast(0.994f, 2, buffer));
assertArrayEquals(new byte[]{'0','.','9','9'}, Arrays.copyOfRange(buffer, 0, 4));
}
/**
* Formats all floats in a defined range, parses them back with the BigDecimal constructor and
* compares them to the expected result. The test only tests a small range for performance
* reasons. It works for ranges up to at least A0 size:
*
* <ul>
* <li>PDF uses 72 dpi resolution</li>
* <li>A0 size is 841mm x 1189mm, this equals to about 2472 x 3495 in dot resolution</li>
* </ul>
*/
@Test
void testFormattingInRange()
{
//Define a range to test
BigDecimal minVal = new BigDecimal("-10");
BigDecimal maxVal = new BigDecimal("10");
BigDecimal maxDelta = BigDecimal.ZERO;
Pattern pattern = Pattern.compile("^\\-?\\d+(\\.\\d+)?$");
byte[] formatBuffer = new byte[32];
for (int maxFractionDigits = 0; maxFractionDigits <= 5; maxFractionDigits++)
{
BigDecimal increment = new BigDecimal(10).pow(-maxFractionDigits, MathContext.DECIMAL128);
for (BigDecimal value = minVal; value.compareTo(maxVal) < 0; value = value.add(increment))
{
//format with the formatFloatFast method and parse back
int byteCount = NumberFormatUtil.formatFloatFast(value.floatValue(), maxFractionDigits, formatBuffer);
assertNotEquals(-1, byteCount);
String newStringResult = new String(formatBuffer, 0, byteCount, StandardCharsets.US_ASCII);
BigDecimal formattedDecimal = new BigDecimal(newStringResult);
//create new BigDecimal with float representation. This is needed because the float
//may not represent the 'value' BigDecimal precisely, in which case the formatFloatFast
//would get a different result.
BigDecimal expectedDecimal = new BigDecimal(value.floatValue());
expectedDecimal = expectedDecimal.setScale(maxFractionDigits, RoundingMode.HALF_UP);
BigDecimal diff = formattedDecimal.subtract(expectedDecimal).abs();
assertTrue(pattern.matcher(newStringResult).matches());
//Fail if diff is greater than maxDelta.
if (diff.compareTo(maxDelta) > 0)
{
fail("Expected: " + expectedDecimal + ", actual: " + newStringResult + ", diff: " + diff);
}
}
}
}
}