Browse Source

Better Rational + Tests

Former-commit-id: f062d6e9e05b6f71bc8a5072059956c425ea8bbc
Former-commit-id: 51371aecc20f3985e50590491228ae1538847a09
Former-commit-id: cbbb41ffa048ce953e297c0a2b32de269bf83572
af/merge-core
James Jackson-South 10 years ago
parent
commit
f93dd1470e
  1. 159
      src/ImageProcessorCore/Numerics/Rational.cs
  2. 57
      tests/ImageProcessorCore.Tests/Numerics/RationalTests.cs

159
src/ImageProcessorCore/Numerics/Rational.cs

@ -19,29 +19,49 @@ namespace ImageProcessorCore
public struct Rational : IEquatable<Rational>
{
/// <summary>
/// Represents a rational object that is not a number. NaN
/// Represents a rational object that is not a number; NaN (0, 0)
/// </summary>
public static Rational Indeterminate = new Rational(3, 0);
public static Rational Indeterminate = new Rational(BigInteger.Zero);
/// <summary>
/// Represents a rational object that is equal to 0.
/// Represents a rational object that is equal to 0 (0, 1)
/// </summary>
public static Rational Zero = new Rational(0, 0);
public static Rational Zero = new Rational(BigInteger.Zero);
/// <summary>
/// Represents a rational object that is equal to 1.
/// Represents a rational object that is equal to 1 (1, 1)
/// </summary>
public static Rational One = new Rational(1, 1);
public static Rational One = new Rational(BigInteger.One);
/// <summary>
/// Represents a Rational object that is equal to negative infinity (-1, 0).
/// Represents a Rational object that is equal to negative infinity (-1, 0)
/// </summary>
public static readonly Rational NegativeInfinity = new Rational(-1, 0);
public static readonly Rational NegativeInfinity = new Rational(BigInteger.MinusOne, BigInteger.Zero);
/// <summary>
/// Represents a Rational object that is equal to positive infinity (+1, 0).
/// Represents a Rational object that is equal to positive infinity (1, 0)
/// </summary>
public static readonly Rational PositiveInfinity = new Rational(1, 0);
public static readonly Rational PositiveInfinity = new Rational(BigInteger.One, BigInteger.Zero);
/// <summary>
/// The maximum number of decimal places
/// </summary>
private const int DoubleMaxScale = 308;
/// <summary>
/// The maximum precision (numbers after the decimal point)
/// </summary>
private static readonly BigInteger DoublePrecision = BigInteger.Pow(10, DoubleMaxScale);
/// <summary>
/// Represents double.MaxValue
/// </summary>
private static readonly BigInteger DoubleMaxValue = (BigInteger)double.MaxValue;
/// <summary>
/// Represents double.MinValue
/// </summary>
private static readonly BigInteger DoubleMinValue = (BigInteger)double.MinValue;
/// <summary>
/// Initializes a new instance of the <see cref="Rational"/> struct.
@ -159,81 +179,81 @@ namespace ImageProcessorCore
{
get
{
if (this.Denominator != 0)
if (this.Denominator != BigInteger.Zero)
{
return false;
}
return this.Numerator == 3;
return this.Numerator == BigInteger.Zero;
}
}
/// <summary>
/// Gets a value indicating whether this instance is an integer.
/// Gets a value indicating whether this instance is an integer (n, 1)
/// </summary>
public bool IsInteger => this.Denominator == 1;
public bool IsInteger => this.Denominator == BigInteger.One;
/// <summary>
/// Gets a value indicating whether this instance is equal to 0
/// Gets a value indicating whether this instance is equal to 0 (0, 1)
/// </summary>
public bool IsZero
{
get
{
if (this.Denominator != 0)
if (this.Denominator != BigInteger.One)
{
return false;
}
return this.Numerator == 0;
return this.Numerator == BigInteger.Zero;
}
}
/// <summary>
/// Gets a value indicating whether this instance is equal to 1.
/// Gets a value indicating whether this instance is equal to 1 (1, 1)
/// </summary>
public bool IsOne
{
get
{
if (this.Denominator != 1)
if (this.Denominator != BigInteger.One)
{
return false;
}
return this.Numerator == 1;
return this.Numerator == BigInteger.One;
}
}
/// <summary>
/// Gets a value indicating whether this instance is equal to negative infinity (-1, 0).
/// Gets a value indicating whether this instance is equal to negative infinity (-1, 0)
/// </summary>
public bool IsNegativeInfinity
{
get
{
if (this.Denominator != 0)
if (this.Denominator != BigInteger.Zero)
{
return false;
}
return this.Numerator == -1;
return this.Numerator == BigInteger.MinusOne;
}
}
/// <summary>
/// Gets a value indicating whether this instance is equal to positive infinity (+1, 0).
/// Gets a value indicating whether this instance is equal to positive infinity (1, 0)
/// </summary>
public bool IsPositiveInfinity
{
get
{
if (this.Denominator != 0)
if (this.Denominator != BigInteger.Zero)
{
return false;
}
return this.Numerator == 1;
return this.Numerator == BigInteger.One;
}
}
@ -245,6 +265,7 @@ namespace ImageProcessorCore
/// </returns>
public double ToDouble()
{
// Shortcut return values
if (this.IsIndeterminate)
{
return double.NaN;
@ -265,7 +286,54 @@ namespace ImageProcessorCore
return (double)this.Numerator;
}
return (double)(this.Numerator / this.Denominator);
// The Double value type represents a double-precision 64-bit number with
// values ranging from -1.79769313486232e308 to +1.79769313486232e308
// values that do not fit into this range are returned as +/-Infinity
if (SafeCastToDouble(this.Numerator) && SafeCastToDouble(this.Denominator))
{
return (double)this.Numerator / (double)this.Denominator;
}
// Scale the numerator to preserve the fraction part through the integer division
// We could probably adjust this to make it less precise if need be.
BigInteger denormalized = (this.Numerator * DoublePrecision) / this.Denominator;
if (denormalized.IsZero)
{
// underflow to -+0
return (this.Numerator.Sign < 0) ? BitConverter.Int64BitsToDouble(unchecked((long)0x8000000000000000)) : 0d;
}
double result = 0;
bool isDouble = false;
int scale = DoubleMaxScale;
while (scale > 0)
{
if (!isDouble)
{
if (SafeCastToDouble(denormalized))
{
result = (double)denormalized;
isDouble = true;
}
else
{
denormalized = denormalized / 10;
}
}
result = result / 10;
scale--;
}
if (!isDouble)
{
return (this.Numerator.Sign < 0) ? double.NegativeInfinity : double.PositiveInfinity;
}
else
{
return result;
}
}
/// <inheritdoc/>
@ -289,14 +357,14 @@ namespace ImageProcessorCore
}
// Indeterminate
if (this.Numerator == 3 && this.Denominator == 0)
if (this.Numerator == BigInteger.Zero && this.Denominator == BigInteger.Zero)
{
return other.Numerator == 3 && other.Denominator == 0;
return other.Numerator == BigInteger.Zero && other.Denominator == BigInteger.Zero;
}
if (other.Numerator == 3 && other.Denominator == 0)
if (other.Numerator == BigInteger.Zero && other.Denominator == BigInteger.Zero)
{
return this.Numerator == 3 && this.Denominator == 0;
return this.Numerator == BigInteger.Zero && this.Denominator == BigInteger.Zero;
}
// ad = bc
@ -342,7 +410,7 @@ namespace ImageProcessorCore
if (this.IsZero)
{
return "[ Zero ]";
return "0";
}
if (this.IsInteger)
@ -382,16 +450,16 @@ namespace ImageProcessorCore
return;
}
if (this.Numerator == 0)
if (this.Numerator == BigInteger.Zero)
{
this.Denominator = 1;
this.Denominator = BigInteger.One;
return;
}
if (this.Numerator == this.Denominator)
{
this.Numerator = 1;
this.Denominator = 1;
this.Numerator = BigInteger.One;
this.Denominator = BigInteger.One;
return;
}
@ -414,20 +482,20 @@ namespace ImageProcessorCore
int eIndex = value.IndexOf("E", StringComparison.Ordinal);
int slashIndex = value.IndexOf("/", StringComparison.Ordinal);
// An integer such as 7
// An integer such as 3
if (periodIndex == -1 && eIndex == -1 && slashIndex == -1)
{
return new Rational(BigInteger.Parse(value));
}
// A fraction such as 3/7
// A fraction such as 22/7
if (periodIndex == -1 && eIndex == -1 && slashIndex != -1)
{
return new Rational(BigInteger.Parse(value.Substring(0, slashIndex)),
BigInteger.Parse(value.Substring(slashIndex + 1)));
}
// No scientific Notation such as 5.997
// No scientific Notation such as 3.14159
if (eIndex == -1)
{
BigInteger n = BigInteger.Parse(value.Replace(".", string.Empty));
@ -435,7 +503,7 @@ namespace ImageProcessorCore
return new Rational(n, d);
}
// Scientific notation such as 2.4556E-2
// Scientific notation such as 3.14159E-2
int characteristic = int.Parse(value.Substring(eIndex + 1));
BigInteger ten = 10;
BigInteger numerator = BigInteger.Parse(value.Substring(0, eIndex).Replace(".", string.Empty));
@ -454,6 +522,17 @@ namespace ImageProcessorCore
return new Rational(numerator, denominator);
}
/// <summary>
/// Returns a value indicating whether the given big integer can be
/// safely cast to a double.
/// </summary>
/// <param name="value">The value to test.</param>
/// <returns><c>true</c> if the value can be safely cast</returns>
private static bool SafeCastToDouble(BigInteger value)
{
return DoubleMinValue <= value && value <= DoubleMaxValue;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>

57
tests/ImageProcessorCore.Tests/Numerics/RationalTests.cs

@ -0,0 +1,57 @@
// <copyright file="RationalTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Tests
{
using Xunit;
/// <summary>
/// Tests the <see cref="Rational"/> struct.
/// </summary>
public class RationalTests
{
/// <summary>
/// Tests the equality operators for equality.
/// </summary>
[Fact]
public void AreEqual()
{
Rational r1 = new Rational(3, 2);
Rational r2 = new Rational(3, 2);
Assert.Equal(r1, r2);
Rational r3 = new Rational(7.55);
Rational r4 = new Rational(755, 100);
Rational r5 = new Rational(151, 20);
Assert.Equal(r3, r4);
Assert.Equal(r4, r5);
}
/// <summary>
/// Tests the equality operators for inequality.
/// </summary>
[Fact]
public void AreNotEqual()
{
Rational first = new Rational(0, 100);
Rational second = new Rational(100, 100);
Assert.NotEqual(first, second);
}
/// <summary>
/// Tests whether the Rational constructor correctly assign properties.
/// </summary>
[Fact]
public void ConstructorAssignsProperties()
{
Rational first = new Rational(4, 5);
Assert.Equal(4, first.Numerator);
Assert.Equal(5, first.Denominator);
}
}
}
Loading…
Cancel
Save