0

I'm thinking to add a value close to 0, the so-called "epsilon", to the denominator to prevent zero division error, such as:

double EPS = DBL_MIN;
double no_zerodivision_error = 0.0 / (0.0 + EPS);

When setting this epsilon value, are there any general best practices or considerations to prevent future problems?

Also, if I choose between DBL_MIN and DBL_EPSILON, is there a preferred value between the two?

I thought any small numbers would be fine, but I'm afraid I might run into a silent problem in the future that is difficult to spot.

Edit1) In my application, there are many normal cases where the denominator can be zero. That's why I'm not considering throwing an exception.

Edit2) There are some cases such "epsilon" is added to the denominator, such as some deep learning calculations. For example,:

# eps: term added to the denominator to improve numerical stability (default: 1e-8)
torch.optim.Adam(..., eps=1e-08, ...)

There are also SO questions and answers such as this.

starriet
  • 2,565
  • 22
  • 23
  • 1
    There is no general answer. It always depends on the application whether an aproach like this is feasible. – j6t Mar 24 '23 at 08:43
  • I am not sure what you what to achieve with a custom eplison, IEEE already handle this case returning either +Inf/-Inf or NaN https://stackoverflow.com/questions/4745311/c-division-by-0. Are they not suitable for your application? I would imagine that you want to stop a calculation that divide by zero instead of carry on without notification. – Alessandro Teruzzi Mar 24 '23 at 08:48
  • I fail to see what you are trying to accomplish. If you have some divisor `x` which could be 0, couldn't it also happen to have the value `-EPS` (so that adding `EPS` may still result in a division by 0)? It seems more robust to throw an exception (or whatever makes sense) in case the numeric value of the divisor is smaller than some defined tolerance. – nielsen Mar 24 '23 at 09:07
  • The *only* divisor that will cause a "divide by zero" error is zero. Other, small numbers may cause floating-point overflow, but that's a different error ... and the 'limit' will depend on the value of the dividend. For example, if that is equal to (or very near) `DBL_MAX`, then any division by a number < 1 will cause overflow. – Adrian Mole Mar 24 '23 at 09:10
  • In the example you give, where the dividend is actually zero, then no finite divisor will cause an error and the result will always be zero. – Adrian Mole Mar 24 '23 at 09:16
  • @nielsen Oh, you're right, I didn't mention that the divisor is greater than zero (some divisor `x` cannot be `-EPS`). I think your advice is right in general, but in my specific case the divisor can be zero in a normal situation, so I can't throw an exception. – starriet Mar 24 '23 at 11:50
  • Your edit has me more concerned than I was originally. Why would a REALLY wrong answer be preferable to handling the condition? – Steven Fisher Mar 24 '23 at 19:04
  • @StevenFisher You're right, but sometimes it's preferable in practice. You know - the real world is not perfectly rigorous mathematics :) I added an example, please take a look. – starriet Mar 25 '23 at 11:34

2 Answers2

1
#include <limits>
using double_meta=std::numeric_limits<double>;

Now you can simply check whether or not you need to do such tricks:

constexpr double inline zero(double const zr) noexcept {
    if constexpr(double_meta::is_iec559)
        return zr;
    else if (abs(zr)==0.0) 
        return double_meta::denorm_min();
    else
        return zr;
};

If the platform supports iec559, floating division by zero is defined to return properly signed version ofdouble_meta::infinity(). Otherwise, double_meta::denorm_min() is the smallest positive value representable by double and division results in values much higher than double_meta::epsilon() which is much larger (as the smallest positive value whose addition to 1 results in a value greater than 1).

By the way using double::denorm_min() is not exactly the same as none trapping 0.0. the division may result in none-NaN numbers. So the code will be platform-dependent, which is inevitable if floating point division by zero is valid part of the code.

A better approach would be to guard the division itself; but I can't give a specific resultant constant, since the availability of any std constant relies on iec559 compliance(in which case floating point division by zero automatically results in infinty).

Red.Wave
  • 2,790
  • 11
  • 17
  • 1
    Small typo: `double_meta::is_iec559()` should be `double_meta::is_iec559` - [`is_iec559`](https://en.cppreference.com/w/cpp/types/numeric_limits/is_iec559) is a static data member, not a function. There's also a small gotcha even when `is_iec559 == true` - division by zero is still technically [undefined behaviour](https://eel.is/c++draft/expr.mul#4) and undefined behaviour is [not allowed in constant expressions](https://eel.is/c++draft/expr.const#5.8). So its impossible to divide by zero within a constexpr context, even if `is_iec559` is true, e.g.: [godbolt](https://godbolt.org/z/qhxe3aaPz) – Turtlefight Mar 24 '23 at 12:20
  • I didn't see a constexpr requirements in the OP. But I must fix the typo. The OP is using Macros for compile constants. So I will keep the `constexper` tip in the comments. But 1+ for that. – Red.Wave Mar 24 '23 at 17:54
1

Are there any best practices or considerations for setting "epsilon" values ​to avoid zero-division errors?

No, there is no best practice for this.

It is most often a bad idea and should be avoided. In cases where it cannot be avoided, what to do depends on the application and the situation.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312