I wrote a Java program that calculates values for the Riemann Zeta Function. Inside the program, I made a library to calculate necessary complex functions such as atan, cos, etc. Everything inside both programs is accessed through the double and BigDecimal data types. This creates major issues when evaluating large values for the Zeta function.
The numerical approximation for the Zeta function references
Directly evaluating this approximation at high values creates issues when s has a large complex form, such as s = (230+30i). I am very grateful to get information about this here. The evaluation of S2.minus(S1) creates errors because I wrote something wrong in the adaptiveQuad method.
As an example, Zeta(2+3i) through this program generates
Calculation of the Riemann Zeta Function in the form Zeta(s) = a + ib.
Enter the value of [a] inside the Riemann Zeta Function: 2
Enter the value of [b] inside the Riemann Zeta Function: 3
The value for Zeta(s) is 7.980219851133409E-1 - 1.137443081631288E-1*i
Total time taken is 0.469 seconds.
Which is correct.
Zeta(100+0i) generates
Calculation of the Riemann Zeta Function in the form Zeta(s) = a + ib.
Enter the value of [a] inside the Riemann Zeta Function: 100
Enter the value of [b] inside the Riemann Zeta Function: 0
The value for Zeta(s) is 1.000000000153236E0
Total time taken is 0.672 seconds.
Which is also correct as compared to Wolfram. The problem is due to something inside the method labelled adaptiveQuad.
Zeta(230+30i) generates
Calculation of the Riemann Zeta Function in the form Zeta(s) = a + ib.
Enter the value of [a] inside the Riemann Zeta Function: 230
Enter the value of [b] inside the Riemann Zeta Function: 30
The value for Zeta(s) is 0.999999999999093108519845391615339162047254997503854254342793916541606842461539820124897870147977114468145672577664412128509813042591501204781683860384769321084473925620572315416715721728082468412672467499199310913504362891199180150973087384370909918493750428733837552915328069343498987460727711606978118652477860450744628906250 - 38.005428584222228490409289204403133867487950535704812764806874887805043029499897666636162309572126423385487374863788363786029170239477119910868455777891701471328505006916099918492113970510619110472506796418206225648616641319533972054228283869713393805956289770456519729094756021581247296126093715429306030273437500E-15*i
Total time taken is 1.746 seconds.
The imaginary part is a bit off as compared to Wolfram.
The algorithm to evaluate the integral is known as Adaptive Quadrature and a double Java implementation is found here. The adaptive quad method applies the following
// adaptive quadrature
public static double adaptive(double a, double b) {
double h = b - a;
double c = (a + b) / 2.0;
double d = (a + c) / 2.0;
double e = (b + c) / 2.0;
double Q1 = h/6 * (f(a) + 4*f(c) + f(b));
double Q2 = h/12 * (f(a) + 4*f(d) + 2*f(c) + 4*f(e) + f(b));
if (Math.abs(Q2 - Q1) <= EPSILON)
return Q2 + (Q2 - Q1) / 15;
else
return adaptive(a, c) + adaptive(c, b);
}
Here is my fourth attempt at writing the program
/**************************************************************************
**
** Abel-Plana Formula for the Zeta Function
**
**************************************************************************
** Axion004
** 08/16/2015
**
** This program computes the value for Zeta(z) using a definite integral
** approximation through the Abel-Plana formula. The Abel-Plana formula
** can be shown to approximate the value for Zeta(s) through a definite
** integral. The integral approximation is handled through the Composite
** Simpson's Rule known as Adaptive Quadrature.
**************************************************************************/
import java.util.*;
import java.math.*;
public class AbelMain5 extends Complex {
private static MathContext MC = new MathContext(512,
RoundingMode.HALF_EVEN);
public static void main(String[] args) {
AbelMain();
}
// Main method
public static void AbelMain() {
double re = 0, im = 0;
double start, stop, totalTime;
Scanner scan = new Scanner(System.in);
System.out.println("Calculation of the Riemann Zeta " +
"Function in the form Zeta(s) = a + ib.");
System.out.println();
System.out.print("Enter the value of [a] inside the Riemann Zeta " +
"Function: ");
try {
re = scan.nextDouble();
}
catch (Exception e) {
System.out.println("Please enter a valid number for a.");
}
System.out.print("Enter the value of [b] inside the Riemann Zeta " +
"Function: ");
try {
im = scan.nextDouble();
}
catch (Exception e) {
System.out.println("Please enter a valid number for b.");
}
start = System.currentTimeMillis();
Complex z = new Complex(new BigDecimal(re), new BigDecimal(im));
System.out.println("The value for Zeta(s) is " + AbelPlana(z));
stop = System.currentTimeMillis();
totalTime = (double) (stop-start) / 1000.0;
System.out.println("Total time taken is " + totalTime + " seconds.");
}
/**
* The definite integral for Zeta(z) in the Abel-Plana formula.
* <br> Numerator = Sin(z * arctan(t))
* <br> Denominator = (1 + t^2)^(z/2) * (e^(2*pi*t) - 1)
* #param t - the value of t passed into the integrand.
* #param z - The complex value of z = a + i*b
* #return the value of the complex function.
*/
public static Complex f(double t, Complex z) {
Complex num = (z.multiply(Math.atan(t))).sin();
Complex D1 = new Complex(1 + t*t).pow(z.divide(TWO));
Complex D2 = new Complex(Math.pow(Math.E, 2.0*Math.PI*t) - 1.0);
Complex den = D1.multiply(D2);
return num.divide(den, MC);
}
/**
* Adaptive quadrature - See http://www.mathworks.com/moler/quad.pdf
* #param a - the lower bound of integration.
* #param b - the upper bound of integration.
* #param z - The complex value of z = a + i*b
* #return the approximate numerical value of the integral.
*/
public static Complex adaptiveQuad(double a, double b, Complex z) {
double EPSILON = 1E-10;
double step = b - a;
double c = (a + b) / 2.0;
double d = (a + c) / 2.0;
double e = (b + c) / 2.0;
Complex S1 = (f(a, z).add(f(c, z).multiply(FOUR)).add(f(b, z))).
multiply(step / 6.0);
Complex S2 = (f(a, z).add(f(d, z).multiply(FOUR)).add(f(c, z).multiply
(TWO)).add(f(e, z).multiply(FOUR)).add(f(b, z))).multiply
(step / 12.0);
Complex result = (S2.subtract(S1)).divide(FIFTEEN, MC);
if(S2.subtract(S1).mod() <= EPSILON)
return S2.add(result);
else
return adaptiveQuad(a, c, z).add(adaptiveQuad(c, b, z));
}
/**
* The definite integral for Zeta(z) in the Abel-Plana formula.
* <br> value = 1/2 + 1/(z-1) + 2 * Integral
* #param z - The complex value of z = a + i*b
* #return the value of Zeta(z) through value and the
* quadrature approximation.
*/
public static Complex AbelPlana(Complex z) {
Complex C1 = ONEHALF.add(ONE.divide(z.subtract(ONE), MC));
Complex C2 = TWO.multiply(adaptiveQuad(1E-16, 100.0, z));
if ( z.real().doubleValue() == 0 && z.imag().doubleValue() == 0)
return new Complex(0.0, 0.0);
else
return C1.add(C2);
}
}
Complex numbers (BigDecimal)
/**************************************************************************
**
** Complex Numbers
**
**************************************************************************
** Axion004
** 08/20/2015
**
** This class is necessary as a helper class for the calculation of
** imaginary numbers. The calculation of Zeta(z) inside AbelMain is in
** the form of z = a + i*b.
**************************************************************************/
import java.math.BigDecimal;
import java.math.MathContext;
import java.text.DecimalFormat;
import java.text.NumberFormat;
public class Complex extends Object{
private BigDecimal re;
private BigDecimal im;
/**
BigDecimal constant for zero
*/
final static Complex ZERO = new Complex(BigDecimal.ZERO) ;
/**
BigDecimal constant for one half
*/
final static Complex ONEHALF = new Complex(new BigDecimal(0.5));
/**
BigDecimal constant for one
*/
final static Complex ONE = new Complex(BigDecimal.ONE);
/**
BigDecimal constant for two
*/
final static Complex TWO = new Complex(new BigDecimal(2.0));
/**
BigDecimal constant for four
*/
final static Complex FOUR = new Complex(new BigDecimal(4.0)) ;
/**
BigDecimal constant for fifteen
*/
final static Complex FIFTEEN = new Complex(new BigDecimal(15.0)) ;
/**
Default constructor equivalent to zero
*/
public Complex() {
re = BigDecimal.ZERO;
im = BigDecimal.ZERO;
}
/**
Constructor with real part only
#param x Real part, BigDecimal
*/
public Complex(BigDecimal x) {
re = x;
im = BigDecimal.ZERO;
}
/**
Constructor with real part only
#param x Real part, double
*/
public Complex(double x) {
re = new BigDecimal(x);
im = BigDecimal.ZERO;
}
/**
Constructor with real and imaginary parts in double format.
#param x Real part
#param y Imaginary part
*/
public Complex(double x, double y) {
re= new BigDecimal(x);
im= new BigDecimal(y);
}
/**
Constructor for the complex number z = a + i*b
#param re Real part
#param im Imaginary part
*/
public Complex (BigDecimal re, BigDecimal im) {
this.re = re;
this.im = im;
}
/**
Real part of the Complex number
#return Re[z] where z = a + i*b.
*/
public BigDecimal real() {
return re;
}
/**
Imaginary part of the Complex number
#return Im[z] where z = a + i*b.
*/
public BigDecimal imag() {
return im;
}
/**
Complex conjugate of the Complex number
in which the conjugate of z is z-bar.
#return z-bar where z = a + i*b and z-bar = a - i*b
*/
public Complex conjugate() {
return new Complex(re, im.negate());
}
/**
* Returns the sum of this and the parameter.
#param augend the number to add
#param mc the context to use
#return this + augend
*/
public Complex add(Complex augend,MathContext mc)
{
//(a+bi)+(c+di) = (a + c) + (b + d)i
return new Complex(
re.add(augend.re,mc),
im.add(augend.im,mc));
}
/**
Equivalent to add(augend, MathContext.UNLIMITED)
#param augend the number to add
#return this + augend
*/
public Complex add(Complex augend)
{
return add(augend, MathContext.UNLIMITED);
}
/**
Addition of Complex number and a double.
#param d is the number to add.
#return z+d where z = a+i*b and d = double
*/
public Complex add(double d){
BigDecimal augend = new BigDecimal(d);
return new Complex(this.re.add(augend, MathContext.UNLIMITED),
this.im);
}
/**
* Returns the difference of this and the parameter.
#param subtrahend the number to subtract
#param mc the context to use
#return this - subtrahend
*/
public Complex subtract(Complex subtrahend, MathContext mc)
{
//(a+bi)-(c+di) = (a - c) + (b - d)i
return new Complex(
re.subtract(subtrahend.re,mc),
im.subtract(subtrahend.im,mc));
}
/**
* Equivalent to subtract(subtrahend, MathContext.UNLIMITED)
#param subtrahend the number to subtract
#return this - subtrahend
*/
public Complex subtract(Complex subtrahend)
{
return subtract(subtrahend,MathContext.UNLIMITED);
}
/**
Subtraction of Complex number and a double.
#param d is the number to subtract.
#return z-d where z = a+i*b and d = double
*/
public Complex subtract(double d){
BigDecimal subtrahend = new BigDecimal(d);
return new Complex(this.re.subtract(subtrahend, MathContext.UNLIMITED),
this.im);
}
/**
* Returns the product of this and the parameter.
#param multiplicand the number to multiply by
#param mc the context to use
#return this * multiplicand
*/
public Complex multiply(Complex multiplicand, MathContext mc)
{
//(a+bi)(c+di) = (ac - bd) + (ad + bc)i
return new Complex(
re.multiply(multiplicand.re,mc).subtract(im.multiply
(multiplicand.im,mc),mc),
re.multiply(multiplicand.im,mc).add(im.multiply
(multiplicand.re,mc),mc));
}
/**
Equivalent to multiply(multiplicand, MathContext.UNLIMITED)
#param multiplicand the number to multiply by
#return this * multiplicand
*/
public Complex multiply(Complex multiplicand)
{
return multiply(multiplicand,MathContext.UNLIMITED);
}
/**
Complex multiplication by a double.
#param d is the double to multiply by.
#return z*d where z = a+i*b and d = double
*/
public Complex multiply(double d){
BigDecimal multiplicand = new BigDecimal(d);
return new Complex(this.re.multiply(multiplicand, MathContext.UNLIMITED)
,this.im.multiply(multiplicand, MathContext.UNLIMITED));
}
/**
Modulus of a Complex number or the distance from the origin in
* the polar coordinate plane.
#return |z| where z = a + i*b.
*/
public double mod() {
if ( re.doubleValue() != 0.0 || im.doubleValue() != 0.0)
return Math.sqrt(re.multiply(re).add(im.multiply(im))
.doubleValue());
else
return 0.0;
}
/**
* Modulus of a Complex number squared
* #param z = a + i*b
* #return |z|^2 where z = a + i*b
*/
public double abs(Complex z) {
double doubleRe = re.doubleValue();
double doubleIm = im.doubleValue();
return doubleRe * doubleRe + doubleIm * doubleIm;
}
public Complex divide(Complex divisor)
{
return divide(divisor,MathContext.UNLIMITED);
}
/**
* The absolute value squared.
* #return The sum of the squares of real and imaginary parts.
* This is the square of Complex.abs() .
*/
public BigDecimal norm()
{
return re.multiply(re).add(im.multiply(im)) ;
}
/**
* The absolute value of a BigDecimal.
* #param mc amount of precision
* #return BigDecimal.abs()
*/
public BigDecimal abs(MathContext mc)
{
return BigDecimalMath.sqrt(norm(),mc) ;
}
/** The inverse of the the Complex number.
#param mc amount of precision
#return 1/this
*/
public Complex inverse(MathContext mc)
{
final BigDecimal hyp = norm() ;
/* 1/(x+iy)= (x-iy)/(x^2+y^2 */
return new Complex( re.divide(hyp,mc), im.divide(hyp,mc)
.negate() ) ;
}
/** Divide through another BigComplex number.
#param oth the other complex number
#param mc amount of precision
#return this/other
*/
public Complex divide(Complex oth, MathContext mc)
{
/* implementation: (x+iy)/(a+ib)= (x+iy)* 1/(a+ib) */
return multiply(oth.inverse(mc),mc) ;
}
/**
Division of Complex number by a double.
#param d is the double to divide
#return new Complex number z/d where z = a+i*b
*/
public Complex divide(double d){
BigDecimal divisor = new BigDecimal(d);
return new Complex(this.re.divide(divisor, MathContext.UNLIMITED),
this.im.divide(divisor, MathContext.UNLIMITED));
}
/**
Exponential of a complex number (z is unchanged).
<br> e^(a+i*b) = e^a * e^(i*b) = e^a * (cos(b) + i*sin(b))
#return exp(z) where z = a+i*b
*/
public Complex exp () {
return new Complex(Math.exp(re.doubleValue()) * Math.cos(im.
doubleValue()), Math.exp(re.doubleValue()) *
Math.sin(im.doubleValue()));
}
/**
The Argument of a Complex number or the angle in radians
with respect to polar coordinates.
<br> Tan(theta) = b / a, theta = Arctan(b / a)
<br> a is the real part on the horizontal axis
<br> b is the imaginary part of the vertical axis
#return arg(z) where z = a+i*b.
*/
public double arg() {
return Math.atan2(im.doubleValue(), re.doubleValue());
}
/**
The log or principal branch of a Complex number (z is unchanged).
<br> Log(a+i*b) = ln|a+i*b| + i*Arg(z) = ln(sqrt(a^2+b^2))
* + i*Arg(z) = ln (mod(z)) + i*Arctan(b/a)
#return log(z) where z = a+i*b
*/
public Complex log() {
return new Complex(Math.log(this.mod()), this.arg());
}
/**
The square root of a Complex number (z is unchanged).
Returns the principal branch of the square root.
<br> z = e^(i*theta) = r*cos(theta) + i*r*sin(theta)
<br> r = sqrt(a^2+b^2)
<br> cos(theta) = a / r, sin(theta) = b / r
<br> By De Moivre's Theorem, sqrt(z) = sqrt(a+i*b) =
* e^(i*theta / 2) = r(cos(theta/2) + i*sin(theta/2))
#return sqrt(z) where z = a+i*b
*/
public Complex sqrt() {
double r = this.mod();
double halfTheta = this.arg() / 2;
return new Complex(Math.sqrt(r) * Math.cos(halfTheta), Math.sqrt(r) *
Math.sin(halfTheta));
}
/**
The real cosh function for Complex numbers.
<br> cosh(theta) = (e^(theta) + e^(-theta)) / 2
#return cosh(theta)
*/
private double cosh(double theta) {
return (Math.exp(theta) + Math.exp(-theta)) / 2;
}
/**
The real sinh function for Complex numbers.
<br> sinh(theta) = (e^(theta) - e^(-theta)) / 2
#return sinh(theta)
*/
private double sinh(double theta) {
return (Math.exp(theta) - Math.exp(-theta)) / 2;
}
/**
The sin function for the Complex number (z is unchanged).
<br> sin(a+i*b) = cosh(b)*sin(a) + i*(sinh(b)*cos(a))
#return sin(z) where z = a+i*b
*/
public Complex sin() {
return new Complex(cosh(im.doubleValue()) * Math.sin(re.doubleValue()),
sinh(im.doubleValue())* Math.cos(re.doubleValue()));
}
/**
The cos function for the Complex number (z is unchanged).
<br> cos(a +i*b) = cosh(b)*cos(a) + i*(-sinh(b)*sin(a))
#return cos(z) where z = a+i*b
*/
public Complex cos() {
return new Complex(cosh(im.doubleValue()) * Math.cos(re.doubleValue()),
-sinh(im.doubleValue()) * Math.sin(re.doubleValue()));
}
/**
The hyperbolic sin of the Complex number (z is unchanged).
<br> sinh(a+i*b) = sinh(a)*cos(b) + i*(cosh(a)*sin(b))
#return sinh(z) where z = a+i*b
*/
public Complex sinh() {
return new Complex(sinh(re.doubleValue()) * Math.cos(im.doubleValue()),
cosh(re.doubleValue()) * Math.sin(im.doubleValue()));
}
/**
The hyperbolic cosine of the Complex number (z is unchanged).
<br> cosh(a+i*b) = cosh(a)*cos(b) + i*(sinh(a)*sin(b))
#return cosh(z) where z = a+i*b
*/
public Complex cosh() {
return new Complex(cosh(re.doubleValue()) *Math.cos(im.doubleValue()),
sinh(re.doubleValue()) * Math.sin(im.doubleValue()));
}
/**
The tan of the Complex number (z is unchanged).
<br> tan (a+i*b) = sin(a+i*b) / cos(a+i*b)
#return tan(z) where z = a+i*b
*/
public Complex tan() {
return (this.sin()).divide(this.cos());
}
/**
The arctan of the Complex number (z is unchanged).
<br> tan^(-1)(a+i*b) = 1/2 i*(log(1-i*(a+b*i))-log(1+i*(a+b*i))) =
<br> -1/2 i*(log(i*a - b+1)-log(-i*a + b+1))
#return arctan(z) where z = a+i*b
*/
public Complex atan(){
Complex ima = new Complex(0.0,-1.0); //multiply by negative i
Complex num = new Complex(this.re.doubleValue(),this.im.doubleValue()
-1.0);
Complex den = new Complex(this.re.negate().doubleValue(),this.im
.negate().doubleValue()-1.0);
Complex two = new Complex(2.0, 0.0); // divide by 2
return ima.multiply(num.divide(den).log()).divide(two);
}
/**
* The Math.pow equivalent of two Complex numbers.
* #param z - the complex base in the form z = a + i*b
* #return z^y where z = a + i*b and y = c + i*d
*/
public Complex pow(Complex z){
Complex a = z.multiply(this.log(), MathContext.UNLIMITED);
return a.exp();
}
/**
* The Math.pow equivalent of a Complex number to the power
* of a double.
* #param d - the double to be taken as the power.
* #return z^d where z = a + i*b and d = double
*/
public Complex pow(double d){
Complex a=(this.log()).multiply(d);
return a.exp();
}
/**
Override the .toString() method to generate complex numbers, the
* string representation is now a literal Complex number.
#return a+i*b, a-i*b, a, or i*b as desired.
*/
public String toString() {
NumberFormat formatter = new DecimalFormat();
formatter = new DecimalFormat("#.###############E0");
if (re.doubleValue() != 0.0 && im.doubleValue() > 0.0) {
return formatter.format(re) + " + " + formatter.format(im)
+"*i";
}
if (re.doubleValue() !=0.0 && im.doubleValue() < 0.0) {
return formatter.format(re) + " - "+ formatter.format(im.negate())
+ "*i";
}
if (im.doubleValue() == 0.0) {
return formatter.format(re);
}
if (re.doubleValue() == 0.0) {
return formatter.format(im) + "*i";
}
return formatter.format(re) + " + i*" + formatter.format(im);
}
}
I am reviewing the answer below.
One problem may be due to
Complex num = (z.multiply(Math.atan(t))).sin();
Complex D1 = new Complex(1 + t*t).pow(z.divide(TWO));
Complex D2 = new Complex(Math.pow(Math.E, 2.0*Math.PI*t) - 1.0);
Complex den = D1.multiply(D2, MathContext.UNLIMITED);
I am not applying BigDecimal.pow(BigDecimal). Although, I don't think this is the direct issue which causes the floating point arithmetic to create differences.
Edit: I tried a new integral approximation of the Zeta function. Ultimately, I will develop a new method to calculate BigDecimal.pow(BigDecimal).
Caveat I agree with all the comments in #laune's answer, but I get the impression you may wish to pursue this anyway. Make sure especially that you really do understand 1) and what that means for you - it's very easy to do a lot of heavy calculations to produce meaningless results.
Arbitrary precision floating point functions in Java
To reiterate a little, I think your problem really is with the maths and numerical method you have chosen, but here's an implementation using the Apfloat library. I'd strongly urge you to use the ready made arbitrary precision library (or a similar one) as it avoids any need for you to "roll your own" arbitrary precision maths functions (such as pow, exp,sin, atan etc). You say
Ultimately, I will develop a new method to calculate BigDecimal.pow(BigDecimal)
It's really hard to get that right.
You need to watch the precision of your constants, too - note I use an Apfloat sample implementation to calculate PI to a large number (for some definition of large!) of sig figs. I am to some degree trusting that the Apfloat library uses suitably precise values for e in exponentiation - the source is available if you want to check.
Different integral formulations to calculate zeta
You put up three different integration based methods in one of your edits:
The one labelled 25.5.12 is the one that you currently have in the question and (although that can be calculated at zero easily), it is hard to work with due to 2) in #laune's answer. I implemented 25.5.12 as integrand1() in the code - I urge you to plot it with range of t for different s = a + 0i and understand how it behaves. Or look at the plots in the zeta article on Wolfram's mathworld. The one labelled 25.5.11 I implemented via integrand2() and the code in the configuration I publish below.
Code
While I'm a bit reluctant to post code that will no doubt find wrong results in some configurations due to all the things above - I have encoded what you are trying to do below, using arbitrary precision floating point objects for the variables.
If you want to change which formulation you use (e.g. from 25.5.11 to 25.5.12), you can change which integrand the wrapper function f() returns or, better yet, change adaptiveQuad to take in an arbitrary integrand method wrapped in a class with an interface... You will also have to alter the arithmetic in findZeta() if you want to use one of the other integral formulations.
Play with the constants at the start to your heart's content. I haven't tested lots of combinations, as I think the maths problems here override the programming ones.
I've left it set up to do 2+3i in about 2000 calls to the adaptive quadature method and match the first 15 or so digits of the Wolfram value.
I've tested it still works with PRECISION = 120l and EPSILON=1e-15. The program matches Wolfram alpha in the first 18 or so significant figures for the three test cases you provide. The last one (230+30i) takes a long time even on a fast computer - it calls the integrand fucntion some 100,000+ times. Note that I use 40 for the value of INFINITY in the integral - not very high - but higher values exhibit the problem 1) as already discussed...
N.B. This is not fast (you'll be measuring in minutes or hours, not seconds - but you only get really quick if you want to accept that 10^-15 ~= 10^-70 as most people would!!). It will give you some digits that match Wolfram Alpha ;) You might want to take PRECISION down to about 20, INFINITY to 10 and EPSILON to 1e-10 to verify a few results with small s first... I've left in some printing so it tells you every 100th time adaptiveQuad is called for comfort.
Reiteration However good your precision - it's not going to overcome the mathematical characteristics of the functions involved in this way of calculating zeta. I strongly doubt this is how Wolfram alpha does it, for instance. Look up series summation methods if you want more tractable methods.
import java.io.PrintWriter;
import org.apfloat.ApcomplexMath;
import org.apfloat.Apcomplex;
import org.apfloat.Apfloat;
import org.apfloat.samples.Pi;
public class ZetaFinder
{
//Number of sig figs accuracy. Note that infinite should be reserved
private static long PRECISION = 40l;
// Convergence criterion for integration
static Apfloat EPSILON = new Apfloat("1e-15",PRECISION);
//Value of PI - enhanced using Apfloat library sample calculation of Pi in constructor,
//Fast enough that we don't need to hard code the value in.
//You could code hard value in for perf enhancement
static Apfloat PI = null; //new Apfloat("3.14159");
//Integration limits - I found too high a value for "infinity" causes integration
//to terminate on first iteration. Plot the integrand to see why...
static Apfloat INFINITE_LIMIT = new Apfloat("40",PRECISION);
static Apfloat ZERO_LIMIT = new Apfloat("1e-16",PRECISION); //You can use zero for the 25.5.12
static Apfloat one = new Apfloat("1",PRECISION);
static Apfloat two = new Apfloat("2",PRECISION);
static Apfloat four = new Apfloat("4",PRECISION);
static Apfloat six = new Apfloat("6",PRECISION);
static Apfloat twelve = new Apfloat("12",PRECISION);
static Apfloat fifteen = new Apfloat("15",PRECISION);
static int counter = 0;
Apcomplex s = null;
public ZetaFinder(Apcomplex s)
{
this.s = s;
Pi.setOut(new PrintWriter(System.out, true));
Pi.setErr(new PrintWriter(System.err, true));
PI = (new Pi.RamanujanPiCalculator(PRECISION+10, 10)).execute(); //Get Pi to a higher precision than integer consts
System.out.println("Created a Zeta Finder based on Abel-Plana for s="+s.toString() + " using PI="+PI.toString());
}
public static void main(String[] args)
{
Apfloat re = new Apfloat("2", PRECISION);
Apfloat im = new Apfloat("3", PRECISION);
Apcomplex s = new Apcomplex(re,im);
ZetaFinder finder = new ZetaFinder(s);
System.out.println(finder.findZeta());
}
private Apcomplex findZeta()
{
Apcomplex retval = null;
//Method currently in question (a.k.a. 25.5.12)
//Apcomplex mult = ApcomplexMath.pow(two, this.s);
//Apcomplex firstterm = (ApcomplexMath.pow(two, (this.s.add(one.negate())))).divide(this.s.add(one.negate()));
//Easier integrand method (a.k.a. 25.5.11)
Apcomplex mult = two;
Apcomplex firstterm = (one.divide(two)).add(one.divide(this.s.add(one.negate())));
Apfloat limita = ZERO_LIMIT;//Apfloat.ZERO;
Apfloat limitb = INFINITE_LIMIT;
System.out.println("Trying to integrate between " + limita.toString() + " and " + limitb.toString());
Apcomplex integral = adaptiveQuad(limita, limitb);
retval = firstterm.add((mult.multiply(integral)));
return retval;
}
private Apcomplex adaptiveQuad(Apfloat a, Apfloat b) {
//if (counter % 100 == 0)
{
System.out.println("In here for the " + counter + "th time");
}
counter++;
Apfloat h = b.add(a.negate());
Apfloat c = (a.add(b)).divide(two);
Apfloat d = (a.add(c)).divide(two);
Apfloat e = (b.add(c)).divide(two);
Apcomplex Q1 = (h.divide(six)).multiply(f(a).add(four.multiply(f(c))).add(f(b)));
Apcomplex Q2 = (h.divide(twelve)).multiply(f(a).add(four.multiply(f(d))).add(two.multiply(f(c))).add(four.multiply(f(e))).add(f(b)));
if (ApcomplexMath.abs(Q2.add(Q1.negate())).compareTo(EPSILON) < 0)
{
System.out.println("Returning");
return Q2.add((Q2.add(Q1.negate())).divide(fifteen));
}
else
{
System.out.println("Recursing with intervals "+a+" to " + c + " and " + c + " to " +d);
return adaptiveQuad(a, c).add(adaptiveQuad(c, b));
}
}
private Apcomplex f(Apfloat x)
{
return integrand2(x);
}
/*
* Simple test integrand (z^2)
*
* Can test implementation by asserting that the adaptiveQuad
* with this function evaluates to z^3 / 3
*/
private Apcomplex integrandTest(Apfloat t)
{
return ApcomplexMath.pow(t, two);
}
/*
* Abel-Plana formulation integrand
*/
private Apcomplex integrand1(Apfloat t)
{
Apcomplex numerator = ApcomplexMath.sin(this.s.multiply(ApcomplexMath.atan(t)));
Apcomplex bottomlinefirstbr = one.add(ApcomplexMath.pow(t, two));
Apcomplex D1 = ApcomplexMath.pow(bottomlinefirstbr, this.s.divide(two));
Apcomplex D2 = (ApcomplexMath.exp(PI.multiply(t))).add(one);
Apcomplex denominator = D1.multiply(D2);
Apcomplex retval = numerator.divide(denominator);
//System.out.println("Integrand evaluated at "+t+ " is "+retval);
return retval;
}
/*
* Abel-Plana formulation integrand 25.5.11
*/
private Apcomplex integrand2(Apfloat t)
{
Apcomplex numerator = ApcomplexMath.sin(this.s.multiply(ApcomplexMath.atan(t)));
Apcomplex bottomlinefirstbr = one.add(ApcomplexMath.pow(t, two));
Apcomplex D1 = ApcomplexMath.pow(bottomlinefirstbr, this.s.divide(two));
Apcomplex D2 = ApcomplexMath.exp(two.multiply(PI.multiply(t))).add(one.negate());
Apcomplex denominator = D1.multiply(D2);
Apcomplex retval = numerator.divide(denominator);
//System.out.println("Integrand evaluated at "+t+ " is "+retval);
return retval;
}
}
A note on "correctness"
Note that in your answer - you are calling zeta(2+3i) and zeta(100) "correct" as compared to Wolfram when they exhibit errors of ~1e-10 and ~1e-9 respectively (they differ in the 10th and 9th decimal place), but you are worried about zeta(230+30i) because it exhibits an error of order 10e-14 in the imaginary component (38e-15 vs 5e-70 which are both very near zero). So in some senses the one you are calling "wrong" is closer to the Wolfram value than the ones you call "correct". Maybe you are worried that the leading digits are different, but this isn't really a measure of accuracy there.
A final note
Unless you're doing it to learn about how functions behave and how floating point precision interacts with it - Don't do things this way. Even Apfloat's own documentation says:
This package is designed for extreme precision. The result might have
a few digits less than you'd expect (about 10) and the last few (about
10) digits in ther result might be inaccurate. If you plan to use
numbers with only a few hundred digits, use a program like PARI (it's
free and available from ftp://megrez.math.u-bordeaux.fr) or a
commercial program like Mathematica or Maple if possible.
I'd add mpmath in python to this list as a free alternative now.
(1) The integration uses adaptQuad, starting with an interval [0,10]. For z=a+ib with increasingly larger values of a and b=0, the integrand is an increasingly oscillating function, with the number of zeros in [0,5] alone being proportional to a and raising to 43 for z=100.
Therefore, starting the approximation with an interval that includes one or more zeros is risky, as the program as posted shows quite clearly. For z=100, the integrand is 0, -2.08E-78 and 7.12E-115 at 0, 5 and 10, respectively. Therefore, comparing the result of Simpson's formula to 1E-20 returns true, and the result is absolutely wrong.
(2) The computation in method AbelPlana involves two complex numbers, C1 and C2. For z=a+0i, they are real, and the table below shows their values for various values of a:
a C1 C2
10 5.689E1 1.024E3
20 2.759E4 1.048E6
30 1.851E7 1.073E9
40 1.409E10 1.099E12
60 9.770E15 1.152E18
100 6.402E27 1.267E30
Now we know that the values of ζ(a+0i) decrease towards 1 for increasing a. It is clearly impossible for two values above 1E15 to produce a meaningful result near one when subtracted from each other.
The table also suggests that for a good result of ζ(a+0i) by using this algorithm, C1 and C2*I (I is the integral) need to be computed with an accuracy of about 45 significant digits. (Arbitrary precision math does not avoid the pitfall described in (1).)
(3) Note that when using a library with arbitrary precision, values such a E and PI should be provided with a better precision than the double values in java.lang.Math can offer.
Edit
(25.5.11) has as many zeros in [0,10] as (25.5.12). The computation at 0 is tricky, but it's not a singularity. It does avoid issue (2).
For an answer regarding using arbitrary precision arithmetic with the integral method described in the OP - see my other answer
However, I got intrigued by this and thought that a series sum method should be more numerically stable. I found the Dirichlet series representation on Wikipedia and implemented it (fully runnable code below).
This gave me an interesting insight. If I set the convergence EPSILON to 1e-30 I get exactly the same digits and exponent (i.e. 1e-70 in the imaginary part) as Wolfram for zeta(100) and zeta(230+ 30i) and the algorithm terminates after only 1 or 2 terms adding to the sum. This suggests two things to me:
Wolfram alpha uses this sum method or something similar to calculate the values it returns.
The "correct"-ness of these values are hard to assess. For instance - zeta(100) has an exact value in terms of PI, so can be judged. I don't know whether this estimate of zeta(230+30i) is better or worse than the one found by the integral method
This method is really quite slow to converge to zeta(2+3i) and may need EPSILON taking lower to be usable.
I also found an academic paper that is a compendium of numeric methods to calculate zeta. This indicates to me that the underlying problem here is certainly "non-trivial"!!
Anyway - I leave the series sum implementation here as an alternative for anyone who may run across it in future.
import java.io.PrintWriter;
import org.apfloat.ApcomplexMath;
import org.apfloat.Apcomplex;
import org.apfloat.Apfloat;
import org.apfloat.ApfloatMath;
import org.apfloat.samples.Pi;
public class ZetaSeries {
//Number of sig figs accuracy. Note that infinite should be reserved
private static long PRECISION = 100l;
// Convergence criterion for integration
static Apfloat EPSILON = new Apfloat("1e-30",PRECISION);
static Apfloat one = new Apfloat("1",PRECISION);
static Apfloat two = new Apfloat("2",PRECISION);
static Apfloat minus_one = one.negate();
static Apfloat three = new Apfloat("3",PRECISION);
private Apcomplex s = null;
private Apcomplex s_plus_two = null;
public ZetaSeries(Apcomplex s) {
this.s = s;
this.s_plus_two = two.add(s);
}
public static void main(String[] args) {
Apfloat re = new Apfloat("230", PRECISION);
Apfloat im = new Apfloat("30", PRECISION);
Apcomplex s = new Apcomplex(re,im);
ZetaSeries z = new ZetaSeries(s);
System.out.println(z.findZeta());
}
private Apcomplex findZeta() {
Apcomplex series_sum = Apcomplex.ZERO;
Apcomplex multiplier = (one.divide(this.s.add(minus_one)));
int stop_condition = 1;
long n = 1;
while (stop_condition > 0)
{
Apcomplex term_to_add = sum_term(n);
stop_condition = ApcomplexMath.abs(term_to_add).compareTo(EPSILON);
series_sum = series_sum.add(term_to_add);
//if(n%50 == 0)
{
System.out.println("At iteration " + n + " : " + multiplier.multiply(series_sum));
}
n+=1;
}
return multiplier.multiply(series_sum);
}
private Apcomplex sum_term(long n_long) {
Apfloat n = new Apfloat(n_long, PRECISION);
Apfloat n_plus_one = n.add(one);
Apfloat two_n = two.multiply(n);
Apfloat t1 = (n.multiply(n_plus_one)).divide(two);
Apcomplex t2 = (two_n.add(three).add(this.s)).divide(ApcomplexMath.pow(n_plus_one,s_plus_two));
Apcomplex t3 = (two_n.add(minus_one).add(this.s.negate())).divide(ApcomplexMath.pow(n,this.s_plus_two));
return t1.multiply(t2.add(t3.negate()));
}
}
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:
Dave Ray
cletus
duffymo
James
Milhous
Oscar Reyes
Jason S
Francisco Canedo
Outlaw Programmer
Beska
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...
import java.math.*;
/**
* Arbitrary-precision fractions, utilizing BigIntegers for numerator and
* denominator. Fraction is always kept in lowest terms. Fraction is
* immutable, and guaranteed not to have a null numerator or denominator.
* Denominator will always be positive (so sign is carried by numerator,
* and a zero-denominator is impossible).
*/
public final class BigFraction extends Number implements Comparable<BigFraction>
{
private static final long serialVersionUID = 1L; //because Number is Serializable
private final BigInteger numerator;
private final BigInteger denominator;
public final static BigFraction ZERO = new BigFraction(BigInteger.ZERO, BigInteger.ONE, true);
public final static BigFraction ONE = new BigFraction(BigInteger.ONE, BigInteger.ONE, true);
/**
* Constructs a BigFraction with given numerator and denominator. Fraction
* will be reduced to lowest terms. If fraction is negative, negative sign will
* be carried on numerator, regardless of how the values were passed in.
*/
public BigFraction(BigInteger numerator, BigInteger denominator)
{
if(numerator == null)
throw new IllegalArgumentException("Numerator is null");
if(denominator == null)
throw new IllegalArgumentException("Denominator is null");
if(denominator.equals(BigInteger.ZERO))
throw new ArithmeticException("Divide by zero.");
//only numerator should be negative.
if(denominator.signum() < 0)
{
numerator = numerator.negate();
denominator = denominator.negate();
}
//create a reduced fraction
BigInteger gcd = numerator.gcd(denominator);
this.numerator = numerator.divide(gcd);
this.denominator = denominator.divide(gcd);
}
/**
* Constructs a BigFraction from a whole number.
*/
public BigFraction(BigInteger numerator)
{
this(numerator, BigInteger.ONE, true);
}
public BigFraction(long numerator, long denominator)
{
this(BigInteger.valueOf(numerator), BigInteger.valueOf(denominator));
}
public BigFraction(long numerator)
{
this(BigInteger.valueOf(numerator), BigInteger.ONE, true);
}
/**
* Constructs a BigFraction from a floating-point number.
*
* Warning: round-off error in IEEE floating point numbers can result
* in answers that are unexpected. For example,
* System.out.println(new BigFraction(1.1))
* will print:
* 2476979795053773/2251799813685248
*
* This is because 1.1 cannot be expressed exactly in binary form. The
* given fraction is exactly equal to the internal representation of
* the double-precision floating-point number. (Which, for 1.1, is:
* (-1)^0 * 2^0 * (1 + 0x199999999999aL / 0x10000000000000L).)
*
* NOTE: In many cases, BigFraction(Double.toString(d)) may give a result
* closer to what the user expects.
*/
public BigFraction(double d)
{
if(Double.isInfinite(d))
throw new IllegalArgumentException("double val is infinite");
if(Double.isNaN(d))
throw new IllegalArgumentException("double val is NaN");
//special case - math below won't work right for 0.0 or -0.0
if(d == 0)
{
numerator = BigInteger.ZERO;
denominator = BigInteger.ONE;
return;
}
final long bits = Double.doubleToLongBits(d);
final int sign = (int)(bits >> 63) & 0x1;
final int exponent = ((int)(bits >> 52) & 0x7ff) - 0x3ff;
final long mantissa = bits & 0xfffffffffffffL;
//number is (-1)^sign * 2^(exponent) * 1.mantissa
BigInteger tmpNumerator = BigInteger.valueOf(sign==0 ? 1 : -1);
BigInteger tmpDenominator = BigInteger.ONE;
//use shortcut: 2^x == 1 << x. if x is negative, shift the denominator
if(exponent >= 0)
tmpNumerator = tmpNumerator.multiply(BigInteger.ONE.shiftLeft(exponent));
else
tmpDenominator = tmpDenominator.multiply(BigInteger.ONE.shiftLeft(-exponent));
//1.mantissa == 1 + mantissa/2^52 == (2^52 + mantissa)/2^52
tmpDenominator = tmpDenominator.multiply(BigInteger.valueOf(0x10000000000000L));
tmpNumerator = tmpNumerator.multiply(BigInteger.valueOf(0x10000000000000L + mantissa));
BigInteger gcd = tmpNumerator.gcd(tmpDenominator);
numerator = tmpNumerator.divide(gcd);
denominator = tmpDenominator.divide(gcd);
}
/**
* Constructs a BigFraction from two floating-point numbers.
*
* Warning: round-off error in IEEE floating point numbers can result
* in answers that are unexpected. See BigFraction(double) for more
* information.
*
* NOTE: In many cases, BigFraction(Double.toString(numerator) + "/" + Double.toString(denominator))
* may give a result closer to what the user expects.
*/
public BigFraction(double numerator, double denominator)
{
if(denominator == 0)
throw new ArithmeticException("Divide by zero.");
BigFraction tmp = new BigFraction(numerator).divide(new BigFraction(denominator));
this.numerator = tmp.numerator;
this.denominator = tmp.denominator;
}
/**
* Constructs a new BigFraction from the given BigDecimal object.
*/
public BigFraction(BigDecimal d)
{
this(d.scale() < 0 ? d.unscaledValue().multiply(BigInteger.TEN.pow(-d.scale())) : d.unscaledValue(),
d.scale() < 0 ? BigInteger.ONE : BigInteger.TEN.pow(d.scale()));
}
public BigFraction(BigDecimal numerator, BigDecimal denominator)
{
if(denominator.equals(BigDecimal.ZERO))
throw new ArithmeticException("Divide by zero.");
BigFraction tmp = new BigFraction(numerator).divide(new BigFraction(denominator));
this.numerator = tmp.numerator;
this.denominator = tmp.denominator;
}
/**
* Constructs a BigFraction from a String. Expected format is numerator/denominator,
* but /denominator part is optional. Either numerator or denominator may be a floating-
* point decimal number, which in the same format as a parameter to the
* <code>BigDecimal(String)</code> constructor.
*
* #throws NumberFormatException if the string cannot be properly parsed.
*/
public BigFraction(String s)
{
int slashPos = s.indexOf('/');
if(slashPos < 0)
{
BigFraction res = new BigFraction(new BigDecimal(s));
this.numerator = res.numerator;
this.denominator = res.denominator;
}
else
{
BigDecimal num = new BigDecimal(s.substring(0, slashPos));
BigDecimal den = new BigDecimal(s.substring(slashPos+1, s.length()));
BigFraction res = new BigFraction(num, den);
this.numerator = res.numerator;
this.denominator = res.denominator;
}
}
/**
* Returns this + f.
*/
public BigFraction add(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
//n1/d1 + n2/d2 = (n1*d2 + d1*n2)/(d1*d2)
return new BigFraction(numerator.multiply(f.denominator).add(denominator.multiply(f.numerator)),
denominator.multiply(f.denominator));
}
/**
* Returns this + b.
*/
public BigFraction add(BigInteger b)
{
if(b == null)
throw new IllegalArgumentException("Null argument");
//n1/d1 + n2 = (n1 + d1*n2)/d1
return new BigFraction(numerator.add(denominator.multiply(b)),
denominator, true);
}
/**
* Returns this + n.
*/
public BigFraction add(long n)
{
return add(BigInteger.valueOf(n));
}
/**
* Returns this - f.
*/
public BigFraction subtract(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
return new BigFraction(numerator.multiply(f.denominator).subtract(denominator.multiply(f.numerator)),
denominator.multiply(f.denominator));
}
/**
* Returns this - b.
*/
public BigFraction subtract(BigInteger b)
{
if(b == null)
throw new IllegalArgumentException("Null argument");
return new BigFraction(numerator.subtract(denominator.multiply(b)),
denominator, true);
}
/**
* Returns this - n.
*/
public BigFraction subtract(long n)
{
return subtract(BigInteger.valueOf(n));
}
/**
* Returns this * f.
*/
public BigFraction multiply(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
return new BigFraction(numerator.multiply(f.numerator), denominator.multiply(f.denominator));
}
/**
* Returns this * b.
*/
public BigFraction multiply(BigInteger b)
{
if(b == null)
throw new IllegalArgumentException("Null argument");
return new BigFraction(numerator.multiply(b), denominator);
}
/**
* Returns this * n.
*/
public BigFraction multiply(long n)
{
return multiply(BigInteger.valueOf(n));
}
/**
* Returns this / f.
*/
public BigFraction divide(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
if(f.numerator.equals(BigInteger.ZERO))
throw new ArithmeticException("Divide by zero");
return new BigFraction(numerator.multiply(f.denominator), denominator.multiply(f.numerator));
}
/**
* Returns this / b.
*/
public BigFraction divide(BigInteger b)
{
if(b == null)
throw new IllegalArgumentException("Null argument");
if(b.equals(BigInteger.ZERO))
throw new ArithmeticException("Divide by zero");
return new BigFraction(numerator, denominator.multiply(b));
}
/**
* Returns this / n.
*/
public BigFraction divide(long n)
{
return divide(BigInteger.valueOf(n));
}
/**
* Returns this^exponent.
*/
public BigFraction pow(int exponent)
{
if(exponent == 0)
return BigFraction.ONE;
else if (exponent == 1)
return this;
else if (exponent < 0)
return new BigFraction(denominator.pow(-exponent), numerator.pow(-exponent), true);
else
return new BigFraction(numerator.pow(exponent), denominator.pow(exponent), true);
}
/**
* Returns 1/this.
*/
public BigFraction reciprocal()
{
if(this.numerator.equals(BigInteger.ZERO))
throw new ArithmeticException("Divide by zero");
return new BigFraction(denominator, numerator, true);
}
/**
* Returns the complement of this fraction, which is equal to 1 - this.
* Useful for probabilities/statistics.
*/
public BigFraction complement()
{
return new BigFraction(denominator.subtract(numerator), denominator, true);
}
/**
* Returns -this.
*/
public BigFraction negate()
{
return new BigFraction(numerator.negate(), denominator, true);
}
/**
* Returns -1, 0, or 1, representing the sign of this fraction.
*/
public int signum()
{
return numerator.signum();
}
/**
* Returns the absolute value of this.
*/
public BigFraction abs()
{
return (signum() < 0 ? negate() : this);
}
/**
* Returns a string representation of this, in the form
* numerator/denominator.
*/
public String toString()
{
return numerator.toString() + "/" + denominator.toString();
}
/**
* Returns if this object is equal to another object.
*/
public boolean equals(Object o)
{
if(!(o instanceof BigFraction))
return false;
BigFraction f = (BigFraction)o;
return numerator.equals(f.numerator) && denominator.equals(f.denominator);
}
/**
* Returns a hash code for this object.
*/
public int hashCode()
{
//using the method generated by Eclipse, but streamlined a bit..
return (31 + numerator.hashCode())*31 + denominator.hashCode();
}
/**
* Returns a negative, zero, or positive number, indicating if this object
* is less than, equal to, or greater than f, respectively.
*/
public int compareTo(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
//easy case: this and f have different signs
if(signum() != f.signum())
return signum() - f.signum();
//next easy case: this and f have the same denominator
if(denominator.equals(f.denominator))
return numerator.compareTo(f.numerator);
//not an easy case, so first make the denominators equal then compare the numerators
return numerator.multiply(f.denominator).compareTo(denominator.multiply(f.numerator));
}
/**
* Returns the smaller of this and f.
*/
public BigFraction min(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
return (this.compareTo(f) <= 0 ? this : f);
}
/**
* Returns the maximum of this and f.
*/
public BigFraction max(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
return (this.compareTo(f) >= 0 ? this : f);
}
/**
* Returns a positive BigFraction, greater than or equal to zero, and less than one.
*/
public static BigFraction random()
{
return new BigFraction(Math.random());
}
public final BigInteger getNumerator() { return numerator; }
public final BigInteger getDenominator() { return denominator; }
//implementation of Number class. may cause overflow.
public byte byteValue() { return (byte) Math.max(Byte.MIN_VALUE, Math.min(Byte.MAX_VALUE, longValue())); }
public short shortValue() { return (short)Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, longValue())); }
public int intValue() { return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, longValue())); }
public long longValue() { return Math.round(doubleValue()); }
public float floatValue() { return (float)doubleValue(); }
public double doubleValue() { return toBigDecimal(18).doubleValue(); }
/**
* Returns a BigDecimal representation of this fraction. If possible, the
* returned value will be exactly equal to the fraction. If not, the BigDecimal
* will have a scale large enough to hold the same number of significant figures
* as both numerator and denominator, or the equivalent of a double-precision
* number, whichever is more.
*/
public BigDecimal toBigDecimal()
{
//Implementation note: A fraction can be represented exactly in base-10 iff its
//denominator is of the form 2^a * 5^b, where a and b are nonnegative integers.
//(In other words, if there are no prime factors of the denominator except for
//2 and 5, or if the denominator is 1). So to determine if this denominator is
//of this form, continually divide by 2 to get the number of 2's, and then
//continually divide by 5 to get the number of 5's. Afterward, if the denominator
//is 1 then there are no other prime factors.
//Note: number of 2's is given by the number of trailing 0 bits in the number
int twos = denominator.getLowestSetBit();
BigInteger tmpDen = denominator.shiftRight(twos); // x / 2^n === x >> n
final BigInteger FIVE = BigInteger.valueOf(5);
int fives = 0;
BigInteger[] divMod = null;
//while(tmpDen % 5 == 0) { fives++; tmpDen /= 5; }
while(BigInteger.ZERO.equals((divMod = tmpDen.divideAndRemainder(FIVE))[1]))
{
fives++;
tmpDen = divMod[0];
}
if(BigInteger.ONE.equals(tmpDen))
{
//This fraction will terminate in base 10, so it can be represented exactly as
//a BigDecimal. We would now like to make the fraction of the form
//unscaled / 10^scale. We know that 2^x * 5^x = 10^x, and our denominator is
//in the form 2^twos * 5^fives. So use max(twos, fives) as the scale, and
//multiply the numerator and deminator by the appropriate number of 2's or 5's
//such that the denominator is of the form 2^scale * 5^scale. (Of course, we
//only have to actually multiply the numerator, since all we need for the
//BigDecimal constructor is the scale.
BigInteger unscaled = numerator;
int scale = Math.max(twos, fives);
if(twos < fives)
unscaled = unscaled.shiftLeft(fives - twos); //x * 2^n === x << n
else if (fives < twos)
unscaled = unscaled.multiply(FIVE.pow(twos - fives));
return new BigDecimal(unscaled, scale);
}
//else: this number will repeat infinitely in base-10. So try to figure out
//a good number of significant digits. Start with the number of digits required
//to represent the numerator and denominator in base-10, which is given by
//bitLength / log[2](10). (bitLenth is the number of digits in base-2).
final double LG10 = 3.321928094887362; //Precomputed ln(10)/ln(2), a.k.a. log[2](10)
int precision = Math.max(numerator.bitLength(), denominator.bitLength());
precision = (int)Math.ceil(precision / LG10);
//If the precision is less than 18 digits, use 18 digits so that the number
//will be at least as accurate as a cast to a double. For example, with
//the fraction 1/3, precision will be 1, giving a result of 0.3. This is
//quite a bit different from what a user would expect.
if(precision < 18)
precision = 18;
return toBigDecimal(precision);
}
/**
* Returns a BigDecimal representation of this fraction, with a given precision.
* #param precision the number of significant figures to be used in the result.
*/
public BigDecimal toBigDecimal(int precision)
{
return new BigDecimal(numerator).divide(new BigDecimal(denominator), new MathContext(precision, RoundingMode.HALF_EVEN));
}
//--------------------------------------------------------------------------
// PRIVATE FUNCTIONS
//--------------------------------------------------------------------------
/**
* Private constructor, used when you can be certain that the fraction is already in
* lowest terms. No check is done to reduce numerator/denominator. A check is still
* done to maintain a positive denominator.
*
* #param throwaway unused variable, only here to signal to the compiler that this
* constructor should be used.
*/
private BigFraction(BigInteger numerator, BigInteger denominator, boolean throwaway)
{
if(denominator.signum() < 0)
{
this.numerator = numerator.negate();
this.denominator = denominator.negate();
}
else
{
this.numerator = numerator;
this.denominator = denominator;
}
}
}
Make it immutable;
Make it canonical, meaning 6/4 becomes 3/2 (greatest common divisor algorithm is useful for this);
Call it Rational, since what you're representing is a rational number;
You could use BigInteger to store arbitrarilyy-precise values. If not that then long, which has an easier implementation;
Make the denominator always positive. Sign should be carried by the numerator;
Extend Number;
Implement Comparable<T>;
Implement equals() and hashCode();
Add factory method for a number represented by a String;
Add some convenience factory methods;
Add a toString(); and
Make it Serializable.
In fact, try this on for size. It runs but may have some issues:
public class BigRational extends Number implements Comparable<BigRational>, Serializable {
public final static BigRational ZERO = new BigRational(BigInteger.ZERO, BigInteger.ONE);
private final static long serialVersionUID = 1099377265582986378L;
private final BigInteger numerator, denominator;
private BigRational(BigInteger numerator, BigInteger denominator) {
this.numerator = numerator;
this.denominator = denominator;
}
private static BigRational canonical(BigInteger numerator, BigInteger denominator, boolean checkGcd) {
if (denominator.signum() == 0) {
throw new IllegalArgumentException("denominator is zero");
}
if (numerator.signum() == 0) {
return ZERO;
}
if (denominator.signum() < 0) {
numerator = numerator.negate();
denominator = denominator.negate();
}
if (checkGcd) {
BigInteger gcd = numerator.gcd(denominator);
if (!gcd.equals(BigInteger.ONE)) {
numerator = numerator.divide(gcd);
denominator = denominator.divide(gcd);
}
}
return new BigRational(numerator, denominator);
}
public static BigRational getInstance(BigInteger numerator, BigInteger denominator) {
return canonical(numerator, denominator, true);
}
public static BigRational getInstance(long numerator, long denominator) {
return canonical(new BigInteger("" + numerator), new BigInteger("" + denominator), true);
}
public static BigRational getInstance(String numerator, String denominator) {
return canonical(new BigInteger(numerator), new BigInteger(denominator), true);
}
public static BigRational valueOf(String s) {
Pattern p = Pattern.compile("(-?\\d+)(?:.(\\d+)?)?0*(?:e(-?\\d+))?");
Matcher m = p.matcher(s);
if (!m.matches()) {
throw new IllegalArgumentException("Unknown format '" + s + "'");
}
// this translates 23.123e5 to 25,123 / 1000 * 10^5 = 2,512,300 / 1 (GCD)
String whole = m.group(1);
String decimal = m.group(2);
String exponent = m.group(3);
String n = whole;
// 23.123 => 23123
if (decimal != null) {
n += decimal;
}
BigInteger numerator = new BigInteger(n);
// exponent is an int because BigInteger.pow() takes an int argument
// it gets more difficult if exponent needs to be outside {-2 billion,2 billion}
int exp = exponent == null ? 0 : Integer.valueOf(exponent);
int decimalPlaces = decimal == null ? 0 : decimal.length();
exp -= decimalPlaces;
BigInteger denominator;
if (exp < 0) {
denominator = BigInteger.TEN.pow(-exp);
} else {
numerator = numerator.multiply(BigInteger.TEN.pow(exp));
denominator = BigInteger.ONE;
}
// done
return canonical(numerator, denominator, true);
}
// Comparable
public int compareTo(BigRational o) {
// note: this is a bit of cheat, relying on BigInteger.compareTo() returning
// -1, 0 or 1. For the more general contract of compareTo(), you'd need to do
// more checking
if (numerator.signum() != o.numerator.signum()) {
return numerator.signum() - o.numerator.signum();
} else {
// oddly BigInteger has gcd() but no lcm()
BigInteger i1 = numerator.multiply(o.denominator);
BigInteger i2 = o.numerator.multiply(denominator);
return i1.compareTo(i2); // expensive!
}
}
public BigRational add(BigRational o) {
if (o.numerator.signum() == 0) {
return this;
} else if (numerator.signum() == 0) {
return o;
} else if (denominator.equals(o.denominator)) {
return new BigRational(numerator.add(o.numerator), denominator);
} else {
return canonical(numerator.multiply(o.denominator).add(o.numerator.multiply(denominator)), denominator.multiply(o.denominator), true);
}
}
public BigRational multiply(BigRational o) {
if (numerator.signum() == 0 || o.numerator.signum( )== 0) {
return ZERO;
} else if (numerator.equals(o.denominator)) {
return canonical(o.numerator, denominator, true);
} else if (o.numerator.equals(denominator)) {
return canonical(numerator, o.denominator, true);
} else if (numerator.negate().equals(o.denominator)) {
return canonical(o.numerator.negate(), denominator, true);
} else if (o.numerator.negate().equals(denominator)) {
return canonical(numerator.negate(), o.denominator, true);
} else {
return canonical(numerator.multiply(o.numerator), denominator.multiply(o.denominator), true);
}
}
public BigInteger getNumerator() { return numerator; }
public BigInteger getDenominator() { return denominator; }
public boolean isInteger() { return numerator.signum() == 0 || denominator.equals(BigInteger.ONE); }
public BigRational negate() { return new BigRational(numerator.negate(), denominator); }
public BigRational invert() { return canonical(denominator, numerator, false); }
public BigRational abs() { return numerator.signum() < 0 ? negate() : this; }
public BigRational pow(int exp) { return canonical(numerator.pow(exp), denominator.pow(exp), true); }
public BigRational subtract(BigRational o) { return add(o.negate()); }
public BigRational divide(BigRational o) { return multiply(o.invert()); }
public BigRational min(BigRational o) { return compareTo(o) <= 0 ? this : o; }
public BigRational max(BigRational o) { return compareTo(o) >= 0 ? this : o; }
public BigDecimal toBigDecimal(int scale, RoundingMode roundingMode) {
return isInteger() ? new BigDecimal(numerator) : new BigDecimal(numerator).divide(new BigDecimal(denominator), scale, roundingMode);
}
// Number
public int intValue() { return isInteger() ? numerator.intValue() : numerator.divide(denominator).intValue(); }
public long longValue() { return isInteger() ? numerator.longValue() : numerator.divide(denominator).longValue(); }
public float floatValue() { return (float)doubleValue(); }
public double doubleValue() { return isInteger() ? numerator.doubleValue() : numerator.doubleValue() / denominator.doubleValue(); }
#Override
public String toString() { return isInteger() ? String.format("%,d", numerator) : String.format("%,d / %,d", numerator, denominator); }
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BigRational that = (BigRational) o;
if (denominator != null ? !denominator.equals(that.denominator) : that.denominator != null) return false;
if (numerator != null ? !numerator.equals(that.numerator) : that.numerator != null) return false;
return true;
}
#Override
public int hashCode() {
int result = numerator != null ? numerator.hashCode() : 0;
result = 31 * result + (denominator != null ? denominator.hashCode() : 0);
return result;
}
public static void main(String args[]) {
BigRational r1 = BigRational.valueOf("3.14e4");
BigRational r2 = BigRational.getInstance(111, 7);
dump("r1", r1);
dump("r2", r2);
dump("r1 + r2", r1.add(r2));
dump("r1 - r2", r1.subtract(r2));
dump("r1 * r2", r1.multiply(r2));
dump("r1 / r2", r1.divide(r2));
dump("r2 ^ 2", r2.pow(2));
}
public static void dump(String name, BigRational r) {
System.out.printf("%s = %s%n", name, r);
System.out.printf("%s.negate() = %s%n", name, r.negate());
System.out.printf("%s.invert() = %s%n", name, r.invert());
System.out.printf("%s.intValue() = %,d%n", name, r.intValue());
System.out.printf("%s.longValue() = %,d%n", name, r.longValue());
System.out.printf("%s.floatValue() = %,f%n", name, r.floatValue());
System.out.printf("%s.doubleValue() = %,f%n", name, r.doubleValue());
System.out.println();
}
}
Output is:
r1 = 31,400
r1.negate() = -31,400
r1.invert() = 1 / 31,400
r1.intValue() = 31,400
r1.longValue() = 31,400
r1.floatValue() = 31,400.000000
r1.doubleValue() = 31,400.000000
r2 = 111 / 7
r2.negate() = -111 / 7
r2.invert() = 7 / 111
r2.intValue() = 15
r2.longValue() = 15
r2.floatValue() = 15.857142
r2.doubleValue() = 15.857143
r1 + r2 = 219,911 / 7
r1 + r2.negate() = -219,911 / 7
r1 + r2.invert() = 7 / 219,911
r1 + r2.intValue() = 31,415
r1 + r2.longValue() = 31,415
r1 + r2.floatValue() = 31,415.857422
r1 + r2.doubleValue() = 31,415.857143
r1 - r2 = 219,689 / 7
r1 - r2.negate() = -219,689 / 7
r1 - r2.invert() = 7 / 219,689
r1 - r2.intValue() = 31,384
r1 - r2.longValue() = 31,384
r1 - r2.floatValue() = 31,384.142578
r1 - r2.doubleValue() = 31,384.142857
r1 * r2 = 3,485,400 / 7
r1 * r2.negate() = -3,485,400 / 7
r1 * r2.invert() = 7 / 3,485,400
r1 * r2.intValue() = 497,914
r1 * r2.longValue() = 497,914
r1 * r2.floatValue() = 497,914.281250
r1 * r2.doubleValue() = 497,914.285714
r1 / r2 = 219,800 / 111
r1 / r2.negate() = -219,800 / 111
r1 / r2.invert() = 111 / 219,800
r1 / r2.intValue() = 1,980
r1 / r2.longValue() = 1,980
r1 / r2.floatValue() = 1,980.180176
r1 / r2.doubleValue() = 1,980.180180
r2 ^ 2 = 12,321 / 49
r2 ^ 2.negate() = -12,321 / 49
r2 ^ 2.invert() = 49 / 12,321
r2 ^ 2.intValue() = 251
r2 ^ 2.longValue() = 251
r2 ^ 2.floatValue() = 251.448975
r2 ^ 2.doubleValue() = 251.448980
I'm trying to work with proper fractions in Java.
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.
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.
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...
It's kinda pointless without arithmetic methods like add() and multiply(), etc.
You should definitely override equals() and hashCode().
You should either add a method to normalize the fraction, or do it automatically. Think about whether you want 1/2 and 2/4 to be considered the same or not - this has implications for the equals(), hashCode() and compareTo() methods.
I will need to order them from smallest to largest,
so eventually I will need to represent them as a double also
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:
public int compareTo(Fraction frac)
{
// we are comparing this=a/b with frac=c/d
// by multiplying both sides by bd.
// If bd is positive, then a/b < c/d <=> ad < bc.
// If bd is negative, then a/b < c/d <=> ad > bc.
// If bd is 0, then you've got other problems (either b=0 or d=0)
int d = frac.getDenominator();
long ad = (long)this.numerator * d;
long bc = (long)this.denominator * frac.getNumerator();
long diff = ((long)d*this.denominator > 0) ? (ad-bc) : (bc-ad);
return (diff > 0 ? 1 : (diff < 0 ? -1 : 0));
}
The use of long here is to ensure there's not an overflow if you multiply two large ints. 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.)
public static void main(String[] args)
{
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
int c = Integer.parseInt(args[2]);
int d = Integer.parseInt(args[3]);
Fraction f1 = new Fraction(a,b);
Fraction f2 = new Fraction(c,d);
int rel = f1.compareTo(f2);
String relstr = "<=>";
System.out.println(a+"/"+b+" "+relstr.charAt(rel+1)+" "+c+"/"+d);
}
(p.s. you might consider restructuring to implement Comparable or Comparator for your class.)
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.)
There are several ways to improve this or any value type:
Make your class immutable, including making numerator and denominator final
Automatically convert fractions to a canonical form, e.g. 2/4 -> 1/2
Implement toString()
Implement "public static Fraction valueOf(String s)" to convert from strings to fractions. Implement similar factory methods for converting from int, double, etc.
Implement addition, multiplication, etc
Add constructor from whole numbers
Override equals/hashCode
Consider making Fraction an interface with an implementation that switches to BigInteger as necessary
Consider sub-classing Number
Consider including named constants for common values like 0 and 1
Consider making it serializable
Test for division by zero
Document your API
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:
public int compareTo(Fraction frac) {
int t = this.numerator * frac.getDenominator();
int f = frac.getNumerator() * this.denominator;
if(t>f) return 1;
if(f>t) return -1;
return 0;
}
how I would improve that code:
a constructor based on String Fraction(String s) //expect "number/number"
a copy constructor Fraction(Fraction copy)
override the clone method
implements the equals, toString and hashcode methods
implements the interface java.io.Serializable, Comparable
a method "double getDoubleValue()"
a method add/divide/etc...
I would make that class as immutable (no setters)
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.
If you're feeling adventurous, take a look at JScience. It has a Rational class that represents fractions.
Specifically: Is there a better way to handle being passed a zero denominator? Setting the denominator to 1 is feels mighty arbitrary. How can I do this right?
I would say throw a ArithmeticException for divide by zero, since that's really what's happening:
public Fraction(int numerator, int denominator) {
if(denominator == 0)
throw new ArithmeticException("Divide by zero.");
this.numerator = numerator;
this.denominator = denominator;
}
Instead of "Divide by zero.", you might want to make the message say "Divide by zero: Denominator for Fraction is zero."
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 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.
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.
public int compareTo(Fraction frac) {
int result = 0
double t = this.doubleValue();
double f = frac.doubleValue();
if(t>f)
result = 1;
else if(f>t)
result -1;
return result;
}
Use Rational class from JScience library. It's the best thing for fractional arithmetic I seen in Java.
I cleaned up cletus' answer:
Added Javadoc for all methods.
Added checks for method preconditions.
Replaced custom parsing in valueOf(String) with the BigInteger(String) which is both more flexible and faster.
import com.google.common.base.Splitter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;
import org.bitbucket.cowwoc.preconditions.Preconditions;
/**
* A rational fraction, represented by {#code numerator / denominator}.
* <p>
* This implementation is based on <a
* href="https://stackoverflow.com/a/474577/14731">https://stackoverflow.com/a/474577/14731</a>
* <p>
* #author Gili Tzabari
*/
public final class BigRational extends Number implements Comparable<BigRational>
{
private static final long serialVersionUID = 0L;
public static final BigRational ZERO = new BigRational(BigInteger.ZERO, BigInteger.ONE);
public static final BigRational ONE = new BigRational(BigInteger.ONE, BigInteger.ONE);
/**
* Ensures the fraction the denominator is positive and optionally divides the numerator and
* denominator by the greatest common factor.
* <p>
* #param numerator a numerator
* #param denominator a denominator
* #param checkGcd true if the numerator and denominator should be divided by the greatest
* common factor
* #return the canonical representation of the rational fraction
*/
private static BigRational canonical(BigInteger numerator, BigInteger denominator,
boolean checkGcd)
{
assert (numerator != null);
assert (denominator != null);
if (denominator.signum() == 0)
throw new IllegalArgumentException("denominator is zero");
if (numerator.signum() == 0)
return ZERO;
BigInteger newNumerator = numerator;
BigInteger newDenominator = denominator;
if (newDenominator.signum() < 0)
{
newNumerator = newNumerator.negate();
newDenominator = newDenominator.negate();
}
if (checkGcd)
{
BigInteger gcd = newNumerator.gcd(newDenominator);
if (!gcd.equals(BigInteger.ONE))
{
newNumerator = newNumerator.divide(gcd);
newDenominator = newDenominator.divide(gcd);
}
}
return new BigRational(newNumerator, newDenominator);
}
/**
* #param numerator a numerator
* #param denominator a denominator
* #return a BigRational having value {#code numerator / denominator}
* #throws NullPointerException if numerator or denominator are null
*/
public static BigRational valueOf(BigInteger numerator, BigInteger denominator)
{
Preconditions.requireThat(numerator, "numerator").isNotNull();
Preconditions.requireThat(denominator, "denominator").isNotNull();
return canonical(numerator, denominator, true);
}
/**
* #param numerator a numerator
* #param denominator a denominator
* #return a BigRational having value {#code numerator / denominator}
*/
public static BigRational valueOf(long numerator, long denominator)
{
BigInteger bigNumerator = BigInteger.valueOf(numerator);
BigInteger bigDenominator = BigInteger.valueOf(denominator);
return canonical(bigNumerator, bigDenominator, true);
}
/**
* #param value the parameter value
* #param name the parameter name
* #return the BigInteger representation of the parameter
* #throws NumberFormatException if value is not a valid representation of BigInteger
*/
private static BigInteger requireBigInteger(String value, String name)
throws NumberFormatException
{
try
{
return new BigInteger(value);
}
catch (NumberFormatException e)
{
throw (NumberFormatException) new NumberFormatException("Invalid " + name + ": " + value).
initCause(e);
}
}
/**
* #param numerator a numerator
* #param denominator a denominator
* #return a BigRational having value {#code numerator / denominator}
* #throws NullPointerException if numerator or denominator are null
* #throws IllegalArgumentException if numerator or denominator are empty
* #throws NumberFormatException if numerator or denominator are not a valid representation of
* BigDecimal
*/
public static BigRational valueOf(String numerator, String denominator)
throws NullPointerException, IllegalArgumentException, NumberFormatException
{
Preconditions.requireThat(numerator, "numerator").isNotNull().isNotEmpty();
Preconditions.requireThat(denominator, "denominator").isNotNull().isNotEmpty();
BigInteger bigNumerator = requireBigInteger(numerator, "numerator");
BigInteger bigDenominator = requireBigInteger(denominator, "denominator");
return canonical(bigNumerator, bigDenominator, true);
}
/**
* #param value a string representation of a rational fraction (e.g. "12.34e5" or "3/4")
* #return a BigRational representation of the String
* #throws NullPointerException if value is null
* #throws IllegalArgumentException if value is empty
* #throws NumberFormatException if numerator or denominator are not a valid representation of
* BigDecimal
*/
public static BigRational valueOf(String value)
throws NullPointerException, IllegalArgumentException, NumberFormatException
{
Preconditions.requireThat(value, "value").isNotNull().isNotEmpty();
List<String> fractionParts = Splitter.on('/').splitToList(value);
if (fractionParts.size() == 1)
return valueOfRational(value);
if (fractionParts.size() == 2)
return BigRational.valueOf(fractionParts.get(0), fractionParts.get(1));
throw new IllegalArgumentException("Too many slashes: " + value);
}
/**
* #param value a string representation of a rational fraction (e.g. "12.34e5")
* #return a BigRational representation of the String
* #throws NullPointerException if value is null
* #throws IllegalArgumentException if value is empty
* #throws NumberFormatException if numerator or denominator are not a valid representation of
* BigDecimal
*/
private static BigRational valueOfRational(String value)
throws NullPointerException, IllegalArgumentException, NumberFormatException
{
Preconditions.requireThat(value, "value").isNotNull().isNotEmpty();
BigDecimal bigDecimal = new BigDecimal(value);
int scale = bigDecimal.scale();
BigInteger numerator = bigDecimal.unscaledValue();
BigInteger denominator;
if (scale > 0)
denominator = BigInteger.TEN.pow(scale);
else
{
numerator = numerator.multiply(BigInteger.TEN.pow(-scale));
denominator = BigInteger.ONE;
}
return canonical(numerator, denominator, true);
}
private final BigInteger numerator;
private final BigInteger denominator;
/**
* #param numerator the numerator
* #param denominator the denominator
* #throws NullPointerException if numerator or denominator are null
*/
private BigRational(BigInteger numerator, BigInteger denominator)
{
Preconditions.requireThat(numerator, "numerator").isNotNull();
Preconditions.requireThat(denominator, "denominator").isNotNull();
this.numerator = numerator;
this.denominator = denominator;
}
/**
* #return the numerator
*/
public BigInteger getNumerator()
{
return numerator;
}
/**
* #return the denominator
*/
public BigInteger getDenominator()
{
return denominator;
}
#Override
#SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public int compareTo(BigRational other)
{
Preconditions.requireThat(other, "other").isNotNull();
// canonical() ensures denominator is positive
if (numerator.signum() != other.numerator.signum())
return numerator.signum() - other.numerator.signum();
// Set the denominator to a common multiple before comparing the numerators
BigInteger first = numerator.multiply(other.denominator);
BigInteger second = other.numerator.multiply(denominator);
return first.compareTo(second);
}
/**
* #param other another rational fraction
* #return the result of adding this object to {#code other}
* #throws NullPointerException if other is null
*/
#SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public BigRational add(BigRational other)
{
Preconditions.requireThat(other, "other").isNotNull();
if (other.numerator.signum() == 0)
return this;
if (numerator.signum() == 0)
return other;
if (denominator.equals(other.denominator))
return new BigRational(numerator.add(other.numerator), denominator);
return canonical(numerator.multiply(other.denominator).
add(other.numerator.multiply(denominator)),
denominator.multiply(other.denominator), true);
}
/**
* #param other another rational fraction
* #return the result of subtracting {#code other} from this object
* #throws NullPointerException if other is null
*/
#SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public BigRational subtract(BigRational other)
{
return add(other.negate());
}
/**
* #param other another rational fraction
* #return the result of multiplying this object by {#code other}
* #throws NullPointerException if other is null
*/
#SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public BigRational multiply(BigRational other)
{
Preconditions.requireThat(other, "other").isNotNull();
if (numerator.signum() == 0 || other.numerator.signum() == 0)
return ZERO;
if (numerator.equals(other.denominator))
return canonical(other.numerator, denominator, true);
if (other.numerator.equals(denominator))
return canonical(numerator, other.denominator, true);
if (numerator.negate().equals(other.denominator))
return canonical(other.numerator.negate(), denominator, true);
if (other.numerator.negate().equals(denominator))
return canonical(numerator.negate(), other.denominator, true);
return canonical(numerator.multiply(other.numerator), denominator.multiply(other.denominator),
true);
}
/**
* #param other another rational fraction
* #return the result of dividing this object by {#code other}
* #throws NullPointerException if other is null
*/
public BigRational divide(BigRational other)
{
return multiply(other.invert());
}
/**
* #return true if the object is a whole number
*/
public boolean isInteger()
{
return numerator.signum() == 0 || denominator.equals(BigInteger.ONE);
}
/**
* Returns a BigRational whose value is (-this).
* <p>
* #return -this
*/
public BigRational negate()
{
return new BigRational(numerator.negate(), denominator);
}
/**
* #return a rational fraction with the numerator and denominator swapped
*/
public BigRational invert()
{
return canonical(denominator, numerator, false);
}
/**
* #return the absolute value of this {#code BigRational}
*/
public BigRational abs()
{
if (numerator.signum() < 0)
return negate();
return this;
}
/**
* #param exponent exponent to which both numerator and denominator is to be raised.
* #return a BigRational whose value is (this<sup>exponent</sup>).
*/
public BigRational pow(int exponent)
{
return canonical(numerator.pow(exponent), denominator.pow(exponent), true);
}
/**
* #param other another rational fraction
* #return the minimum of this object and the other fraction
*/
public BigRational min(BigRational other)
{
if (compareTo(other) <= 0)
return this;
return other;
}
/**
* #param other another rational fraction
* #return the maximum of this object and the other fraction
*/
public BigRational max(BigRational other)
{
if (compareTo(other) >= 0)
return this;
return other;
}
/**
* #param scale scale of the BigDecimal quotient to be returned
* #param roundingMode the rounding mode to apply
* #return a BigDecimal representation of this object
* #throws NullPointerException if roundingMode is null
*/
public BigDecimal toBigDecimal(int scale, RoundingMode roundingMode)
{
Preconditions.requireThat(roundingMode, "roundingMode").isNotNull();
if (isInteger())
return new BigDecimal(numerator);
return new BigDecimal(numerator).divide(new BigDecimal(denominator), scale, roundingMode);
}
#Override
public int intValue()
{
return (int) longValue();
}
#Override
public long longValue()
{
if (isInteger())
return numerator.longValue();
return numerator.divide(denominator).longValue();
}
#Override
public float floatValue()
{
return (float) doubleValue();
}
#Override
public double doubleValue()
{
if (isInteger())
return numerator.doubleValue();
return numerator.doubleValue() / denominator.doubleValue();
}
#Override
#SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof BigRational))
return false;
BigRational other = (BigRational) o;
return numerator.equals(other.denominator) && Objects.equals(denominator, other.denominator);
}
#Override
public int hashCode()
{
return Objects.hash(numerator, denominator);
}
/**
* Returns the String representation: {#code numerator / denominator}.
*/
#Override
public String toString()
{
if (isInteger())
return String.format("%,d", numerator);
return String.format("%,d / %,d", numerator, denominator);
}
}
Initial remark:
Never write this:
if ( condition ) statement;
This is much better
if ( condition ) { statement };
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:
public final class Fraction implements Comparable {
private final int numerator;
private final int denominator;
private final Double internal;
public static Fraction createFraction( int numerator, int denominator ) {
return new Fraction( numerator, denominator );
}
private Fraction(int numerator, int denominator) {
this.numerator = numerator;
this.denominator = denominator;
this.internal = ((double) numerator)/((double) denominator);
}
public int getNumerator() {
return this.numerator;
}
public int getDenominator() {
return this.denominator;
}
private double doubleValue() {
return internal;
}
public int compareTo( Object o ) {
if ( o instanceof Fraction ) {
return internal.compareTo( ((Fraction)o).internal );
}
return 1;
}
public boolean equals( Object o ) {
if ( o instanceof Fraction ) {
return this.internal.equals( ((Fraction)o).internal );
}
return false;
}
public int hashCode() {
return internal.hashCode();
}
public String toString() {
return String.format("%d/%d", numerator, denominator );
}
public static void main( String [] args ) {
System.out.println( Fraction.createFraction( 1 , 2 ) ) ;
System.out.println( Fraction.createFraction( 1 , 2 ).hashCode() ) ;
System.out.println( Fraction.createFraction( 1 , 2 ).compareTo( Fraction.createFraction(2,4) ) ) ;
System.out.println( Fraction.createFraction( 1 , 2 ).equals( Fraction.createFraction(4,8) ) ) ;
System.out.println( Fraction.createFraction( 3 , 9 ).equals( Fraction.createFraction(1,3) ) ) ;
}
}
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.
Even though you have the methods compareTo(), if you want to make use of utilities like Collections.sort(), then you should also implement Comparable.
public class Fraction extends Number implements Comparable<Fraction> {
...
}
Also, for pretty display I recommend overriding toString()
public String toString() {
return this.getNumerator() + "/" + this.getDenominator();
}
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
public Fraction simplify(){
int safe;
int h= Math.max(numerator, denominator);
int h2 = Math.min(denominator, numerator);
if (h == 0){
return new Fraction(1,1);
}
while (h>h2 && h2>0){
h = h - h2;
if (h>h2){
safe = h;
h = h2;
h2 = safe;
}
}
return new Fraction(numerator/h,denominator/h);
}
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:
public class Fraction {
private int num; // numerator
private int denom; // denominator
// default constructor
public Fraction() {}
// constructor
public Fraction( int a, int b ) {
num = a;
if ( b == 0 )
throw new ZeroDenomException();
else
denom = b;
}
// return string representation of ComplexNumber
#Override
public String toString() {
return "( " + num + " / " + denom + " )";
}
// the addition operation
public Fraction add(Fraction x){
return new Fraction(
x.num * denom + x.denom * num, x.denom * denom );
}
// the multiplication operation
public Fraction multiply(Fraction x) {
return new Fraction(x.num * num, x.denom * denom);
}
}
The main program:
static void main(String[] args){
Scanner input = new Scanner(System.in);
System.out.println("Enter numerator and denominator of first fraction");
int num1 =input.nextInt();
int denom1 =input.nextInt();
Fraction x = new Fraction(num1, denom1);
System.out.println("Enter numerator and denominator of second fraction");
int num2 =input.nextInt();
int denom2 =input.nextInt();
Fraction y = new Fraction(num2, denom2);
Fraction result = new Fraction();
System.out.println("Enter required operation: A (Add), M (Multiply)");
char op = input.next().charAt(0);
if(op == 'A') {
result = x.add(y);
System.out.println(x + " + " + y + " = " + result);
}