public static FractionValue DoubleToFraction(double value) { // I used to always do "new FractionValue((decimal)value)" because // it does a good job for fixed length fractions. But it made no attempt // to deal with repeating fractions, which are very common, so I've // pulled in the algorithm I used in my old RPN Calc 2. Now I try both // algorithms and then see which appears to be better. FractionValue local = DoubleToRationalLocal(value); double localDouble = local.ToDouble(); // Use FractionValue's algorithm. FractionValue fraction = new((decimal)value); double fractionDouble = fraction.ToDouble(); // See how far both are off from the original value. double localEpsilon = Math.Abs(localDouble - value); double fractionEpsilon = Math.Abs(fractionDouble - value); // Use the number of digits too. If I divide 5.0/7.0 and then // convert back to a fraction, then the local algorithm nails it. // But if I enter a value of "0.714285714285714", then local's // epsilon is a tad farther off, even though it comes up with a // much smaller and better denominator. int localDenomDigits = NumDigits(local.Denominator); int fracDenomDigits = NumDigits(fraction.Denominator); int diffDigits = Math.Abs(localDenomDigits - fracDenomDigits); FractionValue result; if (diffDigits <= 2) { // The denominator sizes are close, so choose the result based on which // epsilon is smaller. This is important for "0.13333", which produces fewer // denominator digits with the local algorithm but a much larger epsilon. result = localEpsilon < fractionEpsilon ? local : fraction; } else { // The denominator sizes are several orders of magnitude different, so // choose the one with the fewest digits. The FractionValue algorithm // can always get a small epsilon by using a huge denominator, but that // rarely produces a fraction the user actually wants. result = localDenomDigits < fracDenomDigits ? local : fraction; } return result; }
public void DtoF(Command cmd) { this.RequireArgs(1); this.RequireScalarNumericType(0); var value = (NumericValue)cmd.UseTopValue(); NumericValue result = value; switch (value.ValueType) { case RpnValueType.Binary: result = new FractionValue(((BinaryValue)value).ToInteger(), BigInteger.One); break; case RpnValueType.Integer: result = new FractionValue(((IntegerValue)value).AsInteger, BigInteger.One); break; case RpnValueType.Double: result = Utility.DoubleToFraction(((DoubleValue)value).AsDouble); break; } cmd.Commit(result); }
private static IEnumerable <Value> ValidateAndReduce(Value[] valuesToPush) { IEnumerable <Value> result; int numValuesToPush = valuesToPush.Length; if (numValuesToPush == 0) { result = valuesToPush; } else { // This method does value type reduction, which is something // the HP48 doesn't do. It's usually a good thing: // * Complex values with 0 imaginary part are reduced to doubles. // * Doubles with no fractional part are reduced to integers. // * Fractions with a denominator of 1 are reduced to integers. // // But sometimes the results can seem odd. For example: // * If I manually type in "(4,0)" it ends up as the integer 4. // * If 4 and 0 are on the stack, then RtoC returns 4. // // I'm going to keep this logic in spite of the oddities though // because numeric value types will be implicitly converted up // (i.e., widened) whenever necessary in operations. List <Value> values = new(numValuesToPush); foreach (Value value in valuesToPush) { switch (value.ValueType) { case RpnValueType.Complex: ComplexValue complexValue = (ComplexValue)value; Complex complex = complexValue.AsComplex; Validate(complex.Real); Validate(complex.Imaginary); // Reduce to a double or integer if the imaginary part is 0. values.Add(Reduce(complex, complexValue)); break; case RpnValueType.Double: DoubleValue doubleValue = (DoubleValue)value; double dbl = doubleValue.AsDouble; Validate(dbl); // Reduce to an integer if there's no fractional part. values.Add(Reduce(dbl, doubleValue)); break; case RpnValueType.Fraction: FractionValue fraction = (FractionValue)value; // Reduce to an integer if the denominator is 1. if (fraction.Denominator == BigInteger.One) { values.Add(new IntegerValue(fraction.Numerator)); } else { values.Add(value); } break; default: values.Add(value); break; } } result = values; } return(result); }