0

I'm working on an importer for LitJson, to import float values from ints, and doubles, and if overflow-checking is enabled, I want to wrap a potential overflow exception in a JsonException with a bit more information about the failure.

Right now my code looks like this, and I don't know if I need to/can check if the context is checked or not:

private static float DoubleFloatImporter(double value) {
    try
    {
        return (float)value;
    }
    catch (Exception ex)
    {
        throw new JsonException("Value is not a valid Single", ex);
    }
}

2 Answers2

1

You may be thinking of checked and unchecked contexts, but these are not relevant for your example, the explicit conversion (cast) from double to float (so from double-precision binary floating point to single-precision).

A finite double value may round to an infinite float value (either float.PositiveInfinity or float.NegativeInfinity).

For example DoubleFloatImporter(1.23e123). As a double, the input 1.23e123 will be represented as a finite value, but when cast to float, the nearest representable value will be interpreted as +∞.

Edit: As I say in comments, something like:

private static float DoubleFloatImporter(double value) {
    var converted = (float)value;
    if (!float.IsFinite(converted))
        throw new JsonException("Converted value would become infinite or not a number");
    return converted;
}

may suit your need.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • the checked/unchecked contexts was what I was thinking of, and I was hoping, that if the 'global context' was checked that, that would throw an exception. –  Jan 07 '23 at 08:45
  • You could always write `return checked((float)value);` to make sure you had checked context just here, but it will not help because there is a way to represent too huge numbers, and that is called `PositiveInfinity`, and that will be used. For a conversion from `int` to `short` it would have been relevant (`short` does not support infinities). – Jeppe Stig Nielsen Jan 07 '23 at 08:47
  • I didn't/don't care about overflow if the context doesn't care, which is why I was hoping the way I has written this would only throw if the context is checked and the value was out of range, though if converting an out of range double to a float results in +/-∞, then that doesn't help and I would have to throw an exception anyways as JSON does not support infinities as numeric values. –  Jan 07 '23 at 08:49
  • What version of .NET? You should do this: `var converted = (float)value; if (!float.IsFinite(converted)) { throw ... } return converted;` – Jeppe Stig Nielsen Jan 07 '23 at 08:57
  • What I am using is .NET Standard 2.1 (I don't have very many options available for my project), but IsFinite is an available method, and so is IsInfinity. –  Jan 07 '23 at 09:02
0

What about something like this:

static float DoubleFloatImporter(double value)
{
    if (double.IsPositiveInfinity(value))
    {
        return float.PositiveInfinity;
    }
    if (double.IsNegativeInfinity(value))
    {
        return float.NegativeInfinity;
    }
    if (value > Single.MaxValue || value < Single.MinValue)
    {
        throw new OverflowException($"'{value}' doesn't fit");
    }

    return (float)value; //Single.CreateChecked(value);
}

Some examples:

using System.Runtime.CompilerServices;

Convert(1.0);
Convert(double.MaxValue);
Convert(double.PositiveInfinity);
Convert(double.NegativeInfinity);
Convert((double)float.MaxValue + 100);
Convert((double)float.MaxValue * 2);
Convert(double.NaN);

static void Convert(double v, [CallerArgumentExpression("v")] string arg1Exp = "?")
{
    try
    {
        var f = DoubleFloatImporter(v);
        Console.WriteLine($"{arg1Exp}  ->  {f}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"{arg1Exp}  ->  {ex.Message}");
    }
}

The output:

1.0  ->  1
double.MaxValue  ->  '1.7976931348623157E+308' doesn't fit
double.PositiveInfinity  ->  ∞
double.NegativeInfinity  ->  -∞
(double)float.MaxValue + 100  ->  3.4028235E+38
(double)float.MaxValue * 2  ->  '6.805646932770577E+38' doesn't fit
double.NaN  ->  NaN
tymtam
  • 31,798
  • 8
  • 86
  • 126
  • Good. It is perhaps interesting that there exist some input values, like `DoubleFloatImporter(3.40282355E+38)`, where your method would throw an exception, but a blind cast would actually round _down_ to `float.MaxValue`, not _up_ to `float.PositiveInfinity`. This is not necessarily something that will make you change your approach. It depend on what is desired for such values. – Jeppe Stig Nielsen Jan 07 '23 at 10:22
  • That could work, though I'd just throw on infinity anyways, seeing as if a JSON Document managed to say it contained an infinity there's already a problem, same with NaN. –  Jan 07 '23 at 10:27
  • Or `return double.IsFinite(value) && (value > float.MaxValue || value < float.MinValue) throw new OverFlowException(...) : (float) value;` – Dmitry Bychenko Jan 07 '23 at 13:21