// 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 } }