package org.geogebra.common.kernel.statistics; import java.math.BigDecimal; import java.math.BigInteger; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoNumberValue; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.util.MyMath2; public class AlgoNpR extends AlgoElement { private GeoNumeric result; private GeoNumberValue num1, num2; public AlgoNpR(Construction cons, String label, GeoNumberValue num1, GeoNumberValue num2) { super(cons); this.num1 = num1; this.num2 = num2; result = new GeoNumeric(cons); setInputOutput(); compute(); result.setLabel(label); } @Override protected void setInputOutput() { setOnlyOutput(result); input = new GeoElement[] { num1.toGeoElement(), num2.toGeoElement() }; setDependencies(); } @Override public void compute() { if (input[0].isDefined() && input[1].isDefined()) { double nCr = NpR(num1.getDouble(), num2.getDouble()); result.setValue(nCr); } else { result.setUndefined(); } } @Override public Commands getClassName() { return Commands.nPr; } public GeoNumeric getResult() { return result; } private static double NpR(double n, double r) { double INFINITY = Double.POSITIVE_INFINITY; try { if (n == 0d && r == 0d) { return 1d; } if (n < 1d || r < 0d || n < r) { return 0d; } if (Math.floor(n) != n || Math.floor(r) != r) { return 0d; } double ncr = NpRLog(n, r); if (ncr == INFINITY) { return INFINITY; // check to stop needless slow calculations } // NpRLog is not exact for some values // (determined by trial and error) eg 17P16 if (n <= 16) { return ncr; // if (r<2.8+Math.exp((250-n)/100) && n<59000) return ncr; } // NpRBig is more accurate but slower // (but cannot be exact if the answer has more than about 16 // significant digits) return NpRBig(n, r); } catch (Exception e) { return INFINITY; } } private static double NpRBig(double n, double r) { BigInteger ncr = BigInteger.ONE, nn, nr; // nn=BigInteger.valueOf((long)n); // rr=BigInteger.valueOf((long)r); // need a long-winded conversion in case n>10^18 Double nnn = new Double(n); Double rrr = new Double(n - r); nn = (new BigDecimal(nnn.toString())).toBigInteger(); nr = (new BigDecimal(rrr.toString())).toBigInteger(); nr = nr.add(BigInteger.ONE); while (nr.compareTo(nn) <= 0) { ncr = ncr.multiply(nr); nr = nr.add(BigInteger.ONE); } return ncr.doubleValue(); } private static double NpRLog(double n, double r) { // exact for n<=37 // also if r<2.8+Math.exp((250-n)/100) && n<59000 // eg NpR2(38,19) is wrong return Math.floor(0.5 + Math .exp(MyMath2.logGamma(n + 1d) - MyMath2.logGamma(n - r + 1))); } }