Java中将双精度数转换为分数

4

我尝试将double转换为有理数。我会检查小数点后面有多少位,然后将数字123.456保存为例如123456/1000。

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
}   

然而,对于数字123.456,我得到了一个舍入误差,结果是123455。我猜测可以使用BigDecimal来解决这个问题,但我无法让它工作。此外,计算了它将成为什么有理数之后,我想使用参数(int numerator,int denominator)调用另一个构造函数,但显然不能在println所在的行中调用构造函数。我该怎么做?


1
难道只需计算句点的位置,然后从字符串中删除(使用字符串替换)句点不是更简单吗? - Asaf
1
你可不可以将小数点后的所有数字都移到它前面,同时计算出所移动数字的数量呢?去掉小数点,将结果解析回整数,然后再创建第二个整数,其值是移动数字的数量乘以10? - Jeroen Vannevel
2
有点跑题,但你知道任何有理数都有无限个分数表示吗?一种选择是寻找“规范分数”,但这比你尝试做的要稍微复杂一些。 - SJuan76
此外,可能与主题无关,但您可能需要考虑一些舍入算法。例如,double 无法准确表示 1/10,但我不确定您是否想要表示有理数 0.1000000000000000055511151231257827021181583404541015625。或者,如您所提到的,您可以使用 BigDecimal。 - yshavit
5个回答

6

对于第一个问题,Java将.6存储为.5999999(重复)。请参阅以下输出:

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

一个解决方法是在循环结束后立即使用 d = Math.round(d)。

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));
     }
}

它可以正常工作-试试看。

关于你问题的第二部分...

为了从第一个构造函数调用第二个构造函数,您可以使用“this”关键字。

this(num, denom)

但它必须是构造函数中的第一行……这在这里没有意义(我们必须先进行一些计算)。所以我不会费力去尝试。

乘以10.0并没有改变结果。 - Neutrino
关于第二部分:可能我没有准确地陈述我的问题。我有两个构造函数:第一个带参数(double d),另一个带参数(int num,int denom)。在第一个构造函数中,我想将双精度数转换为分数并调用第二个构造函数。 - Neutrino
Java在你乘以10后将双精度数123.456存储为123455.999999。获得正确值的一种方法是在此处四舍五入。 - ktm5124
请看我的修改后的帖子 - 我进行了测试,得到了123456/1000作为我的标准输出。 - ktm5124
非常感谢,现在没问题了。 - Neutrino

5

这段代码可能对你来说有些过于复杂,但它可以解决你所遇到的四舍五入误差问题,并且还能处理重复小数(例如4.99999999999999会变成5,0.33333333333333333333会变成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;
}

这将提供以下结果(测试用例的结果以注释形式显示):
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

虽然不太优雅,但我认为这可以完成你所要求的功能。

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);

现在简化 :D - Ky -

3

在这里,d=123.456,那么num=123456,j=1000。

/**
 * This method calculates a rational number from a double.
 * The denominator will always be a multiple of 10. 
 *
 * @param d the double to calculate the fraction from.
 * @return the result as Pair of <numerator , denominator>.
 */
private static Pair<Integer,Integer> calculateRational(double d){
    int j=1, num;
    do{
        j=j*10;
    }while((d *j)%10!=0);
    j=j/10;
    num=(int)(d*j);
    return new Pair<>(num,j);
}

以下是一些测试:

@Test
public  void testCalculateRational() {
    Assert.assertEquals(new Pair<>(124567, 1000), calculateRational(124.567));
    Assert.assertEquals(new Pair<>(12456, 100), calculateRational(124.56));
    Assert.assertEquals(new Pair<>(56, 100), calculateRational(0.56));
}

0

尝试一下

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

}

我想将数字123.456转换为123456。当我更改为i <= digitsDec时,我得到一个数字1234559。 - Neutrino

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