2

Looking for some advice regarding Infinity/-Infinity in C#.

I'm currently building Geometry classes, for shapes like Rectangle/Circle etc. The user will provide the input for Width/Depth and Diameter respectively.

These properties will be double and I understand that instead of explicitly overflowing, if you were to multiple double.MaxValue by 2, then you would get +infinity etc.

Each of the shape classes will have additional properties such as Area, Perimeter etc. Therefore, even if the provided dimensions are less than MaxValue, there is a chance that computed values could be a huge number if the user was so inclined:

E.g. Math.PI x Math.Pow(diameter, 2) / 4 => Math.PI x Math.Pow(double.MaxValue, 2) / 4

(i.e. This method would result in +infinity even though the user provided MaxValue as input.)

My question is whether I should always be guarding against infinity? Should I throw an exception (OverflowException) if either the user value or the computed values enter infinity?

It looks like it could be a code smell to check for infinity for every property/method in these geometric classes... Is there a better way?

public double Area
{
    get
    {
        double value = Math.PI * Math.Pow(this.diameter, 2) / 4;
        if (double.IsInfinity(value)
        {
            throw new OverflowException("Area has overflowed.");
        }
    }
}

public double Perimeter
{
    get
    {
        double value = Math.PI * this.diameter;
        if (double.IsInfinity(value)
        {
            throw new OverflowException("Perimeter has overflowed.");
        }
    }
}

Appreciate your time and thoughts! Cheers.

JB101UK
  • 33
  • 1
  • 8
  • @PavelAnikhouski That's not relevant for floating point operations, except conversions to integral types. – jdphenix May 17 '20 at 20:07
  • Hi @PavelAnikhouski, thanks for your comment! my understanding is that checked and unchecked only applies to **int** types? Whereas double/float _overflow_ into infinity? Have I misunderstood? – JB101UK May 17 '20 at 20:08

3 Answers3

2

Each of the shape classes will have additional properties such as Area, Perimeter etc. Therefore, even if the provided dimensions are less than MaxValue, there is a chance that computed values could be a huge number if the user was so inclined

double max is 1.7976931348623157E+308, worrying about the user inputing some geometry data that hits that limit is pointless. Simply give back infinty, it'll probably never happen. To put in perspective, the distante to Proxima Centauri in micrometers is aproximately 4,014 × 10^22 so you'll notice how worrying about this is a waste of time.

You could, in order to at least give a reasonable alternative to the user, let him define what one unit in your geometry environment represents; one micrometer, milimeter, meter, kilometer, lightyear, parsec, etc. so he will always keep things in reasonable scales.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • thanks @InBetween, your argument makes a lot of sense. I can see I was probably worrying about nothing, but I was concerned that if someone really wanted to break the application then they could (even if the average user would not be interested in doing so). I was looking at the problem from a defensive coding perspective, but the risk is so low I guess it's not even worth stressing about. Cheers – JB101UK May 17 '20 at 20:49
0

Conceptually, you have a design problem. Values that result in an infinite area result in an invalid state for your shape.

For example, say the shape in question is a circle. Any value passed in which results in Area > double.MaxValue is an invalid state. Stated differently, any value diameter where Math.PI * Math.Pow(diamater, 2) > double.MaxValue is invalid.

Solve double.MaxValue = Math.PI * Math.Pow(diamater, 2) for diameter (it's 1.5129091144565236E+154, and you're welcome to validate my math), and check for that in your constructor,

private const double MaxDiameter = 1.5129091144565236E+154;
Circle(double diameter) 
{
    if (diameter > MaxDiameter) 
    {
        throw ArgumentOutOfRangeException(); 
    }
}

--

None of the above probably matters

In practice, consider the following. the diameter of the known universe is around 30 gigaparsecs, which converted to Planck lengths (the smallest distance unit I can think of) is around 5.7e61, which is far, far below double.MaxValue.

I'd not worry about double overflow in your properties. It's sensible to design your shape so you it uses a unit type you've defined, so you always know what units you're working with.

public Circle(Meters diamater)
public Rectangle(Meters width, Meters height)
jdphenix
  • 15,022
  • 3
  • 41
  • 74
  • Thanks @jdphenix, I fully agree with your logic. I did wonder whether I was maybe making mountains out of molehills! haha I suppose I was just concerned that someone would try and break the application simply for the sake of it. But I guess they'd only be breaking their own process, so not worth stressing about. Thanks for your contribution, it's much appreciated! My only follow up question is what kind of fields/properties would you associate with a dedicated Meters type? I presume it would just contain a length property? Cheers – JB101UK May 17 '20 at 20:47
  • `MetersSquared` and `MetersCubed` for appropriate properties, I suppose. I figure it's probably fine to hardcore the number of dimensions like that. I suspect you aren't doing n-dimension geometry :) – jdphenix May 17 '20 at 20:51
0

Throw exception on Infinite values is a design choice

IMO, there is no "good answer" for this, it depend on how you want to use your classes. If the aim is drawing, for instance, it probably makes sense to prevent huge values.

For calculations and comparisons, I wouldn't be definitive. The Inf and -Inf (and NaN) values defined for IEEE-standard floating-point values are consistent. Users of the class knows that you're using double so, it's reasonable to expect all the features that goes with them.

Which shape has the biggest area, a square with 10 units side or a circle so large you consider it's infinite? The answer would be consistent since Inf > 100 is true.

If you plan to fail, fail early

If you decide to go on the (reasonable) Exception path, I'd suggest to fail as early as possible.

That means, instead of calculate the area / perimeter in getters, calculate the area and perimeter as as soon as possible (in constructor if your class is immutable, or in setters of the relevant other properties like diameter / length, etc..) and check for any infinite values at that time.

Also, check for NaNs values as well.

class Circle
{
    public Circle(double diameter) 
    {
        if (double.IsInfinity(diameter) || double.IsNaN(diameter))
        {
            throw new ArgumentOutOfRangeException(nameof(diameter));
        }
        Diameter = diameter;

        Perimeter = Math.PI * diameter;
        if (double.IsInfinity(Perimeter))
        {
            throw new OverflowException("Perimeter has overflowed.");
        }
    }

    double Diameter { get; }
    double Perimeter { get; }
}
Pac0
  • 21,465
  • 8
  • 65
  • 74