In my current game I have the need for values ranging from 0 to 1, or from -1 to 1. The values should never exceed this threshold, so instead of constantly validating the values I made a custom data type which essentially clamps a float to that threshold. I have been researching various ways of making it better, and I feel like what I have now is just about perfect for my uses. I decided against implementing IConvertible since all I'm really doing is wrapping a float with code I need; the value is still a float, and can be extracted and converted in the unlikely event it would need to be.
Is there anything I can improve on? Anything I'm doing wrong? I'd love to know!
P.S. You'll notice there's mention of a "SBloat", that's the same thing as a Bloat, just signed and ranging from -1.0 to 1.0. The code is almost identical.
using System;
using UnityEngine;
namespace ProjectBleak {
/// <summary>
/// Represents an unsigned binary float ranging from 0 to 1.0. If the value exceeds this limit, it will be clamped to stay in range.
/// </summary>
[System.Diagnostics.DebuggerDisplay("{value}")]
public struct Bloat : IEquatable<Single>, IFormattable {
/// <summary> 0 </summary>
public static readonly float MinValue = 0f;
/// <summary> 1.0 </summary>
public static readonly float MaxValue = 1f;
private float value;
/// <summary> Should an error be thrown when the value would be lower than 0 or higher than 1.0? </summary>
public bool ErrorOnExceed { get; set; }
/// <summary>
/// Represents an unsigned binary float ranging from 0 to 1.0. If the value exceeds this limit, it will be clamped to stay in range.
/// </summary>
/// <param name="value">Value ranging from 0 to 1.0</param>
/// <param name="errorOnExceed">Should an error be thrown when the value would be lower than 0 or higher than 1.0?</param>
public Bloat(float value, bool errorOnExceed = false) {
ErrorOnExceed = errorOnExceed;
if (value < MinValue) {
value = MinValue;
if (ErrorOnExceed) {
Debug.LogError($"Bloat:: {value} exceeds minimum allowed value.");
}
}
else if (value > MaxValue) {
value = MaxValue;
if (ErrorOnExceed) {
Debug.LogError($"Bloat:: {value} exceeds maximum allowed value.");
}
}
this.value = value;
}
public override int GetHashCode() {
unchecked {
int hash = 17;
hash = hash * 23 + value.GetHashCode();
if (ErrorOnExceed) {
hash = hash |= 1 << 19;
}
else {
hash = hash |= 1 << 13;
}
return hash;
}
}
public bool Equals(float other) {
return value == other;
}
public override bool Equals(object obj) {
if (obj is SBloat || obj is Bloat || obj is Single) {
return Equals((float)obj);
}
return false;
}
public override String ToString() {
return value.ToString();
}
public String ToString(IFormatProvider provider) {
return value.ToString(provider);
}
public String ToString(String format) {
return value.ToString(format);
}
public String ToString(String format, IFormatProvider provider) {
return value.ToString(format, provider);
}
// Keep the Signed-to-Unsigned conversion explicit since we will be losing any negative number
public static explicit operator Bloat(SBloat s) => new Bloat(s);
public static implicit operator Bloat(Single f) => new Bloat(f);
public static implicit operator float(Bloat b) => b.value;
public static Bloat operator +(Bloat left, Single right) {
return new Bloat(left.value + right, left.ErrorOnExceed);
}
public static Bloat operator -(Bloat left, Single right) {
return new Bloat(left.value - right, left.ErrorOnExceed);
}
public static Bloat operator +(Bloat left, Bloat right) {
return new Bloat(left.value + right.value, left.ErrorOnExceed);
}
public static Bloat operator -(Bloat left, Bloat right) {
return new Bloat(left.value - right.value, left.ErrorOnExceed);
}
public static bool operator ==(Bloat left, Single right) {
return left.value == right;
}
public static bool operator !=(Bloat left, Single right) {
return left.value != right;
}
public static bool operator <(Bloat left, Single right) {
return left.value < right;
}
public static bool operator >(Bloat left, Single right) {
return left.value > right;
}
public static bool operator <=(Bloat left, Single right) {
return left.value <= right;
}
public static bool operator >=(Bloat left, Single right) {
return left.value >= right;
}
public static bool operator ==(Single left, Bloat right) {
return left == right.value;
}
public static bool operator !=(Single left, Bloat right) {
return left != right.value;
}
public static bool operator <(Single left, Bloat right) {
return left < right.value;
}
public static bool operator >(Single left, Bloat right) {
return left > right.value;
}
public static bool operator <=(Single left, Bloat right) {
return left <= right.value;
}
public static bool operator >=(Single left, Bloat right) {
return left >= right.value;
}
public static bool operator ==(Bloat left, Bloat right) {
return left.value == right.value;
}
public static bool operator !=(Bloat left, Bloat right) {
return left.value != right.value;
}
public static bool operator <(Bloat left, Bloat right) {
return left.value < right.value;
}
public static bool operator >(Bloat left, Bloat right) {
return left.value > right.value;
}
public static bool operator <=(Bloat left, Bloat right) {
return left.value <= right.value;
}
public static bool operator >=(Bloat left, Bloat right) {
return left.value >= right.value;
}
public static bool operator ==(Bloat left, SBloat right) {
return left.value == right;
}
public static bool operator !=(Bloat left, SBloat right) {
return left.value != right;
}
public static bool operator <(Bloat left, SBloat right) {
return left.value < right;
}
public static bool operator >(Bloat left, SBloat right) {
return left.value > right;
}
public static bool operator <=(Bloat left, SBloat right) {
return left.value <= right;
}
public static bool operator >=(Bloat left, SBloat right) {
return left.value >= right;
}
}
}
BFloat
, so the link withfloat
is more obvious. Actually, binary seems to suggest only 2 possible values, so normalized or ranged might be more clear. \$\endgroup\$