// Copyright (C) Stichting Deltares 2019. All rights reserved.
//
// This file is part of Riskeer.
//
// Riskeer 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, IComparable,
IComparable, IComparable
{
///
/// The maximum number of decimal places supported by this class.
///
public const int MaximumNumberOfDecimalPlaces = 15;
///
/// Represents a value that is not a number (NaN). This field is constant.
///
///
public static readonly RoundedDouble NaN = new RoundedDouble(MaximumNumberOfDecimalPlaces, double.NaN);
///
/// 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);
NumberOfDecimalPlaces = numberOfDecimalPlaces;
Value = RoundDouble(value, numberOfDecimalPlaces);
}
///
/// Gets the number of decimal places use to round to.
///
public int NumberOfDecimalPlaces { get; }
///
/// Gets the value.
///
public double Value { get; }
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)
{
return newNumberOfDecimalPlaces != NumberOfDecimalPlaces
? new RoundedDouble(newNumberOfDecimalPlaces, Value)
: this;
}
public static bool operator <(RoundedDouble left, RoundedDouble right)
{
return left.Value < right.Value;
}
public static bool operator <=(RoundedDouble left, RoundedDouble right)
{
return left.Value <= right.Value;
}
public static bool operator >(RoundedDouble left, RoundedDouble right)
{
return left.Value > right.Value;
}
public static bool operator >=(RoundedDouble left, RoundedDouble right)
{
return left.Value >= right.Value;
}
public static bool operator <(RoundedDouble left, double right)
{
return left.Value < right;
}
public static bool operator <=(RoundedDouble left, double right)
{
return left.Value <= right;
}
public static bool operator >(RoundedDouble left, double right)
{
return left.Value > right;
}
public static bool operator >=(RoundedDouble left, double right)
{
return left.Value >= right;
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
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(CultureInfo.CurrentCulture,
"Value must be in range [0, {0}].",
MaximumNumberOfDecimalPlaces);
throw new ArgumentOutOfRangeException(nameof(numberOfDecimalPlaces),
message);
}
}
private static bool IsSpecialDoubleValue(double value)
{
return double.IsNaN(value) ||
double.IsPositiveInfinity(value) ||
double.IsNegativeInfinity(value);
}
private string GetFormat()
{
return "F" + NumberOfDecimalPlaces;
}
#region ToString
public override string ToString()
{
return ToString(null, null);
}
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);
}
#endregion
#region CompareTo
public int CompareTo(object obj)
{
if (obj == null)
{
return 1;
}
if (obj is RoundedDouble)
{
return CompareTo((RoundedDouble) obj);
}
if (obj is double)
{
return CompareTo((double) obj);
}
throw new ArgumentException("Arg must be double or RoundedDouble");
}
public int CompareTo(double other)
{
return Value.CompareTo(other);
}
public int CompareTo(RoundedDouble other)
{
return Value.CompareTo(other.Value);
}
#endregion
#region Equals
public bool Equals(double other)
{
return Value.Equals(other);
}
public bool Equals(RoundedDouble other)
{
return Value.Equals(other.Value);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((RoundedDouble) obj);
}
#endregion
}
}