4

I'm writing a full double to float function for the Arduino (irrelevant, but I couldn't find any "proper" ones) and I do this check:

if (d < 0) {
    d *= -1;
    bin += "-";
}

I know because of floating point imprecisions double equality is finicky. So is it safe to do that? Or should I stick to this (which I use in later parts of my code anyways)

int compareNums(double x, double y) {
    if (abs(x - y) <= EPSILON) {
        return 0;
    } else if (x > y) {
        return 1;
    } else {
        return -1;
    }
}

And a couple quick questions: does it matter if I do d < 0 or d < 0.0?

I'm multiplying a double d by 10 until it has no fractional part, so I do a check similar to d == (int) d. I'm wondering what's a good epsilon to use (I used this here http://msdn.microsoft.com/en-us/library/6x7575x3(v=vs.80).aspx), since I don't want to end up with an infinite loop. According to the article 0.000000119209 is the smallest distinguishable difference for floats or something like that.

Thanks

Raekye
  • 5,081
  • 8
  • 49
  • 74
  • 1
    `bin += "-";` is not valid C... – R.. GitHub STOP HELPING ICE Jul 18 '12 at 03:42
  • I won't matter whether you use `d < 0` or `d < 0.0`. (See: the comments under: [Why does changing 0.1f to 0 slow down performance by 10x?](http://stackoverflow.com/questions/9314534/why-does-changing-0-1f-to-0-slow-down-performance-by-10x)) But I having trouble understanding your last paragraph. Obviously `d == (int)d` won't always hold. – Mysticial Jul 18 '12 at 03:44
  • 1
    @R.. I'm using the Arduino String class (notice capital String) so it works. I would use the C++ string but you can't convert between the two (and I need to print the string using Arduin's Serial). – Raekye Jul 18 '12 at 03:59
  • @Mysticial I'm wondering: How I should compare when a double is essentially an int (no fractional part)? and: Should I pick the smallest possible epsilon? (see link in question). I keep multiplying a double by 10 until it's essentially and integer, so I don't want an infinite loop because of floating point math. Hopefully that clears things up – Raekye Jul 18 '12 at 04:02
  • 3
    If you're using a String **class** (Arduino or otherwise), then you're almost certainly using C++ rather than C, and your question is incorrectly tagged. (Yes, it matters.) – Keith Thompson Jul 18 '12 at 04:02
  • 2
    `d = -d;` is likely to be more efficient than `d *= -1;`; in any case, it's clearer. – Keith Thompson Jul 18 '12 at 04:04
  • @Keiththompson sorry about that, didn't pay much attention to tagging c/c++ since I don't think the code above specifically related to the language, but I'll fix it – Raekye Jul 18 '12 at 04:04
  • @Raeki you could replace `bin += "-";` with `...` and then tag it C and C++ ... but please don't use the C tag exclusively unless the code is valid C. – Jim Balter Jul 18 '12 at 04:16
  • @Raeki Just realized that my first comment was full of typos... (that's what I get for commenting on two questions at once) Okay, you would basically do `abs(d - (int)d) < epsilon`. – Mysticial Jul 18 '12 at 04:17
  • @Mysticial alright thanks for clarification! Any recommened epsilon, or is `FLT_EPSILON` from `` perfectly safe? As I said if there's even a tiny error I'd end up with an infinite loop and that'd be no fun – Raekye Jul 18 '12 at 16:52
  • You definitely want something greater than `FLT_EPSILON`. Since round-off error will usually be larger than that anyways. But it's indeed fuzzy. How large of an epsilon you want will depend on what you're doing. You'll need to experiment. – Mysticial Jul 18 '12 at 16:54
  • @Mysticial Ah... well now I'm thinking I'm using floats (doubles, but in Arduino they're the same) which only have 6-7 digits of precision. In that case I would eventually lose the fractional part after `* 10` 6-7 times right..? – Raekye Jul 18 '12 at 16:56
  • 1
    Yes, that's correct. But because of round-off, it's unlikely to become an integer after multiplying by `*10` several times. It just means that the margin of error will have become greater than an integer. – Mysticial Jul 18 '12 at 16:58
  • @Mysticial sorry not totally getting it... do you mean it'll be could still be stored as "1234566.999999"? And not sure what you mean by "margin of error will have become greater than an integer" - difference will be greater than 1? – Raekye Jul 18 '12 at 17:05
  • 1
    Yes, that's correct. It could become `1234566.999999`. Or it could be `1234567.0000001`. Or even `1234566.1654984`. It all depends on how it's rounded. A `float` only has about 6-7 digits of precision, so if your number is greater than that, then even the 1's place will become uncertain. – Mysticial Jul 18 '12 at 17:09
  • @Mysticial alright thanks. I'm only printing the variable (original is still stored somewhere else) so I'm okay if it's off a bit. How could it get off to `1234566.1654984` though? Surely it's not that big of a difference..? – Raekye Jul 18 '12 at 18:09
  • 2
    I'm just saying that anything after the margin of error is possible. Sure the floating-point representation might restrict you to a subset of possibilities, but in general, expect anything. – Mysticial Jul 18 '12 at 18:34

2 Answers2

2

d < 0 is valid (though I'd prefer to write d < 0.0. In the first case the zero will be "promoted" to double before the comparison.

And comparing double to zero with < or > is perfectly valid, and does not require an "epsilon".

bin += "-"; is nonsensical.

In general comparing floats/doubles with "==" is invalid and should never be done (except for some special cases such as checking for zero or infinity). Some languages do not even allow "==" (or equivalent) between floats.

d == (int) d is more nonsense.

Hot Licks
  • 47,103
  • 17
  • 93
  • 151
  • 1
    Interesting. What languages disallow floating-point equality comparisons? – Keith Thompson Jul 18 '12 at 04:03
  • @abelenky I incorrectly tagged it "c" when I asked. It's also kinda out of context in my question. `d == (int) d is more nonsense.` was suppose to be kinda pseudocode. I'm doing a check to see if a double is essentially equal to its integer counterpart (ie no fractional part), but didn't write out the whole function I used) – Raekye Jul 18 '12 at 04:15
1

See my answer to this question:

How dangerous is it to compare floating point values?

Specifically, the recommendations that you should not be using absolute epsilons and should not be using floating point whatsoever until you've thoroughly read and understood What Every Computer Scientist Should Know About Floating-Point Arithmetic.

As for this specific piece of code in your question, where it seems your goal is to print a textual representation of the number, simply testing < 0 is correct. And it does not matter whether you write 0 or 0.0.

Community
  • 1
  • 1
R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 1
    For the textual representation, `-0.0` means testing `d < 0` is not enough. – Daniel Fischer Jul 18 '12 at 03:47
  • Well only if you care about showing `-0.0` as a distinct output... If you do care, then yes more work is needed, but the bigger issue I was addressing is that using an inexact test to compare for sign before writing out the number is likely to give completely bogus output... – R.. GitHub STOP HELPING ICE Jul 18 '12 at 03:50
  • I read the question (now starting on What Every Computer Scientist Should Know About Floating-Point Arithmetic :) ), which gave me a good epsilon. +1 – Raekye Jul 18 '12 at 04:13