varantir
varantir

Reputation: 6854

Number type between 0 and 1

Is there a natural way to define a number type between 0 and 1? Essentially one could do everything with double, the problem is that I should define some bound checking in some functions and I had the (probably stupid) idea to outsource the bound checking to a class, something like

class Probability {

    // the value of the Probability
    double val;

    Probability(double val):value(val){
    // freak out if val > 1 or val < 0
    //...
    //
    };

    // operators such as
    Probability operator + (Probability const & a, Probability const & b){
        double result a.val + b.val;
        if ((result > 1) || (result < 0)){
            // freak out
            result = 0
        }
        return result;
    }
    // ...
    //
}

The problem with this approach is probably that it slows down every operation. Is there a faster way of bound checking? I also would like to know how to handle the "freak out" sections in the code above.

Upvotes: 0

Views: 678

Answers (2)

Andrew
Andrew

Reputation: 5352

You can use the class Probability to enforce the bounds, storing a double internally. If you want operations such as P(0.75) + P(0.5) - P(0.6) to work, you can have the operators return a proxy object, which doesn't do the bounds checking. This proxy object would have a conversion operator to Probability, and the Probability constructor would check the bounds. If you only ever use the Probability type directly, and allow temporaries of the form ProbabilityResultProxy, you'll get the desired behaviour.

The example below outlines this approach. Obviously there's quite a lot missing that you'd want in a real implementation, but I wanted to focus on a specific solution, rather than providing a complete class.

Live example:

#include <iostream>

class Probability {
  public:

    Probability(double value) {
        if (value < 0 || value > 1) throw std::runtime_error("Invalid probability");
        value_ = value;
    }

    double value() const { return value_; }

  private:
    double value_;
};

class ProbabilityResultProxy {
  public:
    explicit ProbabilityResultProxy(double p) : value_(p) {}

    double value() const { return value_; }

    operator Probability() {
        return Probability(value_);
    }

  private:
    double value_;
};

ProbabilityResultProxy operator+(const Probability& lhs, const Probability& rhs) {
    return ProbabilityResultProxy(lhs.value() + rhs.value());
}
ProbabilityResultProxy operator+(const ProbabilityResultProxy& lhs, const Probability& rhs) {
    return ProbabilityResultProxy(lhs.value() + rhs.value());
}
ProbabilityResultProxy operator+(const Probability& lhs, const ProbabilityResultProxy& rhs) {
    return ProbabilityResultProxy(lhs.value() + rhs.value());
}
ProbabilityResultProxy operator+(const ProbabilityResultProxy& lhs, const ProbabilityResultProxy& rhs) {
    return ProbabilityResultProxy(lhs.value() + rhs.value());
}

ProbabilityResultProxy operator-(const Probability& lhs, const Probability& rhs) {
    return ProbabilityResultProxy(lhs.value() - rhs.value());
}
ProbabilityResultProxy operator-(const ProbabilityResultProxy& lhs, const Probability& rhs) {
    return ProbabilityResultProxy(lhs.value() - rhs.value());
}
ProbabilityResultProxy operator-(const Probability& lhs, const ProbabilityResultProxy& rhs) {
    return ProbabilityResultProxy(lhs.value() - rhs.value());
}
ProbabilityResultProxy operator-(const ProbabilityResultProxy& lhs, const ProbabilityResultProxy& rhs) {
    return ProbabilityResultProxy(lhs.value() - rhs.value());
}

int main() {
    Probability p1(0.75);
    Probability p2(0.5);
    Probability p3(0.6);

    Probability result = p1 + p2 - p3;
    std::cout << result.value() << "\n";

    try {
        Probability result2 = p1 + p2;
        std::cout << result2.value();
    } catch (const std::runtime_error& e) {
        std::cout << e.what() << "\n";
    }

    return 0;
}

Here, the mathematical operators are defined for each combination of Probability and ProbabilityResultProxy. Each operation returns a proxy object, and the final assignment causes the bounds check to be performed.


If you want, you could make the ProbabilityResultProxy a private member class of Probability, and make the operators friends of Probability. This prevents anyone from instantiating the proxy class directly.

Upvotes: 2

MSalters
MSalters

Reputation: 179991

The "freak out" option is simple: throw std::invalid_argument. It doesn't matter that throwing is a bit slow; you're not getting an answer anyway. The upside is that not throwing is fast as the optimizer can assume the non-exception paths are far more likely.

Performance-wise it might be beneficial to have a IntermediateResult class such that only the final assignment back to Probability is range-checked. This sidesteps the 0.75+0.5-0.6 example.

Upvotes: 1

Related Questions