// Copyright (C) Stichting Deltares 2016. All rights reserved.
//
// This file is part of Ringtoets.
//
// Ringtoets is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see .
//
// All names, logos, and references to "Deltares" are registered trademarks of
// Stichting Deltares and remain full property of Stichting Deltares at all times.
// All rights reserved.
using System;
using System.ComponentModel;
using System.Globalization;
using Core.Common.Base.Properties;
using Core.Common.Base.TypeConverters;
namespace Core.Common.Base.Data
{
///
/// This class represents a that is being rounded to a certain
/// number of places.
///
[TypeConverter(typeof(RoundedDoubleConverter))]
public struct RoundedDouble : IEquatable, IEquatable, IFormattable
{
///
/// The maximum number of decimal places supported by this class.
///
public const int MaximumNumberOfDecimalPlaces = 15;
private readonly double value;
private readonly int numberOfDecimalPlaces;
///
/// Initializes a new instance of the class with a
/// given value.
///
/// The number of decimal places.
/// The value to initialize the instance with.
///
/// Thrown when is not in range [0, ].
///
public RoundedDouble(int numberOfDecimalPlaces, double value = 0.0)
{
ValidateNumberOfDecimalPlaces(numberOfDecimalPlaces);
this.numberOfDecimalPlaces = numberOfDecimalPlaces;
this.value = RoundDouble(value, numberOfDecimalPlaces);
}
///
/// Gets the number of decimal places use to round to.
///
public int NumberOfDecimalPlaces
{
get
{
return numberOfDecimalPlaces;
}
}
///
/// Gets the value.
///
public double Value
{
get
{
return value;
}
}
public static bool operator ==(RoundedDouble left, RoundedDouble right)
{
return Equals(left, right);
}
public static bool operator !=(RoundedDouble left, RoundedDouble right)
{
return !Equals(left, right);
}
public static RoundedDouble operator -(RoundedDouble left, RoundedDouble right)
{
int smallestNumberOfDecimalPlaces = Math.Min(left.numberOfDecimalPlaces, right.numberOfDecimalPlaces);
return new RoundedDouble(smallestNumberOfDecimalPlaces,
left.value - right.value);
}
public static RoundedDouble operator +(RoundedDouble left, RoundedDouble right)
{
int smallestNumberOfDecimalPlaces = Math.Min(left.numberOfDecimalPlaces, right.numberOfDecimalPlaces);
return new RoundedDouble(smallestNumberOfDecimalPlaces,
left.value + right.value);
}
public static RoundedDouble operator *(RoundedDouble left, double right)
{
return new RoundedDouble(left.numberOfDecimalPlaces, left.value * right);
}
public static RoundedDouble operator *(double left, RoundedDouble right)
{
return new RoundedDouble(right.numberOfDecimalPlaces, left * right.value);
}
public static RoundedDouble operator *(RoundedDouble left, RoundedDouble right)
{
int smallestNumberOfDecimalPlaces = Math.Min(left.numberOfDecimalPlaces, right.numberOfDecimalPlaces);
return new RoundedDouble(smallestNumberOfDecimalPlaces, left.value * right.value);
}
public static implicit operator double(RoundedDouble d)
{
return d.value;
}
public static explicit operator RoundedDouble(double d)
{
return new RoundedDouble(MaximumNumberOfDecimalPlaces, d);
}
///
/// Converts this value to another but with the given
/// number of decimal places instead.
///
/// The new number of decimal places.
/// The converted .
public RoundedDouble ToPrecision(int newNumberOfDecimalPlaces)
{
if (newNumberOfDecimalPlaces == NumberOfDecimalPlaces)
{
return this;
}
return new RoundedDouble(newNumberOfDecimalPlaces, value);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((RoundedDouble)obj);
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public string ToString(string format, IFormatProvider formatProvider)
{
if (double.IsPositiveInfinity(value))
{
return Resources.RoundedDouble_ToString_PositiveInfinity;
}
if (double.IsNegativeInfinity(value))
{
return Resources.RoundedDouble_ToString_NegativeInfinity;
}
return Value.ToString(format ?? GetFormat(), formatProvider ?? CultureInfo.CurrentCulture);
}
public override string ToString()
{
return ToString(null, null);
}
public bool Equals(double other)
{
return Value.Equals(other);
}
public bool Equals(RoundedDouble other)
{
return Value.Equals(other.Value);
}
private static double RoundDouble(double value, int numberOfDecimalPlaces)
{
return IsSpecialDoubleValue(value) ?
value :
Math.Round(value, numberOfDecimalPlaces, MidpointRounding.AwayFromZero);
}
///
/// Validates .
///
/// The new value for .
///
/// Thrown when is not in range [0, 15].
///
private static void ValidateNumberOfDecimalPlaces(int numberOfDecimalPlaces)
{
if (numberOfDecimalPlaces < 0 || numberOfDecimalPlaces > MaximumNumberOfDecimalPlaces)
{
string message = string.Format("Value must be in range [0, {0}].",
MaximumNumberOfDecimalPlaces);
throw new ArgumentOutOfRangeException("numberOfDecimalPlaces",
message);
}
}
private static bool IsSpecialDoubleValue(double value)
{
return double.IsNaN(value) ||
double.IsPositiveInfinity(value) ||
double.IsNegativeInfinity(value);
}
private string GetFormat()
{
return "F" + NumberOfDecimalPlaces;
}
}
}