从给定范围内生成随机BigDecimal值

9

我需要在给定的范围内生成随机BigDecimal值。如何在Java中实现?


可能是 https://dev59.com/p1TTa4cB1Zd3GeqPogcQ 的重复问题。 - biziclop
3个回答

12
class BigDecRand {
    public static void main(String[] args) {
        String range = args[0];
        BigDecimal max = new BigDecimal(range + ".0");
        BigDecimal randFromDouble = new BigDecimal(Math.random());
        BigDecimal actualRandomDec = randFromDouble.divide(max,BigDecimal.ROUND_DOWN);

        BigInteger actualRandom = actualRandomDec.toBigInteger();
    }
}

但是如何从范围[-0.45,0.6]生成一个值呢? - Marcin Sanecki
1
你可以使用这段代码来生成一个指定范围内的随机数,然后将其加上最小值。R = min + rand(range),其中 range = max - min - corsiKa
这是一个产生BigDecimal随机数的Java类:public class BigDecimalGenerator { public static void main(String[] args) { BigDecimal max = new BigDecimal("0.44"); BigDecimal min = new BigDecimal("-0.44"); BigDecimal range = max.subtract(min); BigDecimal result = min.add(range.multiply(new BigDecimal(Math.random()))); System.out.println(result); }} - Marcin Sanecki
我应该指出,这在动态性方面是有限制的。随机性受到介于1和0之间的“double”精度的限制。希望这已经足够随机了 :) - corsiKa
例如,当 BigDecimal max = new BigDecimal("100000000000000000000000000000000"); BigDecimal min = new BigDecimal("-100000000000000000000000000000000"); 时,您的结果看起来像这样:88392126894971667638856160920113.3251190185546875000000000000000000000000000000000 - corsiKa

8

我用这种方式做这件事

public static BigDecimal generateRandomBigDecimalFromRange(BigDecimal min, BigDecimal max) {
    BigDecimal randomBigDecimal = min.add(new BigDecimal(Math.random()).multiply(max.subtract(min)));
    return randomBigDecimal.setScale(2,BigDecimal.ROUND_HALF_UP);
}

我运行它的方式:

BigDecimal random = Application.generateRandomBigDecimalFromRange(
    new BigDecimal(-1.21).setScale(2, BigDecimal.ROUND_HALF_UP),
    new BigDecimal(21.28).setScale(2, BigDecimal.ROUND_HALF_UP)
);

5

以往的答案并未解决通过使用双精度浮点数(具有相对较少位数)缩放任意大数字位数值所导致的精度损失。以下的BigRandom实现可以生成指定精度的随机BigInteger和BigDecimal值:

// The short version
public static BigDecimal between(BigDecimal min, BigDecimal MAX) {
  int digitCount = Math.max(min.precision(), MAX.precision());
  int bitCount = (int)(digitCount / Math.log10(2.0));

  // convert Random BigInteger to a BigDecimal between 0 and 1
  BigDecimal alpha = new BigDecimal(
    new BigInteger( bitCount, new Random() )
  ).movePointLeft(digitCount);

  return min.add(MAX.subtract(min).multiply(alpha, new MathContext(digitCount)));
}

// Full Implementation
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.Random;

public class BigRandom {

    private static Random defaultRandom = new Random();

    // Constants:
    private static double log2 = Math.log10(2.0);

    // Computes number of bits needed to represent an n digit positive integer.

    private static int bitCount(int n) {
        return (int)( n / log2 );
    }

    // Static Methods for generating Random BigInteger values:

    public static BigInteger nextBigInteger(int precision) {
        return nextBigInteger(precision, defaultRandom);
    }

    public static BigInteger nextBigInteger(int precision, Random r) {
        return new BigInteger(bitCount(precision), r);
    }

    public static BigInteger nextBigInteger(BigInteger norm) {
        return nextBigInteger(norm, defaultRandom);
    }

    public static BigInteger nextBigInteger(BigInteger norm, Random r) {
        BigDecimal bdNorm = new BigDecimal(norm);
        int precision = bdNorm.precision() - bdNorm.scale();
        return bdNorm.multiply(nextBigDecimal(precision, r), new MathContext(precision + 1)).toBigInteger();
    }

    public static BigInteger between(BigInteger min, BigInteger MAX) {
        return between(min, MAX, defaultRandom);
    }

    public static BigInteger between(BigInteger min, BigInteger MAX, Random r) {
        return min.add( nextBigInteger( MAX.subtract(min), r ) );
    }

    // Static Methods for generating Random BigDecimal values:

    public static BigDecimal nextBigDecimal(int scale) {
        return nextBigDecimal(scale, defaultRandom);
    }

    public static BigDecimal nextBigDecimal(int scale, Random r) {
        BigInteger bi = nextBigInteger(scale, r);  // generate random BigInteger with a number of digits equal to scale.
        BigDecimal bd = new BigDecimal(bi);  // convert BigInteger to a BigDecimal
        return bd.movePointLeft(bd.precision());  // move the decimal point all the way to the left
    }

    public static BigDecimal nextBigDecimal(BigDecimal norm, int scale) {
        return nextBigDecimal(norm, scale, defaultRandom);
    }

    public static BigDecimal nextBigDecimal(BigDecimal norm, int scale, Random r) {
        return norm.multiply( nextBigDecimal( scale, r ), new MathContext( (norm.precision() - norm.scale()) + scale) );
    }

    public static BigDecimal between(BigDecimal min, BigDecimal MAX) {
        return between(min, MAX, defaultRandom);
    }

    public static BigDecimal between(BigDecimal min, BigDecimal MAX, Random r) {
        return min.add(
            nextBigDecimal(
                MAX.subtract(min),
                Math.max( min.precision(), MAX.precision() ),
                r
            )
        );
    }


    public static void main(String[] args) {
        // Make a BigInteger independently from this implementation.
        int bc = ((150 - defaultRandom.nextInt(50)) * 8) - defaultRandom.nextInt(8);
        BigInteger bi = new BigInteger(bc, defaultRandom);
        String bistr = bi.toString();
        int precision = bistr.length();

        System.out.println("Independently generated random BigInteger:\n" + bistr);
        System.out.println("\tprecision: " + bistr.length());

        System.out.println("\n\n------------------------\n\n");

        // demonstrate nextBigInteger(precision)
        System.out.println("demonstrate nextBigInteger(precision = " + precision + "):\n");
        for (int i = 0; i < 5; i++) {
            BigInteger bii = nextBigInteger(precision);
            String biistr = bii.toString();
            System.out.println("iteration " + i + " nextBigInteger(precision = " + precision + "):\n\t" + biistr);
            System.out.println("\tprecision: " + biistr.length() + " == " + precision + " : " + ( biistr.length() == precision ));
        }

        System.out.println("\n\n------------------------\n\n");

        // demonstrate nextBigInteger(norm)
        System.out.println("demonstrate nextBigInteger(\n\tnorm = " + bi + "\n):\n");
        for (int i = 0; i < 5; i++) {
            BigInteger bii = nextBigInteger(bi);
            String biistr = bii.toString();
            System.out.println("iteration " + i + " nextBigInteger(norm = ... ):\n\t" + biistr);
            System.out.println("\tprecision: " + biistr.length() + " <= " + precision + " : " + ( biistr.length() <= precision ));
            System.out.println("\t( bii <= bi ) = " + (bii.compareTo(bi) <= 0));
        }

        BigInteger bin = bi.negate();

        System.out.println("\n\n------------------------\n\n");

        // demonstrate between(min, MAX)
        System.out.println("demonstrate between(\n\tmin = " + bin + ",\n\tMAX = " + bi + "\n):\n");
        for (int i = 0; i < 5; i++) {
            BigInteger bii = between(bin, bi);
            String biistr = bii.toString();
            System.out.println("iteration " + i + " between(norm = ... ):\n\t" + biistr);
            System.out.println("\tprecision: " + biistr.length() + " <= " + precision + " : " + ( biistr.length() <= precision ));
            System.out.println("\t( bii >= -bi ) = " + (bii.compareTo(bin) >= 0));
            System.out.println("\t( bii < bi ) = " + (bii.compareTo(bi) < 0));
        }

        System.out.println("\n\n------------------------\n\n");

        // Make a BigDecimal independently from this implementation.
        BigDecimal bd = new BigDecimal(Double.MAX_VALUE);
        for (int i = 10; i < 50; i = i + 10) {
            bd = bd.add( new BigDecimal(defaultRandom.nextDouble()).pow(i) );
        }

        System.out.println("Independently generated random BigDecimal:\n" + bd);
        System.out.println("\tprecision: " + bd.precision() + " scale: " + bd.scale());

        System.out.println("\n\n------------------------\n\n");

        // demonstrate nextBigDecimal(scale)
        System.out.println("demonstrate nextBigDecimal(scale = " + bd.scale() + "):\n");
        for (int i = 0; i < 5; i++) {
            BigDecimal bdi = nextBigDecimal(bd.scale());
            System.out.println("iteration " + i + " nextBigDecimal(scale = " + bd.scale() + "):\n\t" + bdi);
            System.out.println("\tprecision: " + bdi.precision() + " scale: " + bdi.scale());
        }

        System.out.println("\n\n------------------------\n\n");

        // demonstrate nextBigDecimal(norm, scale)
        System.out.println("demonstrate nextBigDecimal(\n\tnorm = " + bd + ",\n\tscale = " + bd.scale() + "\n):\n");
        for (int i = 0; i < 5; i++) {
            BigDecimal bdi = nextBigDecimal(bd, bd.scale());
            System.out.println("iteration " + i + " nextBigDecimal(norm = ..., scale = " + bd.scale() + "):\n\t" + bdi);
            System.out.println("\tprecision: " + bdi.precision() + " scale: " + bdi.scale());
            System.out.println("\t( bdi <= bd ) = " + (bdi.compareTo(bd) <= 0));
        }

        System.out.println("\n\n------------------------\n\n");

        // demonstrate between(min, MAX)
        BigDecimal bdn = bd.negate();
        System.out.println("demonstrate between(\n\tmin = " + bdn + ",\n\tMAX = " + bd + "\n):\n");
        for (int i = 0; i < 5; i++) {
            BigDecimal bdi = between(bdn, bd);
            System.out.println("iteration " + i + " between(-bd, bd):\n\t" + bdi);
            System.out.println("\tprecision: " + bdi.precision() + " scale: " + bdi.scale());
            System.out.println("\t( bdi >= -bd ) = " + (bdi.compareTo(bdn) >= 0));
            System.out.println("\t( bdi < bd ) = " + (bdi.compareTo(bd) < 0));
        }

    }
}

以下示例试图澄清先前答案遗漏了潜在有效的随机值非常大的间隔的原因。
请考虑任意小的epsilon值:
e = x 10^(-无穷大)
和一个区间:
[0 + e,1 + e]。
随着我们尝试用越来越大的数值替换无限大来近似这个概念范围,该范围端点的比例远远超过返回双精度浮点数的Math.random()函数的比例。
从概念上说:
BigDecimal e = ...  // arbitrarily small value.
BigDecimal min = new BigDecimal(0.0).add(e);
BigDecimal MAX = new BigDecimal(1.0).add(e);

BigDecimal norm = MAX.subtract(min);  // 1.0

BigDecimal randBigDecimal = min.add(
  norm.multiply(
    new BigDecimal(
      Math.random()
    )
  )
); // equivalent to e + (1.0 * Math.random())


如果 Math.random() 返回 0,那么 randBigDecimal 等于:e; 如果 Math.random() 返回 Double.MIN_VALUE,那么 randBigDecimal 等于:Double.MIN_VALUE + e
我们可以列举出所有可能的 randBigDecimal 值,如下所示:
double d = 0.0;

// Don't actually run this loop!  :)
while (d < 1.0) {
  System.out.println(e + d);
  d = Math.nextUp(d);
}

当e的规模超过Java的双精度浮点数类型时,也就是使用BigDecimal的主要动机时,此算法在e + d和e + Math.nextUp(d)之间留下了越来越大的空隙。

无论如何,该算法始终省略掉min + 2e、min + 3e、...、min + (N-1)e、min + Ne。对于所有整数[2,N],使得(new BigDecimal(N).times(new BigDecimal(Double.MIN_VALUE))).scale() > e.scale()

当然,在e和2e之间存在许多数字的无限,但我们希望我们的随机BigDecimal算法至少涵盖与Math.max(min.scale(), MAX.scale())具有相同规模的所有值。


1
@corsiKa - 对于Ben Mc Kenneby的这种可靠方法有什么评论?顺便说一句,Ben - 非常感谢你的贡献! - Marcin Sanecki
1
谢谢,Marcin。原来Java的BigInteger拥有99.99%的功能被隐藏在:new BigInteger(bitCount, random)中。 将其转换为0到1之间的BigDecimal只需移动小数点即可。 - Ben McKenneby
编辑。我在开头添加了一个摘要版本,并纠正了一个精度问题。 - Ben McKenneby

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接