SeedUtils.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.rng.simple.internal;

  18. import org.apache.commons.rng.UniformRandomProvider;
  19. import org.apache.commons.rng.core.util.NumberFactory;

  20. /**
  21.  * Utility for creating seeds.
  22.  */
  23. final class SeedUtils {
  24.     /** The modulus {@code 256 % 9}. */
  25.     private static final int MOD_09 = 256 % 9;
  26.     /** The modulus {@code 256 % 10}. */
  27.     private static final int MOD_10 = 256 % 10;
  28.     /** The modulus {@code 256 % 11}. */
  29.     private static final int MOD_11 = 256 % 11;
  30.     /** The modulus {@code 256 % 12}. */
  31.     private static final int MOD_12 = 256 % 12;
  32.     /** The modulus {@code 256 % 13}. */
  33.     private static final int MOD_13 = 256 % 13;
  34.     /** The modulus {@code 256 % 14}. */
  35.     private static final int MOD_14 = 256 % 14;
  36.     /** The modulus {@code 256 % 15}. */
  37.     private static final int MOD_15 = 256 % 15;
  38.     /** The 16 hex digits in an array. */
  39.     private static final byte[] HEX_DIGIT_ARRAY = {0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8,
  40.                                                    0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0};

  41.     /**
  42.      * Provider of unsigned 8-bit integers.
  43.      */
  44.     private static class UnsignedByteProvider {
  45.         /** Mask to extract the lowest 2 bits from an integer. */
  46.         private static final int MASK_2_BITS = 0x3;
  47.         /** Mask to extract the lowest 8 bits from an integer. */
  48.         private static final int MASK_8_BITS = 0xff;

  49.         /** Source of randomness. */
  50.         private final UniformRandomProvider rng;
  51.         /** The current 32-bits of randomness. */
  52.         private int bits;
  53.         /** The counter tracking the bits to extract. */
  54.         private int counter;

  55.         /**
  56.          * @param rng Source of randomness.
  57.          */
  58.         UnsignedByteProvider(UniformRandomProvider rng) {
  59.             this.rng = rng;
  60.         }

  61.         /**
  62.          * Return the next unsigned byte.
  63.          *
  64.          * @return Value in the range[0,255]
  65.          */
  66.         int nextUnsignedByte() {
  67.             // Every 4 bytes generate a new 32-bit value
  68.             final int count = counter & MASK_2_BITS;
  69.             counter++;
  70.             if (count == 0) {
  71.                 bits = rng.nextInt();
  72.                 // Consume the first byte
  73.                 return bits & MASK_8_BITS;
  74.             }
  75.             // Consume a remaining byte (occurs 3 times)
  76.             bits >>>= 8;
  77.             return bits & MASK_8_BITS;
  78.         }
  79.     }

  80.     /**
  81.      * Class contains only static methods.
  82.      */
  83.     private SeedUtils() {}

  84.     /**
  85.      * Creates an {@code int} containing a permutation of 8 hex digits chosen from 16.
  86.      *
  87.      * @param rng Source of randomness.
  88.      * @return hex digit permutation.
  89.      */
  90.     static int createIntHexPermutation(UniformRandomProvider rng) {
  91.         final UnsignedByteProvider provider = new UnsignedByteProvider(rng);
  92.         return createUpperBitsHexPermutation(provider);
  93.     }

  94.     /**
  95.      * Creates a {@code long} containing a permutation of 8 hex digits chosen from 16 in
  96.      * the upper and lower 32-bits.
  97.      *
  98.      * @param rng Source of randomness.
  99.      * @return hex digit permutation.
  100.      */
  101.     static long createLongHexPermutation(UniformRandomProvider rng) {
  102.         final UnsignedByteProvider provider = new UnsignedByteProvider(rng);
  103.         // Extract upper bits and combine with a second sample
  104.         return NumberFactory.makeLong(createUpperBitsHexPermutation(provider),
  105.                 createUpperBitsHexPermutation(provider));
  106.     }

  107.     /**
  108.      * Creates a {@code int} containing a permutation of 8 hex digits chosen from 16.
  109.      *
  110.      * @param provider Source of randomness.
  111.      * @return hex digit permutation.
  112.      */
  113.     private static int createUpperBitsHexPermutation(UnsignedByteProvider provider) {
  114.         // Compute a Fisher-Yates shuffle in-place on the 16 hex digits.
  115.         // Each digit is chosen uniformly from the remaining digits.
  116.         // The value is swapped with the current digit.

  117.         // The following is an optimised sampling algorithm that generates
  118.         // a uniform deviate in the range [0,n) from an unsigned byte.
  119.         // The expected number of samples is 256/n.
  120.         // This has a bias when n does not divide into 256. So samples must
  121.         // be rejected at a rate of (256 % n) / 256:
  122.         // n     256 % n   Rejection rate
  123.         // 9     4         0.015625
  124.         // 10    6         0.0234375
  125.         // 11    3         0.01171875
  126.         // 12    4         0.015625
  127.         // 13    9         0.03515625
  128.         // 14    4         0.015625
  129.         // 15    1         0.00390625
  130.         // 16    0         0
  131.         // The probability of no rejections is 1 - (1-p15) * (1-p14) ... = 0.115

  132.         final byte[] digits = HEX_DIGIT_ARRAY.clone();

  133.         // The first digit has no bias. Get an index using a mask to avoid modulus.
  134.         int bits = copyToOutput(digits, 0, 15, provider.nextUnsignedByte() & 0xf);

  135.         // All remaining digits have a bias so use the rejection algorithm to find
  136.         // an appropriate uniform deviate in the range [0,n)
  137.         bits = copyToOutput(digits, bits, 14, nextUnsignedByteInRange(provider, MOD_15, 15));
  138.         bits = copyToOutput(digits, bits, 13, nextUnsignedByteInRange(provider, MOD_14, 14));
  139.         bits = copyToOutput(digits, bits, 12, nextUnsignedByteInRange(provider, MOD_13, 13));
  140.         bits = copyToOutput(digits, bits, 11, nextUnsignedByteInRange(provider, MOD_12, 12));
  141.         bits = copyToOutput(digits, bits, 10, nextUnsignedByteInRange(provider, MOD_11, 11));
  142.         bits = copyToOutput(digits, bits,  9, nextUnsignedByteInRange(provider, MOD_10, 10));
  143.         bits = copyToOutput(digits, bits,  8, nextUnsignedByteInRange(provider, MOD_09,  9));

  144.         return bits;
  145.     }


  146.     /**
  147.      * Get the next unsigned byte in the range {@code [0,n)} rejecting any over-represented
  148.      * sample value using the pre-computed modulus.
  149.      *
  150.      * <p>This algorithm is as per Lemire (2019) adapted for 8-bit arithmetic.</p>
  151.      *
  152.      * @param provider Provider of bytes.
  153.      * @param threshold Modulus threshold {@code 256 % n}.
  154.      * @param n Upper range (exclusive)
  155.      * @return Value.
  156.      * @see <a href="https://arxiv.org/abs/1805.10941">
  157.      * Lemire (2019): Fast Random Integer Generation in an Interval</a>
  158.      */
  159.     private static int nextUnsignedByteInRange(UnsignedByteProvider provider, int threshold, int n) {
  160.         // Rejection method using multiply by a fraction:
  161.         // n * [0, 2^8 - 1)
  162.         //     ------------
  163.         //         2^8
  164.         // The result is mapped back to an integer and will be in the range [0, n)
  165.         int m;
  166.         do {
  167.             // Compute product of n * [0, 2^8 - 1)
  168.             m = n * provider.nextUnsignedByte();

  169.             // Test the sample uniformity.
  170.         } while ((m & 0xff) < threshold);
  171.         // Final divide by 2^8
  172.         return m >>> 8;
  173.     }

  174.     /**
  175.      * Copy the lower hex digit to the output bits. Swap the upper hex digit into
  176.      * the lower position. This is equivalent to a swap step of a Fisher-Yates shuffle on
  177.      * an array but the output of the shuffle are written to the bits.
  178.      *
  179.      * @param digits Digits.
  180.      * @param bits Bits.
  181.      * @param upper Upper index.
  182.      * @param lower Lower index.
  183.      * @return Updated bits.
  184.      */
  185.     private static int copyToOutput(byte[] digits, int bits, int upper, int lower) {
  186.         // Move the existing bits up and append the next hex digit.
  187.         // This is equivalent to swapping lower to upper.
  188.         final int newbits = bits << 4 | digits[lower] & 0xf;
  189.         // Swap upper to lower.
  190.         digits[lower] = digits[upper];
  191.         return newbits;
  192.     }
  193. }