Double to fraction in Java

2019-02-14 06:18发布

问题:

So what I'm trying to do is convert double to rational number. I check how many digits there is after decimal point and I want to save the number 123.456 as 123456 / 1000, for example.

public Rational(double d){      
    String s = String.valueOf(d);
    int digitsDec = s.length() - 1 - s.indexOf('.');        

    for(int i = 0; i < digitsDec; i++){
        d *= 10;
    }

    System.out.println((int)d); //checking purposes
}   

However, for the number 123.456 I get a round off error and the result is 123455. I guess it'd be possible to fix this with BigDecimal but I can't get it to work. Also, having calculated what rational number it would be, I would like to call another constructor with parameters (int numerator, int denominator) but I can't obviously call the constructor in the line where println is now. How should I do this?

回答1:

For the first part of the question, Java is storing .6 as .5999999 (repeating). See this output:

(after first multiply): d=1234.56
(after second multiply): d=12345.599999999999
(after third multiply): d=123455.99999999999

One fix is to use d = Math.round(d) immediately after your loop finishes.

public class Rational {

     private int num, denom;

     public Rational(double d) {
          String s = String.valueOf(d);
          int digitsDec = s.length() - 1 - s.indexOf('.');        

          int denom = 1;
          for(int i = 0; i < digitsDec; i++){
             d *= 10;
             denom *= 10;
          }
          int num = (int) Math.round(d);

          this.num = num; this.denom = denom;
     }

     public Rational(int num, int denom) {
          this.num = num; this.denom = denom;
     }

     public String toString() {
          return String.valueOf(num) + "/" + String.valueOf(denom);
     }

     public static void main(String[] args) {
          System.out.println(new Rational(123.456));
     }
}

It works - try it.

For the second part of your question...

In order to call the second constructor from the first, you can use the "this" keyword

this(num, denom)

But it has to be the very first line in the constructor... which doesn't make sense here (we have to do some calculations first). So I wouldn't bother trying to do that.



回答2:

This code may be overkill for you, but it deals with the rounding error that you're experiencing, and it also takes care of repeating decimals (4.99999999999999 turns into 5, and 0.33333333333333333333 turns into 1/3).

public static Rational toRational(double number){
return toRational(number, 8);
}

public static Rational toRational(double number, int largestRightOfDecimal){

long sign = 1;
if(number < 0){
    number = -number;
    sign = -1;
}

final long SECOND_MULTIPLIER_MAX = (long)Math.pow(10, largestRightOfDecimal - 1);
final long FIRST_MULTIPLIER_MAX = SECOND_MULTIPLIER_MAX * 10L;
final double ERROR = Math.pow(10, -largestRightOfDecimal - 1);
long firstMultiplier = 1;
long secondMultiplier = 1;
boolean notIntOrIrrational = false;
long truncatedNumber = (long)number;
Rational rationalNumber = new Rational((long)(sign * number * FIRST_MULTIPLIER_MAX), FIRST_MULTIPLIER_MAX);

double error = number - truncatedNumber;
while( (error >= ERROR) && (firstMultiplier <= FIRST_MULTIPLIER_MAX)){
    secondMultiplier = 1;
    firstMultiplier *= 10;
    while( (secondMultiplier <= SECOND_MULTIPLIER_MAX) && (secondMultiplier < firstMultiplier) ){
        double difference = (number * firstMultiplier) - (number * secondMultiplier);
        truncatedNumber = (long)difference;
        error = difference - truncatedNumber;
        if(error < ERROR){
            notIntOrIrrational = true;
            break;
        }
        secondMultiplier *= 10;
    }
}

if(notIntOrIrrational){
    rationalNumber = new Rational(sign * truncatedNumber, firstMultiplier - secondMultiplier);
}
return rationalNumber;
}

This provides the following results (results from test cases are shown as comments):

Rational.toRational(110.0/3.0); // 110/3
Rational.toRational(11.0/1000.0); // 11/1000
Rational.toRational(17357.0/33300.0); // 17357/33300
Rational.toRational(215.0/21.0); // 215/21
Rational.toRational(0.123123123123123123123123); // 41/333
Rational.toRational(145731.0/27100.0); // 145731/27100
Rational.toRational(Math.PI); // 62831853/20000000
Rational.toRational(62.0/63.0); // 62/63
Rational.toRational(24.0/25.0); // 24/25
Rational.toRational(-24.0/25.0); //-24/25
Rational.toRational(-0.25333333333333333333333); // -19/75
Rational.toRational(-4.9999999999999999999999); // -5
Rational.toRational(4.9999999999999999999999);  // 5
Rational.toRational(123.456); // 15432/125


回答3:

It's not elegant, however, I believe this does what you're asking.

double a = 123.456;
String aString = Double.toString(a);        
String[] fraction = aString.split("\\.");

int denominator = (int)Math.pow(10, fraction[1].length());
int numerator = Integer.parseInt(fraction[0] + "" + fraction[1]);

System.out.println(numerator + "/" + denominator);


回答4:

Here, d=123.456 then num=123456, j=1000

public Rational(double d){      
  double temp =d;
  int j=1, num;
  do{
     j=j*10;
     }while((temp*j)%10==0);        
  j=j/10;
  num=(int)(d*j);
  System.out.println(num);
  System.out.println(j);
}   


回答5:

Try

for(int i = 0; i <= digitsDec; i++){

}