Skip to content
Permalink
Browse files
RNG-169: Update array seed conversion to use optimum seed length
Avoid duplication of the input byte[] to zero fill it to a length
modulus 4 or 8 for conversion by the NumberFactory.

During array to array conversions only convert the minimum of the
required number of bytes for the native seed type, or the number of
input bytes.

Change all array conversions to use little-endian byte order.
  • Loading branch information
aherbert committed Mar 16, 2022
1 parent c3c8021 commit ca6f140ac7074fcacf1dcba32f071ba465875c41
Showing 14 changed files with 754 additions and 87 deletions.
@@ -16,26 +16,51 @@
*/
package org.apache.commons.rng.simple.internal;

import java.util.Arrays;

import org.apache.commons.rng.core.util.NumberFactory;

/**
* Creates a {@code int[]} from a {@code byte[]}.
*
* @since 1.0
*/
public class ByteArray2IntArray implements SeedConverter<byte[], int[]> {
/** Number of bytes in an {@code int}. */
private static final int INT_SIZE = 4;

public class ByteArray2IntArray implements Seed2ArrayConverter<byte[], int[]> {
/** {@inheritDoc} */
@Override
public int[] convert(byte[] seed) {
final byte[] tmp = seed.length % INT_SIZE == 0 ?
seed :
Arrays.copyOf(seed, INT_SIZE * ((seed.length + INT_SIZE - 1) / INT_SIZE));
// Full length conversion
return convertSeed(seed, SeedUtils.intSizeFromByteSize(seed.length));
}

/**
* {@inheritDoc}
*
* @since 1.5
*/
@Override
public int[] convert(byte[] seed, int outputSize) {
return convertSeed(seed, outputSize);
}

/**
* Creates an array of {@code int} values from a sequence of bytes. The integers are
* filled in little-endian order (least significant byte first).
*
* @param input Input bytes
* @param length Output length
* @return an array of {@code int}.
*/
private static int[] convertSeed(byte[] input, int length) {
final int[] output = new int[length];

// Overflow-safe minimum using long
final int n = (int) Math.min(input.length, length * (long) Integer.BYTES);
// Little-endian fill
for (int i = 0; i < n; i++) {
// i = byte index
// i >> 2 = integer index
// i & 0x3 = byte number in the integer [0, 3]
// (i & 0x3) << 3 = little-endian byte shift to the integer {0, 8, 16, 24}
output[i >> 2] |= (input[i] & 0xff) << ((i & 0x3) << 3);
}

return NumberFactory.makeIntArray(tmp);
return output;
}
}
@@ -16,26 +16,51 @@
*/
package org.apache.commons.rng.simple.internal;

import java.util.Arrays;

import org.apache.commons.rng.core.util.NumberFactory;

/**
* Creates a {@code long[]} from a {@code byte[]}.
*
* @since 1.0
*/
public class ByteArray2LongArray implements SeedConverter<byte[], long[]> {
/** Number of bytes in a {@code long}. */
private static final int LONG_SIZE = 8;

public class ByteArray2LongArray implements Seed2ArrayConverter<byte[], long[]> {
/** {@inheritDoc} */
@Override
public long[] convert(byte[] seed) {
final byte[] tmp = seed.length % LONG_SIZE == 0 ?
seed :
Arrays.copyOf(seed, LONG_SIZE * ((seed.length + LONG_SIZE - 1) / LONG_SIZE));
// Full length conversion
return convertSeed(seed, SeedUtils.longSizeFromByteSize(seed.length));
}

/**
* {@inheritDoc}
*
* @since 1.5
*/
@Override
public long[] convert(byte[] seed, int outputSize) {
return convertSeed(seed, outputSize);
}

/**
* Creates an array of {@code long} values from a sequence of bytes. The longs are
* filled in little-endian order (least significant byte first).
*
* @param input Input bytes
* @param length Output length
* @return an array of {@code long}.
*/
private static long[] convertSeed(byte[] input, int length) {
final long[] output = new long[length];

// Overflow-safe minimum using long
final int n = (int) Math.min(input.length, length * (long) Long.BYTES);
// Little-endian fill
for (int i = 0; i < n; i++) {
// i = byte index
// i >> 3 = long index
// i & 0x7 = byte number in the long [0, 7]
// (i & 0x7) << 3 = little-endian byte shift to the long {0, 8, 16, 24, 32, 36, 40, 48, 56}
output[i >> 3] |= (input[i] & 0xffL) << ((i & 0x7) << 3);
}

return NumberFactory.makeLongArray(tmp);
return output;
}
}
@@ -16,25 +16,55 @@
*/
package org.apache.commons.rng.simple.internal;

import org.apache.commons.rng.core.util.NumberFactory;

/**
* Creates a {@code long[]} from an {@code int[]}.
*
* <p>Note: From version 1.5 this conversion uses the int bytes to compose the long bytes
* in little-endian order.
* The output {@code long[]} can be converted back using {@link LongArray2IntArray}.
*
* @since 1.0
*/
public class IntArray2LongArray implements SeedConverter<int[], long[]> {
public class IntArray2LongArray implements Seed2ArrayConverter<int[], long[]> {
/** {@inheritDoc} */
@Override
public long[] convert(int[] seed) {
final int outSize = (seed.length + 1) / 2;
final long[] out = new long[outSize];
for (int i = 0; i < outSize; i++) {
final int lo = seed[i];
final int hi = outSize + i < seed.length ? seed[outSize + i] : 0;
out[i] = NumberFactory.makeLong(hi, lo);
// Full length conversion
return convertSeed(seed, SeedUtils.longSizeFromIntSize(seed.length));
}

/**
* {@inheritDoc}
*
* @since 1.5
*/
@Override
public long[] convert(int[] seed, int outputSize) {
return convertSeed(seed, outputSize);
}

/**
* Creates an array of {@code long} values from a sequence of ints. The longs are
* filled in little-endian order (least significant byte first).
*
* @param input Input bytes
* @param length Output length
* @return an array of {@code long}.
*/
private static long[] convertSeed(int[] input, int length) {
final long[] output = new long[length];

// Overflow-safe minimum using long
final int n = (int) Math.min(input.length, length * 2L);
// Little-endian fill
for (int i = 0; i < n; i++) {
// i = int index
// i >> 1 = long index
// i & 0x1 = int number in the long [0, 1]
// (i & 0x1) << 5 = little-endian byte shift to the long {0, 32}
output[i >> 1] |= (input[i] & 0xffffffffL) << ((i & 0x1) << 5);
}

return out;
return output;
}
}
@@ -16,24 +16,55 @@
*/
package org.apache.commons.rng.simple.internal;

import org.apache.commons.rng.core.util.NumberFactory;

/**
* Creates an {@code int[]} from a {@code long[]}.
*
* <p>Note: From version 1.5 this conversion uses the long bytes in little-endian order.
* The output {@code int[]} can be converted back using {@link IntArray2LongArray}.
*
* @since 1.0
*/
public class LongArray2IntArray implements SeedConverter<long[], int[]> {
public class LongArray2IntArray implements Seed2ArrayConverter<long[], int[]> {
/** {@inheritDoc} */
@Override
public int[] convert(long[] seed) {
final int[] out = new int[seed.length * 2];
for (int i = 0; i < seed.length; i++) {
final long current = seed[i];
out[i] = NumberFactory.extractLo(current);
out[seed.length + i] = NumberFactory.extractHi(current);
// Full length conversion
return convertSeed(seed, SeedUtils.intSizeFromLongSize(seed.length));
}

/**
* {@inheritDoc}
*
* @since 1.5
*/
@Override
public int[] convert(long[] seed, int outputSize) {
return convertSeed(seed, outputSize);
}

/**
* Creates an array of {@code int} values from a sequence of bytes. The integers are
* filled in little-endian order (least significant byte first).
*
* @param input Input bytes
* @param length Output length
* @return an array of {@code int}.
*/
private static int[] convertSeed(long[] input, int length) {
final int[] output = new int[length];

// Overflow-safe minimum using long
final int n = (int) Math.min(input.length * 2L, length);
// Little-endian fill
// Alternate low/high 32-bits from each long
for (int i = 0; i < n; i++) {
// i = int index
// i >> 1 = long index
// i & 0x1 = int number in the long [0, 1]
// (i & 0x1) << 5 = little-endian long shift to the int {0, 32}
output[i] = (int)((input[i >> 1]) >>> ((i & 0x1) << 5));
}

return out;
return output;
}
}
@@ -118,11 +118,15 @@ protected int[] convert(int[] seed, int size) {
}
@Override
protected int[] convert(long[] seed, int size) {
return LONG_ARRAY_TO_INT_ARRAY.convert(seed);
// Avoid zero filling seeds that are too short
return LONG_ARRAY_TO_INT_ARRAY.convert(seed,
Math.min(size, SeedUtils.intSizeFromLongSize(seed.length)));
}
@Override
protected int[] convert(byte[] seed, int size) {
return BYTE_ARRAY_TO_INT_ARRAY.convert(seed);
// Avoid zero filling seeds that are too short
return BYTE_ARRAY_TO_INT_ARRAY.convert(seed,
Math.min(size, SeedUtils.intSizeFromByteSize(seed.length)));
}
},
/** The seed type is {@code long[]}. */
@@ -143,15 +147,19 @@ protected long[] convert(Long seed, int size) {
}
@Override
protected long[] convert(int[] seed, int size) {
return INT_ARRAY_TO_LONG_ARRAY.convert(seed);
// Avoid zero filling seeds that are too short
return INT_ARRAY_TO_LONG_ARRAY.convert(seed,
Math.min(size, SeedUtils.longSizeFromIntSize(seed.length)));
}
@Override
protected long[] convert(long[] seed, int size) {
return seed;
}
@Override
protected long[] convert(byte[] seed, int size) {
return BYTE_ARRAY_TO_LONG_ARRAY.convert(seed);
// Avoid zero filling seeds that are too short
return BYTE_ARRAY_TO_LONG_ARRAY.convert(seed,
Math.min(size, SeedUtils.longSizeFromByteSize(seed.length)));
}
};

@@ -210,4 +210,65 @@ private static int copyToOutput(byte[] digits, int bits, int upper, int lower) {
digits[lower] = digits[upper];
return newbits;
}

/**
* Compute the size of an {@code int} array required to hold the specified number of bytes.
* Allows space for any remaining bytes that do not fit exactly in a 4 byte integer.
* <pre>
* n = ceil(size / 4)
* </pre>
*
* @param size the size in bytes (assumed to be positive)
* @return the size in ints
*/
static int intSizeFromByteSize(int size) {
return (size + 3) >>> 2;
}

/**
* Compute the size of an {@code long} array required to hold the specified number of bytes.
* Allows space for any remaining bytes that do not fit exactly in an 8 byte long.
* <pre>
* n = ceil(size / 8)
* </pre>
*
* @param size the size in bytes (assumed to be positive)
* @return the size in longs
*/
static int longSizeFromByteSize(int size) {
return (size + 7) >>> 3;
}

/**
* Compute the size of an {@code int} array required to hold the specified number of longs.
* Prevents overflow to a negative number when doubling the size.
* <pre>
* n = min(size * 2, 2^31 - 1)
* </pre>
*
* @param size the size in longs (assumed to be positive)
* @return the size in ints
*/
static int intSizeFromLongSize(int size) {
// Avoid overflow when doubling the length.
// If n is negative the signed shift creates a mask with all bits set;
// otherwise it is zero and n is unchanged after the or operation.
// The final mask clears the sign bit in the event n did overflow.
final int n = size << 1;
return (n | (n >> 31)) & Integer.MAX_VALUE;
}

/**
* Compute the size of an {@code long} array required to hold the specified number of ints.
* Allows space for an odd int.
* <pre>
* n = ceil(size / 2)
* </pre>
*
* @param size the size in ints (assumed to be positive)
* @return the size in longs
*/
static int longSizeFromIntSize(int size) {
return (size + 1) >>> 1;
}
}

0 comments on commit ca6f140

Please sign in to comment.