That is correct, I am using Java, and I am using doubles.
Yeah, I knew it was the accuracy of floating-point values. Big Java and Big C++ by Cay Horstmann have 2 issues with that. A certain number cannot be represented in binary, just as 1/3 can't be represented in decimal.
I may include a function that returns a Fraction object, as well, however the double-returning function will still be needed. Anyways, so you guys believe I should create special cases for certain cardinal angles, each 45 degrees, perhaps? Just test if its within a close-enough range to one of these values (Within E, 10-14?).
But this leads me to a question? How do calculators get the precise answers (specifically when dealing with trigonometric functions)? Even the Windows calculator....
Here's the source: (disregard the UnitVector class, it's a stub). I've also included the source files a bytecode
The command-line for Vector is:
cmd d {v_1 [v_2 ... v_d]} {u_1 [u_2 ... u_d]}
where cmd is "none", "add", "dot"(product) or "angle"(between vectors)
d is dimensions of each vector
v_1 through v_d is components of vector 1
u_1 through u_d is components of vector 2
or you can run with no args and it will execute the test() function.
Vector.java
Java: |
/*
* Vector.java
*
* Created on June 26, 2007, 3:01 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package org. aaziz. math. geom;
import java.util.Arrays;
import java.text.NumberFormat;
import java.util.Random;
import java.util.List;
/**
* Represents a geometric vector.
*
* <p>
* This class is abstract because certain methods (such as multiplication)
* are different for vectors of different dimension
* </p>
*
* <p>
* e.g. for 3-dimensionsal vectors there is a 'dot' product and a 'cross'
* product, while 2-dimensional vectors only have the 'dot' product operation.
* </p>
*
* <p>
* A vector {@code v} could be represented as {@code v = (a,b)}. In that case,
* the vector has 2 dimensions, such as {@code x} and {@code y} in a
* Cartesian plane.
* </p>
*
* <p>
* The component {@code a} would be the component of the first dimension,
* {@code x} for example. Likewise, {@code b} would be the component of the
* second dimension, {@code y} for example.
* </p>
*
* <p>
* So, for {@code v}, {@code |v|} represents the magnitude of the vector. That
* is, {@code |v|} is the length of the vector. The square of the magnitude
* is equal to the square of each component, summed.
* </p>
*
* <p>
* Subclasses that define a fixed dimension should override the constructor
* {@link #Vector(int)} to forbid the client to set a different number
* of dimensions other than intended.
* </p>
*
* @author ANTHONYA
*/;
public class Vector implements Dimensional
{
/**
* The components of this vector
*/
private double[] components;
/**
* Creates a new, blank {@code Vector} of zero dimensions, thus has
* no magnitude or direction.
*/
public Vector()
{
this(0);
}
/**
* Create a new {@code Vector} as a copy from another.
*
* <p>The new {@code Vector} is given the same amount of dimensions as
* {@code v}. The new {@code Vector} will be parellel to
* {@code v} and have the same magnitude as well.</p>
*
* @param v {@code Vector} to be copied
*/
public Vector(Vector v )
{
this(v. getDimensions());
for (int i = 0; i < this. getDimensions(); i++ )
this. setComponent(i, v. getComponent(i ));
}
/**
* Instantiate new {@code Vector} with {@code dimesions}
* dimensions, with no magnitude (a zero Vector).
*
* @param dimensions the amount of dimensions this {@code Vector}
* will have
*/
public Vector(int dimensions )
{
this. components = new double[dimensions ];
for (int i = 0; i < this. getDimensions(); i++ )
this. setComponent(i, 0);
}
/**
* Instantiate new {@code Vector} using the supplied
* values in {@code components}.
*
* <p>
* This object will have the same dimensions as there is components, or
* otherwise: {@code this.getDimensions() == components.length}.
* </p>
*
* @param components component values that this object will have
*/
public Vector(double... components)
{
this(components. length);
for (int i = 0; i < this. getDimensions(); i++ )
this. setComponent(i, components [i ]);
}
/**
* Returns the amount of dimensions this {@code Vector} has.
*
* @return the dimensions this {@code Vector} has.
*/
public int getDimensions ()
{
return components. length;
}
/**
* Returns the component of the specified {@code dimension}.
*
* @param dimension dimension to get component of (such as x, y, etc)
*
* @return component (magnitude) of the specified {@code dimension}.
* @throws InvalidDimensionException when
* {@code dimension < 0 || dimension > getDimensions()}.
*/
public double getComponent (int dimension ) throws InvalidDimensionException
{
if (dimension < 0 || dimension > getDimensions ())
throw new InvalidDimensionException (dimension );
return components [dimension ];
}
/**
* Gets all the components as an array of integers.
*
* <p>
* The first index represents the first dimension, etc.
* </p>
*
* @return list of components, in order
*/
public double[] getComponents ()
{
return Arrays. copyOf(components, components. length);
}
/**
* Sets the component of the specified {@code dimension}.
*
* @param dimension dimension to set component for (such as x, y, etc)
* @param value value to set to the component of the specified
* {@code dimension}
*
* @throws InvalidDimensionException when
* {@code dimension < 0 || dimension > getDimensions()}.
*/
public void setComponent (int dimension, double value )
throws InvalidDimensionException
{
if (dimension < 0 || dimension > getDimensions ())
throw new InvalidDimensionException (dimension );
components [dimension ] = value;
}
/**
* Calculates and returns the magnitude of this {@code Vector}.
*
* <p>
* Calculates the magnitute of this Vector, in unit, using an algorithm
* based on Pythagorean's Theorem. The magnitude is always a positive
* scalar value (i.e. no direction).
* </p>
*
* <p>
* The formula used is:
* </p>
*
* <pre>
* v = a vector
* |v| = magnitude of v
* v_1 = component of v in 1st dimension
* v_2 = component of v in 2nd dimension
* .
* .
* v_n = component of v in nth dimension
*
* (^ represents exponent operator)
*
* |v| = sqrt(v_1^2 + v_2^2 + .. + v_n^n)
* </pre>
*
* @return magnitute of this {@code Vector} in units
*/
public double getMagnitude ()
{
// Need to get the sum of each component, squared
// eg. x^2 + y^2 + z^2
int sumOfSquares = 0;
for (double component : components )
sumOfSquares += Math. pow(component, 2);
return Math. sqrt(sumOfSquares );
}
/**
* Calculates the angle between this {@code Vector} and {@code other}.
*
* <p>
* The angle between two vectors is the angle when they are joined at the
* tails. The vectors can be of any dimensions, as long as they have the
* same amount of dimensions. The analytical formula for finding the angle
* between any two vectors is:
* </p>
*
* <pre>
* let v and u be vectors of the same dimension
*
* let |v| and |u| represent the magnitudes of v and u, respectively
*
* let A represent the angle between v and u
*
* v @ u where @ represents <b>dot product</b>
* cos A = -------
* |v|*|u|
* </pre>
*
* @param other another {@code Vector}
*
* @return the angle, in degrees, between this {@code Vector} and
* {@code other}.
*
* @throws InvalidDimensionException if
* {@code this.getDimensions() != other.getDimensions()}
*/
public double angleBetween (Vector other ) throws InvalidDimensionException
{
if (this. getDimensions() != other. getDimensions())
{
throw new InvalidDimensionException (
"Vectors must be of the same dimension to" +
"find the angle between them.");
}
// Calculate right hand side of equation (in javadoc comments)
double rhs = (this. dot(other ))
/ (this. getMagnitude() * other. getMagnitude());
// Use inverse of cos to find angle
// Trigonometric functions of the Math class give angles in radians
double angle = Math. toDegrees(Math. acos(rhs ));
//
return angle ;
}
/**
* Returns a {@code Vector} that is opposite to this one.
*
* <p>
* If {@code v} is a vector, than the opposite is denoted as {@code -v}.
* This opposite vector has the same magnitude as the original, and is in
* an opposite direction. Each component in {@code -v} is the negative of
* its respective component in {@code v}.
* <p>
*
* <p>
* Since this new vector has the same magnitude as the old one, it is true
* that {@code v.getMagnitude() == v.opposite().getMagnitude()}.
* </p>
*
* <p>This is the same as calling {@code scale(-1)}</p>
*
* @return a {@code Vector} opposite to this one
*
* @see #scale(double)
*/
public Vector opposite ()
{
return scale (- 1);
}
public UnitVector unitVector ()
{
return new UnitVector (this);
}
/**
* Returns a {@code Vector} that is opposite to this one.
*
* <p>
* If {@code v} is a vector, than the opposite is denoted as {@code -v}.
* This opposite vector has the same magnitude as the original, and is in
* an opposite direction. Each component in {@code -v} is the negative of
* its respective component in {@code v}.
* <p>
*
* <p>
* Since this new vector has the same magnitude as the old one, it is true
* that {@code v.getMagnitude() == v.opposite().getMagnitude()}.
* </p>
*
* <p>This is the same as calling {@code scale(-1)}</p>
*
* @param factor factor by which to scale this {@code Vector}
*
* @return a {@code Vector} that is parallel to this one, whose magnitude is
* a scale of
*
* @see #scale(double)
*/
public Vector scale (double factor )
{
// Create a new set of components and scale each one
double[] scaledComps = getComponents ();
for (double c : scaledComps )
c *= factor;
return new Vector(scaledComps );
}
/**
* Returns the sum of this {@code Vector} added to another.
*
* <p>
* This method adds the vectors by adding each component to the
* corresponding component in {@code other}. It is calculated using the
* following equations:
* </p>
*
* <pre>
* v = a vector
* u = a vector of same dimensions of v
*
* v_1 = component of v in 1st dimension
* v_2 = component of v in 2nd dimension
* .
* .
* v_n = component of v in nth dimension
*
* u_1 = component of u in 1st dimension
* u_2 = component of u in 2nd dimension
* .
* .
* u_n = component of u in nth dimension
*
* where v = (v_1, v_2, .. , v_n)
* and u = (u_1, u_2, .. , u_n)
*
* v + u = (v_1, v_2, .. , v_n) + (u_1, u_2, .. , u_n)
*
* if t = v + u then t is a vector such that
*
* t = (v_1 + u_1, v_2 + u_2, .. , v_n + u_n)
* </pre>
*
* <p>
* Furthermore, say there is a vector {@code a} and a vector {@code b}, and
* {@code c = a + b}. If {@code b} is placed so that it's tail is at the
* head of {@code a} (or vice versa), then the tail of {@code c} would be
* at the tail of {@code a} and the head of {@code c} would be at the head
* of {@code b}. This is called the parallelogram law:
* </p>
*
* <p>
* <img src="doc-files/parallelogram_law.gif">
* </p>
*
* <p><i>
* <b>Note</b>: this operation is purely functional and does not modify
* the original {@code Vector} in any way. Only vectors of the same
* dimensions can be added.
* </i></p>
*
* @param other {@code Vector} to add to this one
*
* @return a {@code Vector} that is the sum of this {@code Vector} and
* {@code other}
*
* @throws InvalidDimensionException if
* {@code this.getDimensions() != other.getDimensions()}
*/
public Vector add (Vector other ) throws InvalidDimensionException
{
if (this. getDimensions() != other. getDimensions())
{
throw new InvalidDimensionException (
"Only Vectors of same dimension can be added");
}
double[] otherComponents = other. getComponents();
// Add components from this object and the specified one into a
// new array of components
double[] newComponents = new double[components. length];
for (int i = 0; i < newComponents. length; i++ )
{
newComponents [i ] = components [i ] + otherComponents [i ];
}
Vector v = new Vector(newComponents );
return v;
}
/**
* Returns the dot product of this {@code Vector} and another.
*
* <p>
* The dot product (or scalar product) of two vectors. It is calculated
* using the following equation:
* </p>
*
*
* <pre>
* v = a vector
* u = a vector of same dimensions of v
*
* v_1 = component of v in 1st dimension
* v_2 = component of v in 2nd dimension
* .
* .
* v_n = component of v in nth dimension
*
* u_1 = component of u in 1st dimension
* u_2 = component of u in 2nd dimension
* .
* .
* u_n = component of u in nth dimension
*
* where v = (v_1, v_2, .. , v_n)
* and u = (u_1, u_2, .. , u_n)
*
* u • v = (v_1, v_2, .. , v_n) • (u_1, u_2, .. , u_n)
*
* u • v = (v_1)(u_1) + (v_2)(u_2) + .. + (v_n)(u_n)
* </pre>
*
* <p><i>
* <b>Note</b>: this operation is purely functional and does not modify
* the original {@code Vector} in any way. The dot product can only be
* calculated on vectors of the same dimension.
* </i></p>
*
* @param other {@code Vector} to perform dot product operation with
*
* @return an scalar ({@code double}) that is dot product of this
* {@code Vector} and {@code other}
*
* @throws InvalidDimensionException if
* {@code this.getDimensions() != other.getDimensions()}
*/
public double dot (Vector other ) throws InvalidDimensionException
{
if (this. getDimensions() != other. getDimensions())
{
throw new InvalidDimensionException (
"Only Vectors of same dimension can be added");
}
double[] otherComponents = other. getComponents();
// Multiply each component in this vector by its respective component
// in other, and sum the result (using a running total)
double dotProduct = 0;
for (int i = 0; i < components. length; i++ )
{
dotProduct += components [i ] * otherComponents [i ];
}
return dotProduct;
}
/**
* Equates if two vectors are equal (parallel and same magnitude).
*
* <p>
* Two {@code Vector}s are considered equal if they are parallel
* (have the same direction) as well as have the same magnitude.
* </p>
*
* <p>
* This can also be equated by comparing that the components in each
* dimension are the same.
* </p>
*
* @return
* - {@code true} if:<br>
* {@code obj} is of a non-null object of type {@code Vector}
* ; and<br>
* {@code obj.getDimensions() == this.getDimensions()}
* ; and<br>
* {@code obj} has the same magnitude/direction as this
* {@code Vector}
* <br>
* <br>
* - {@code false} otherwise
*/
@Override
public boolean equals (Object obj )
{
if (obj == this)
return true;
if (obj == null)
return false;
if (getClass () != obj. getClass())
return false;
// Cast obj to Vector to compare
final Vector other = (Vector) obj;
// Compare amount of dimensions
if (this. getDimensions() != other. getDimensions())
return false;
// Compare each component
for (int i = 0; i < this. getDimensions(); i++ )
{
if (this. getComponent(i ) != other. getComponent(i ))
return false;
}
return true;
}
/**
* Returns a hashcode value of this {@code Vector}.
*
* @return hashcode of this object.
*/
@Override
public int hashCode ()
{
// Multiple each component by the magnitude and a mix-up number,
// then add it to the hash value
int hash = 7;
double magnitude = getMagnitude ();
for (double i : components )
{
hash = 31 * hash + (int)Math. round(i * getMagnitude ());
}
return hash;
}
/**
* Returns a {@code String} representation of this {@code Vector}.
*
* <p>
* The string representation of a vector is its components ordered in
* brackets. For example, {@code new Vector(2, 4).toString()} will return
* "{@code (2,4)}".
* </p>
*
* @return string representation of this {@code Vector}.
*/
@Override
public String toString ()
{
NumberFormat nf = NumberFormat. getInstance();
nf. setMinimumFractionDigits(0);
StringBuilder vComps = new StringBuilder ();
// Don't add a comma after last component - (1,2,3,4)
for (int i = 0; i < components. length - 1; i ++ )
{
vComps. append(
nf. format( components [i ] )
). append(",");
}
vComps. append(
nf. format( components [components. length - 1] )
);
StringBuilder builder = new StringBuilder ();
builder. append("("). append(vComps ). append(")");
return builder. toString();
}
/**
* Test
*/
public static void test ()
{
Vector i = new Vector(2, 2);
Vector j = new Vector(2, 2);
double angle = i. angleBetween(j );
System. out. format("Angle is %s%n", angle );
Random r = new Random();
double x = r. nextDouble() * r. nextInt(Integer. MAX_VALUE);
double y = r. nextDouble() * r. nextInt(Integer. MAX_VALUE);
double z = r. nextDouble() * r. nextInt(Integer. MAX_VALUE);
System. out. format("x is %s; y is %s; z is %s%n", x, y, z );
System. out. format(
"Math.ulp(x) is %s; Math.ulp(y) is %s; Math.ulp(z) is %s%n",
Math. ulp(x ), Math. ulp(y ), Math. ulp(z ));
}
/**
* Driver method
*
* @param args takes several args:<br>
* (1) operation. Is one of {@code add}, {@code dot}, {@code angle},
* @code none}<br>
* (2) number of dimensions each vector will have<br>
* (3) first vector, each component separated by a space, so for a
* vector (2,3,4) you would type
* {@code 2 3 4}<br>
* (4) second vector, same as above<br>
*/
public static void main (String[] args )
{
List<String> argList = Arrays. asList(args );
// Perform no-arg test if run w/o args
if (argList. isEmpty())
{
test ();
}
// Needs at least 4 arguments
if (argList. size() < 4)
{
System. out. format("Not enough arguments. Terminated.%n");
System. exit(1);
}
final int NONE = 0;
final int ADD = 1;
final int DOT = 2;
final int ANGLE = 3;
int op = NONE;
// Parse operation argument
if (argList. get(0). equalsIgnoreCase("none"))
{
op = NONE;
}
else if (argList. get(0). equalsIgnoreCase("add"))
{
op = ADD;
}
else if (argList. get(0). equalsIgnoreCase("dot"))
{
op = DOT;
}
else if (argList. get(0). equalsIgnoreCase("angle"))
{
op = ANGLE;
}
else
{
System. out. format("Operation must be one of none, add, dot, or angle");
System. exit(1);
}
// Determine amount of dimensions
int dims = 0;
try
{
dims = Integer. parseInt(argList. get(1));
}
catch (NumberFormatException e )
{
System. out. format("Second parameter must be an integer." +
"You supplied %s.%n", argList. get(1));
System. exit(1);
}
// Make sure there are d * 2 + 2 arguments (that is, 2 vectors
// (each has 'dims' dimensions) and first 2 args)
if (argList. size() < dims * 2 + 2)
{
System. out. format(
"Needs %s arguments. Each vector's component is a separate argument." +
"You supplied %s arguments.", dims * 2 + 2, args. length);
System. exit(1);
}
// Read components into two lists
double[] compsA = new double[dims ];
double[] compsB = new double[dims ];
for (int i = 2; i < argList. size(); i++ )
{
//Try to parse component
double compArg = 0;
try
{
compArg = Double. parseDouble(argList. get(i ));
}
catch (NumberFormatException e )
{
System. out. format(
"Component %s is not valid. It must be a decimal.",
argList. get(i ));
System. exit(1);
}
// Test if component is for first or second vector
if (i - 2 < dims )
{
compsA [i - 2] = compArg;
}
else
{
compsB [i - dims - 2] = compArg;
}
}
Vector a = new Vector(compsA );
Vector b = new Vector(compsB );
Object result = new String("Error!");
switch (op )
{
case NONE:
result = new String("No operation");
break;
case ADD:
result = a. add(b );
break;
case DOT:
result = a. dot(b );
break;
case ANGLE:
result = a. angleBetween(b );
break;
default:
break;
}
System. out. format(
"Vector a is %s. It's magnitude is %s and it's UnitVector is %s.%n",
a, a. getMagnitude(), a. unitVector());
System. out. format(
"Vector b is %s. It's magnitude is %s and it's UnitVector is %s.%n",
b, b. getMagnitude(), b. unitVector());
System. out. format("Operation result: %s.%n", result );
}
}
|
InvalidDimensionException.java
Java: |
/*
* InvalidDimensionException.java
*
* Created on 27-Jun-2007, 11:04:17 AM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package org. aaziz. math. geom;
/**
* Thrown when a client tries to perform an operation on a {@link Dimensional}
* involving an invalid dimension.
*
* <p>
* An invalid dimension could be:
* <ul>
* <li>Dimensions less than zero</li>
* <li>Dimension greater than the number of dimensions of the object</li>
* <li>Dimension is not valid in other way (eg. not accessable)</li>
* <ul>
* </p>
*
* @author ANTHONYA
*/
public class InvalidDimensionException extends RuntimeException
{
private int dimension = 0;
/**
* Constructs a <code>InvalidDimensionException</code> with a general
* error message.
*/
public InvalidDimensionException ()
{
super ("Invalid dimension");
}
/**
* Constructs a <code>InvalidDimensionException</code> with a specified
* error message.
* @param message message of exception
*/
public InvalidDimensionException (String message )
{
super (message );
}
/**
* Constructs a <code>InvalidDimensionException</code> with the specifed
* dimension number that is invalid or caused the exception.
*
* @param dimension the dimension that is the cause of the issue.
*/
public InvalidDimensionException (int dimension )
{
super ("Invalid dimension: " + dimension );
this. dimension = dimension;
}
/**
* Constructs a <code>InvalidDimensionException</code> with the specifed
* dimension number that is invalid or caused the exception and optional
* exception that was raised while performing the operation.
*
* @param dimension the dimension that is the cause of the issue
* @param cause exception that was raised while performing the operation
*/
public InvalidDimensionException (int dimension, Throwable cause )
{
super ("Invalid dimension: " + dimension, cause );
this. dimension = dimension;
}
/**
* Get the dimension that that is invalid or otherwised caused this
* exception
*
* @return dimension number that caused this exception
*/
public int getDimension ()
{
return dimension;
}
}
|
Dimensional.java
Java: |
/*
* Dimensional.java
*
* Created on 27-Jun-2007, 12:03:40 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package org.aaziz.math.geom;
/**
* This class represents a object that has dimensions.
*
* <p>This could be a geometric vector, line, plane, etc.</p>
*
* @see InvalidDimensionException
*
* @author ANTHONYA
*/
public interface Dimensional
{
/**
* Returns the number of dimensions this <code>Dimensional</code> has.
*
* @return the number of dimensions
*/
public int getDimensions();
}
|
UnitVector.java
Java: |
/*
* UnitVector.java
*
* Created on 29-Jun-2007, 4:24:21 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package org. aaziz. math. geom;
/**
*
* @author ANTHONYA
*/
public class UnitVector extends Vector implements Dimensional
{
public UnitVector (Vector v )
{
super (v ); //todo
}
} |
|