在Java中表示分数的最佳方式?
我正在尝试在Java中使用分数。
我想实现算术函数。 为此,我首先需要一种标准化功能的方法。 我知道在找到共同分母之前我无法将 1/6 和 1/2 相加。 我必须添加 1/6 和 3/6。 一个幼稚的方法是让我添加 2/12 和 6/12,然后减少。 如何以最小的性能损失实现共同点? 什么算法最适合这个?
版本 8(感谢 hstoerr):
改进包括:
- equals() 方法现在与compareTo() 方法一致
final class Fraction extends Number {
private int numerator;
private int denominator;
public Fraction(int numerator, int denominator) {
if(denominator == 0) {
throw new IllegalArgumentException("denominator is zero");
}
if(denominator < 0) {
numerator *= -1;
denominator *= -1;
}
this.numerator = numerator;
this.denominator = denominator;
}
public Fraction(int numerator) {
this.numerator = numerator;
this.denominator = 1;
}
public int getNumerator() {
return this.numerator;
}
public int getDenominator() {
return this.denominator;
}
public byte byteValue() {
return (byte) this.doubleValue();
}
public double doubleValue() {
return ((double) numerator)/((double) denominator);
}
public float floatValue() {
return (float) this.doubleValue();
}
public int intValue() {
return (int) this.doubleValue();
}
public long longValue() {
return (long) this.doubleValue();
}
public short shortValue() {
return (short) this.doubleValue();
}
public boolean equals(Fraction frac) {
return this.compareTo(frac) == 0;
}
public int compareTo(Fraction frac) {
long t = this.getNumerator() * frac.getDenominator();
long f = frac.getNumerator() * this.getDenominator();
int result = 0;
if(t>f) {
result = 1;
}
else if(f>t) {
result = -1;
}
return result;
}
}
我已经删除了所有以前的版本。 我要感谢:
I'm trying to work with fractions in Java.
I want to implement arithmetic functions. For this, I will first require a way to normalize the functions. I know I can't add 1/6 and 1/2 until I have a common denominator. I will have to add 1/6 and 3/6. A naive approach would have me add 2/12 and 6/12 and then reduce. How can I achieve a common denominator with the least performance penalty? What algorithm is best for this?
Version 8 (thanks to hstoerr):
Improvements include:
- the equals() method is now consistent with the compareTo() method
final class Fraction extends Number {
private int numerator;
private int denominator;
public Fraction(int numerator, int denominator) {
if(denominator == 0) {
throw new IllegalArgumentException("denominator is zero");
}
if(denominator < 0) {
numerator *= -1;
denominator *= -1;
}
this.numerator = numerator;
this.denominator = denominator;
}
public Fraction(int numerator) {
this.numerator = numerator;
this.denominator = 1;
}
public int getNumerator() {
return this.numerator;
}
public int getDenominator() {
return this.denominator;
}
public byte byteValue() {
return (byte) this.doubleValue();
}
public double doubleValue() {
return ((double) numerator)/((double) denominator);
}
public float floatValue() {
return (float) this.doubleValue();
}
public int intValue() {
return (int) this.doubleValue();
}
public long longValue() {
return (long) this.doubleValue();
}
public short shortValue() {
return (short) this.doubleValue();
}
public boolean equals(Fraction frac) {
return this.compareTo(frac) == 0;
}
public int compareTo(Fraction frac) {
long t = this.getNumerator() * frac.getDenominator();
long f = frac.getNumerator() * this.getDenominator();
int result = 0;
if(t>f) {
result = 1;
}
else if(f>t) {
result = -1;
}
return result;
}
}
I have removed all previous versions. My thanks to:
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(26)
碰巧我不久前为 Project Euler 问题编写了一个 BigFraction 类。 它保留 BigInteger 分子和分母,因此永远不会溢出。 但对于许多你知道永远不会溢出的操作来说,它会有点慢......无论如何,如果你想要的话就使用它。 我一直渴望以某种方式展示这一点。 :)
编辑:此代码的最新、最好的版本,包括单元测试现在托管在 GitHub 上 以及 可通过 Maven Central 获取。 我将原始代码留在这里,以便这个答案不仅仅是一个链接......
It just so happens that I wrote a BigFraction class not too long ago, for Project Euler problems. It keeps a BigInteger numerator and denominator, so it'll never overflow. But it'll be a tad slow for a lot of operations that you know will never overflow.. anyway, use it if you want it. I've been dying to show this off somehow. :)
Edit: Latest and greatest version of this code, including unit tests is now hosted on GitHub and also available via Maven Central. I'm leaving my original code here so that this answer isn't just a link...
BigInteger
存储任意精确的值。 如果不是,那么long
,它的实现更容易;数字
;Comparable
< /a>;equals()
和hashCode()
;String
表示的数字添加工厂方法;toString()< /代码>
; 并
可序列化
。事实上,请试穿一下尺寸。 它运行但可能有一些问题:
输出是:
BigInteger
to store arbitrarilyy-precise values. If not that thenlong
, which has an easier implementation;Number
;Comparable<T>
;equals()
andhashCode()
;String
;toString()
; andSerializable
.In fact, try this on for size. It runs but may have some issues:
Output is:
Apache Commons Math 已经有一个 Fraction 类已经有一段时间了。 大多数时候的答案是:“我希望 Java 在核心库中有像 X 这样的东西!” 可以在 Apache Commons 库 下找到。
Apache Commons Math has had a Fraction class for quite some time. Most times the answer to, "Boy I wish Java had something like X in the core library!" can be found under the umbrella of the Apache Commons library.
请使其成为不可变类型! 分数的值不会改变——例如,二分之一不会变成三分之一。 您可以使用 withDenominator 代替 setDenominator,它返回一个新分数,该分数具有相同的分子但具有指定的分母。
使用不可变类型,生活会变得容易。
覆盖 equals 和 hashcode 也是明智的,因此它可以在映射和集合中使用。 Outlaw Programmer 关于算术运算符和字符串格式的观点也很好。
作为一般指南,请查看 BigInteger 和 BigDecimal。 他们做的事情并不相同,但他们足够相似,可以给你提供很好的想法。
Please make it an immutable type! The value of a fraction doesn't change - a half doesn't become a third, for example. Instead of setDenominator, you could have withDenominator which returns a new fraction which has the same numerator but the specified denominator.
Life is much easier with immutable types.
Overriding equals and hashcode would be sensible too, so it can be used in maps and sets. Outlaw Programmer's points about arithmetic operators and string formatting are good too.
As a general guide, have a look at BigInteger and BigDecimal. They're not doing the same thing, but they're similar enough to give you good ideas.
嗯,一方面,我会去掉 setter 并使 Fractions 不可变。
您可能还需要加法、减法等方法,也许还需要某种方法来获取各种字符串格式的表示形式。
编辑:我可能会将这些字段标记为“最终”以表明我的意图,但我想这没什么大不了的......
Well, for one, I'd get rid of the setters and make Fractions immutable.
You'll probably also want methods to add, subtract, etc., and maybe some way to get the representation in various String formats.
EDIT: I'd probably mark the fields as 'final' to signal my intent but I guess it's not a big deal...
是绝对必要的。 (事实上,如果您想正确处理相等性,请不要依赖 double 来正常工作。)如果 b*d 为正,则 a/b < c/d 如果 ad < 公元前。 如果涉及到负整数,可以适当处理...
我可能会重写为:
这里使用
long
是为了确保在将两个大int
相乘时不会溢出。代码>s. 如果你能保证分母总是非负的(如果是负数,只需将分子和分母都取负即可),那么你就可以不必检查 b*d 是否为正,并节省一些步骤。 我不确定您正在寻找零分母的什么行为。不确定与使用双打进行比较相比性能如何。 (也就是说,如果您非常关心性能)这是我用来检查的测试方法。 (看起来工作正常。)
(ps,您可能会考虑重组以实现您的类的
Comparable
或Comparator
。)Not strictly necessary. (In fact if you want to handle equality correctly, don't rely on double to work properly.) If b*d is positive, a/b < c/d if ad < bc. If there are negative integers involved, that can be handled appropriately...
I might rewrite as:
The use of
long
here is to ensure there's not an overflow if you multiply two largeint
s. handle If you can guarantee that the denominator is always nonnegative (if it's negative, just negate both numerator and denominator), then you can get rid of having to check whether b*d is positive and save a few steps. I'm not sure what behavior you're looking for with zero denominator.Not sure how performance compares to using doubles to compare. (that is, if you care about performance that much) Here's a test method I used to check. (Appears to work properly.)
(p.s. you might consider restructuring to implement
Comparable
orComparator
for your class.)一个非常小的改进可能是保存您正在计算的双精度值,以便您仅在第一次访问时计算它。 除非您经常访问这个数字,否则这不会是一个巨大的胜利,但这也不是太难做到。
另外一点可能是您在分母中进行的错误检查...您会自动将 0 更改为 1。不确定这对于您的特定应用程序是否正确,但一般来说,如果有人尝试除以 0,则表示出现了非常错误的情况。 我会让它抛出一个异常(如果您认为需要的话,这是一个专门的异常),而不是以用户不知道的看似任意的方式更改值。
与其他一些评论相比,关于添加方法来添加减法等......因为您没有提到需要它们,所以我假设您不需要。 除非您正在构建一个真正要在许多地方或由其他人使用的库,否则请使用 YAGNI(您不会需要它,所以它不应该在那里。)
One very minor improvement could potentially be to save the double value that you're computing so that you only compute it on the first access. This won't be a big win unless you're accessing this number a lot, but it's not overly difficult to do, either.
One additional point might be the error checking you do in the denominator...you automatically change 0 to 1. Not sure if this is correct for your particular application, but in general if someone is trying to divide by 0, something is very wrong. I'd let this throw an exception (a specialized exception if you feel it's needed) rather than change the value in a seemingly arbitrary way that isn't known to the user.
In constrast with some other comments, about adding methods to add subtract, etc...since you didn't mention needing them, I'm assuming you don't. And unless you're building a library that is really going to be used in many places or by other people, go with YAGNI (you ain't going to need it, so it shouldn't be there.)
有多种方法可以改进此值类型或任何值类型:
基本上,查看其他值类的 API,例如 Double,整数并做他们所做的:)
There are several ways to improve this or any value type:
Basically, take a look at the API for other value classes like Double, Integer and do what they do :)
如果将一个分数的分子和分母与另一个分数的分母相乘,反之亦然,最终会得到两个具有相同分母的分数(仍然是相同的值),您可以直接比较分子。 因此您不需要计算双精度值:
If you multiply the numerator and denominator of one Fraction with the denominator of the other and vice versa, you end up with two fractions (that are still the same values) with the same denominator and you can compare the numerators directly. Therefore you wouldn't need to calculate the double value:
我将如何改进该代码:
how I would improve that code:
你已经有一个compareTo函数...我将实现Comparable接口。
不过,对于你要用它做什么可能并不重要。
You have a compareTo function already ... I would implement the Comparable interface.
May not really matter for whatever you're going to do with it though.
如果您喜欢冒险,请查看 JScience。 它有一个
Rational
类代表分数。If you're feeling adventurous, take a look at JScience. It has a
Rational
class that represents fractions.我会说抛出一个 ArithmeticException 来除以零,因为这确实发生了:
而不是“除以零。”,您可能想让消息说“除以零:分数的分母为零。”
I would say throw a ArithmeticException for divide by zero, since that's really what's happening:
Instead of "Divide by zero.", you might want to make the message say "Divide by zero: Denominator for Fraction is zero."
创建分数对象后,为什么要允许其他对象设置分子或分母? 我认为这些应该是只读的。 它使对象不可变...
另外...将分母设置为零应该抛出无效参数异常(我不知道它在Java中是什么)
Once you've created a fraction object why would you want to allow other objects to set the numerator or the denominator? I would think these should be read only. It makes the object immutable...
Also...setting the denominator to zero should throw an invalid argument exception (I don't know what it is in Java)
Timothy Budd 在他的“C++ 数据结构”中对 Rational 类进行了很好的实现。 当然,不同的语言,但它可以很好地移植到 Java。
我会推荐更多的构造函数。 默认构造函数的分子为 0,分母为 1。单个 arg 构造函数将假定分母为 1。想想您的用户可能如何使用此类。
不检查零分母吗? 按合同编程会让你添加它。
Timothy Budd has a fine implementation of a Rational class in his "Data Structures in C++". Different language, of course, but it ports over to Java very nicely.
I'd recommend more constructors. A default constructor would have numerator 0, denominator 1. A single arg constructor would assume a denominator of 1. Think how your users might use this class.
No check for zero denominator? Programming by contract would have you add it.
我将提出第三个或第五个或任何使您的分数不可变的建议。 我还建议您扩展 Number 类。 我可能会看看 Double类,因为您可能想要实现许多相同的方法。
您可能还应该实现 Comparable 和 < a href="http://java.sun.com/javase/6/docs/api/java/io/Serializing.html" rel="nofollow noreferrer">Serialized 因为这种行为可能是预期的。 因此,您需要实现compareTo()。 您还需要重写 equals(),并且我必须强调您还需要重写 hashCode()。 这可能是您不希望compareTo()和equals()保持一致的少数情况之一,因为可彼此约简的分数不一定相等。
I'll third or fifth or whatever the recommendation for making your fraction immutable. I'd also recommend that you have it extend the Number class. I'd probably look at the Double class, since you're probably going to want to implement many of the same methods.
You should probably also implement Comparable and Serializable since this behavior will probably be expected. Thus, you will need to implement compareTo(). You will also need to override equals() and I cannot stress strongly enough that you also override hashCode(). This might be one of the few cases though where you don't want compareTo() and equals() to be consistent since fractions reducable to each other are not necessarily equal.
我喜欢的一种清理做法是只有一次返回。
A clean up practice that I like is to only have only one return.
使用 JScience 库中的 Rational 类。 这是我在 Java 中看到的最适合小数算术的东西。
Use Rational class from JScience library. It's the best thing for fractional arithmetic I seen in Java.
我清理了 cletus 的答案:
valueOf(String)
中的自定义解析替换为BigInteger(String)
,后者更灵活、更快。I cleaned up cletus' answer:
valueOf(String)
with theBigInteger(String)
which is both more flexible and faster.最初的评论:
永远不要这样写:
这样要好得多,
只是为了养成一个好习惯。
通过按照建议使类不可变,您还可以利用 double 来执行 equals 和 hashCode 以及compareTo 操作
这是我的快速肮脏版本:
关于静态工厂方法,如果您对要处理的 Fraction 进行子类化,以后它可能会很有用更复杂的事情,或者如果您决定对最常用的对象使用池。
事实可能并非如此,我只是想指出这一点。 :)
请参阅 Effective Java 第一项。
Initial remark:
Never write this:
This is much better
Just create to create a good habit.
By making the class immutable as suggested, you can also take advantage of the double to perform the equals and hashCode and compareTo operations
Here's my quick dirty version:
About the static factory method, it may be useful later, if you subclass the Fraction to handle more complex things, or if you decide to use a pool for the most frequently used objects.
It may not be the case, I just wanted to point it out. :)
See Effective Java first item.
添加一些简单的东西可能会有用,比如往复、取余数和得到整体。
Might be useful to add simple things like reciprocate, get remainder and get whole.
即使您有compareTo()方法,如果您想使用像Collections.sort()这样的实用程序,那么您还应该实现Comparable。
另外,为了漂亮的显示,我建议重写 toString()
最后,我将类公开,以便您可以从不同的包中使用它。
Even though you have the methods compareTo(), if you want to make use of utilities like Collections.sort(), then you should also implement Comparable.
Also, for pretty display I recommend overriding toString()
And finally, I'd make the class public so that you can use it from different packages.
这个函数使用欧几里得算法进行简化在定义分数时非常有用
This function simplify using the eucledian algorithm is quite useful when defining fractions
对于工业级分数/有理数实现,我将实现它,以便它可以表示 NaN、正无穷大、负无穷大和可选的负零,其操作语义与浮点算术的 IEEE 754 标准状态完全相同(它还简化了与浮点值的转换)。 另外,由于与零、一和上面的特殊值进行比较只需要简单地将分子和分母与 0 和 1 进行组合比较 - 我将添加几个 isXXX 和 CompareToXXX 方法以方便使用(例如 eq0() 会在幕后使用分子 == 0 && 分母 != 0 而不是让客户端与零值实例进行比较)。 一些静态预定义值(ZERO、ONE、TWO、TEN、ONE_TENTH、NAN 等)也很有用,因为它们作为常量值出现在多个位置。 恕我直言,这是最好的方法。
For industry-grade Fraction/Rational implementation, I would implement it so it can represent NaN, positive infinity, negative infinity, and optionally negative zero with operational semantics exactly the same as the IEEE 754 standard states for floating point arithmetics (it also eases the conversion to/from floating point values). Plus, since comparison to zero, one, and the special values above only needs simple, but combined comparison of the numerator and denominator against 0 and 1 - i would add several isXXX and compareToXXX methods for ease of use (eg. eq0() would use numerator == 0 && denominator != 0 behind the scenes instead of letting the client to compare against a zero valued instance). Some statically predefined values (ZERO, ONE, TWO, TEN, ONE_TENTH, NAN, etc.) are also useful, since they appear at several places as constant values. This is the best way IMHO.
类分数:
主程序:
Class Fraction:
The main program: