11

I am doing a bunch of applied-mathematics/signal-processing/algorithms C++ code.

I have enabled the -Wconversion compiler warning to catch issues like runtime conversion of numbers that are type double to type int32_t.

Obviously I am always concerned during these conversions because:

  • loss of the numbers decimal value
  • possible positive or negative overflow ("positive overflow" is when a double has a value greater than INT32_MAX and tries to store that value in the destination type (int32_t in this case))

Whenever I am concerned about such a conversion I usually use the one-liner check:

boost::numeric_cast<DestType>(SourceType)

However I would like to do the same thing without boost.

Does straight C++ have an equivalent to boost::numeric_cast<DestType>(SourceType)?

If straight C++ does not have an equivalent, what would be a comparable non-boost implementation?

I would think a somewhat comparable check would be basically a template function that has a single if statement to check the input parameter against positive or negative overflow (by using the std::numeric_limits<DestType> ::max() and ::min() and throws an exception).

phuclv
  • 37,963
  • 15
  • 156
  • 475
Trevor Boyd Smith
  • 18,164
  • 32
  • 127
  • 177
  • No, there is no numeric cast in Standard Library. As for non-boost implementation, boost is open-source, you can take implementation from boost and use it as is. – SergeyA Apr 04 '18 at 18:43
  • Naturally, if you do take the implementation from boost and use it like @SergeyA suggests, you still need to comply with the open source license that Boost uses. – Justin Apr 04 '18 at 18:45
  • 1
    @SergeyA I checked the Boost implementation, and it's unfortunately not as simple as copying a header file. – Emile Cormier Jan 01 '20 at 02:23
  • That is "straight C++". Presumably you meant _"C++ standard library"_. It is still C++ even if it is in a third-party library. – Clifford Aug 05 '23 at 15:59

2 Answers2

3

As @SergeyA stated, no the standard C++ spec does not currently have an equivalent to Boost's boost::numeric_cast<typename Destination>(Source value).

Here is a straight-forward implementation that uses only standard C++:

template<typename Dst, typename Src>
inline Dst numeric_cast(Src value)
{
    typedef std::numeric_limits<Dst> DstLim;
    typedef std::numeric_limits<Src> SrcLim;

    const bool positive_overflow_possible = DstLim::max() < SrcLim::max();
    const bool negative_overflow_possible =
            SrcLim::is_signed
            or
            (DstLim::lowest() > SrcLim::lowest());

    // unsigned <-- unsigned
    if((not DstLim::is_signed) and (not SrcLim::is_signed)) {
        if(positive_overflow_possible and (value > DstLim::max())) {
            throw std::overflow_error(__PRETTY_FUNCTION__ +
                                      std::string(": positive overflow"));
        }
    }
    // unsigned <-- signed
    else if((not DstLim::is_signed) and SrcLim::is_signed) {
        if(positive_overflow_possible and (value > DstLim::max())) {
            throw std::overflow_error(__PRETTY_FUNCTION__ +
                                      std::string(": positive overflow"));
        }
        else if(negative_overflow_possible and (value < 0)) {
            throw std::overflow_error(__PRETTY_FUNCTION__ +
                                      std::string(": negative overflow"));
        }

    }
    // signed <-- unsigned
    else if(DstLim::is_signed and (not SrcLim::is_signed)) {
        if(positive_overflow_possible and (value > DstLim::max())) {
            throw std::overflow_error(__PRETTY_FUNCTION__ +
                                      std::string(": positive overflow"));
        }
    }
    // signed <-- signed
    else if(DstLim::is_signed and SrcLim::is_signed) {
        if(positive_overflow_possible and (value > DstLim::max())) {
            throw std::overflow_error(__PRETTY_FUNCTION__ +
                                      std::string(": positive overflow"));
        } else if(negative_overflow_possible and (value < DstLim::lowest())) {
            throw std::overflow_error(__PRETTY_FUNCTION__ +
                                      std::string(": negative overflow"));
        }
    }

    // limits have been checked, therefore safe to cast
    return static_cast<Dst>(value);
} 

notes:

  • compiler is g++ version 4.8.5.
  • compiler flags:
    • -std=c++0x
    • -O0
    • -g3
    • -pedantic
    • -pedantic-errors
    • -Wall
    • -Wextra
    • -Werror
    • -Wconversion
    • -c
    • -fmessage-length=0
    • -Wsign-conversion
    • -fPIC
    • -MMD
    • -MP
  • for floating point types you can't use std::numeric_limits<float>::min() but instead must use std::numeric_limits<Dst>::lowest() because ::min returns is 1e-38 instead of a negative floating point value
  • lots of the std::numeric_limits are const expressions and so the compiler will be able to greatly simplify this at compile time (i.e. N if statements will be reduced to one if-statement at compile time or none)
Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
Trevor Boyd Smith
  • 18,164
  • 32
  • 127
  • 177
  • This compiles with -Wconversion in g++ as mentioned, but not in clang. See https://godbolt.org/z/MMaFny – Zitrax Dec 30 '18 at 13:30
  • In addition g++ > 4.8.5 can also warn due to `-Wsign-compare` which is added by `-Wall`. – Zitrax Dec 30 '18 at 14:29
  • Shouldn't `negative_overflow_possible` simply be `std::numeric_limits::lowest() > std::numeric_limits::lowest()`? I don't understand the need for the `std::numeric_limits::is_signed or` part. – Emile Cormier Jan 01 '20 at 02:57
  • I think you meant **and** not **or**, so `negative_overflow_possible = std::numeric_limits::is_signed and ...` I presume the `std::numeric_limits::is_signed and` part is intended for short-circuiting the evaluation of the following `std::numeric_limits::lowest() > std::numeric_limits::lowest()` expression. – Emile Cormier Jan 01 '20 at 03:09
  • Unless I missed something, I also noticed that the four outer if..else if clauses are redundant. The logic can be greatly simplified. – Emile Cormier Jan 01 '20 at 03:52
  • @EmileCormier at the moment i can't verify whether your comments/edits are right or wrong because verifying would take more time that i have at the moment. at the very least i would have to find the code that runs all the test cases. – Trevor Boyd Smith Jan 13 '20 at 17:51
  • @TrevorBoydSmith Your example was still useful as a starting point, and I thank you for it. – Emile Cormier Jan 13 '20 at 18:43
  • Were you able to simplify the code in the end ? – Johan Boulé Jul 07 '21 at 23:02
1

There's no direct analog but you can do the range change easily with In C++20's std::in_range

if constexpr (std::in_range<Dst>(value)) {
    // ...
} else {
    throw std::range_error(__PRETTY_FUNCTION__ ": out of range");
}

There's also std::cmp_* in intcmp to do comparisons yourself. Note that std::in_range and std::cmp_* only allow you to check the range, not whether the value is representable in the target type or not, for example a large uint64_t value like 9'007'199'254'740'993 can be entirely in double's range but not exactly representable by double. You'll need to check that manually if required

if constexpr (std::cmp_greater(val, std::numeric_limits<Dst>::max()))
{
    throw std::overflow_error(__PRETTY_FUNCTION__ ": overflow");
}
else if constexpr (std::cmp_less(val, std::numeric_limits<Dst>::min()))
{
    throw std::underflow_error(__PRETTY_FUNCTION__ ": underflow");
}
else
{
    auto value = static_cast<Dst>(val);
    if (value != val)
        throw std::domain_error("in-range value not representable in Dst");
    else
        // Do something with `value`
}
phuclv
  • 37,963
  • 15
  • 156
  • 475