10

Is there an easy way to determine the sign of a floating point number?

I experimented and came up with this:

#include <iostream>

int main(int argc, char** argv)
{
 union
 {
  float f;
  char c[4];
 };

 f = -0.0f;
 std::cout << (c[3] & 0x10000000) << "\n";

 std::cin.ignore();
 std::cin.get();
 return 0;
}

where (c[3] & 0x10000000) gives a value > 0 for a negative number but I think this requires me to make the assumptions that:

  • The machine's bytes are 8 bits big
  • a float point number is 4 bytes big?
  • the machine's most significant bit is the left-most bit (endianness?)

Please correct me if any of those assumptions are wrong or if I have missed any.

In silico
  • 51,091
  • 10
  • 150
  • 143
Rarge
  • 221
  • 1
  • 4
  • 13
  • 3
    Until we know what system you're on, how can we correct your assumption about whether your machine is big or little endian? – jball Nov 20 '10 at 22:05
  • my assumptions were generalised, sorry if that was not clear. – Rarge Nov 20 '10 at 22:07
  • what's wrong with <0? I know even floating point 0 can have a sign but the simple < operator is simple and will cover almost all of your cases – hhafez Nov 20 '10 at 22:50
  • @hhafez because the only results I am getting is -0 and +0 – Rarge Nov 20 '10 at 22:52
  • oh ok, then do a simple is value == -0.0 if not then use the less than operator simple. I know it is an interesting exercise trying to get the sign bit though, but if all you want to is it <0 or == -0.0 then that is pretty simple then :) – hhafez Nov 20 '10 at 22:56
  • 2
    That is not possible because -0.0f == 0.0f – Rarge Nov 20 '10 at 23:02
  • see my modification to James Answer, I tested it and it distinguishes between 0.0f and -0.0f in a very simple manner – hhafez Nov 20 '10 at 23:25
  • The code given in both your edits does not work on my machine. – Rarge Nov 20 '10 at 23:39

9 Answers9

12

Use signbit() from math.h.

arsenm
  • 2,903
  • 1
  • 23
  • 23
  • 2
    `signbit()` is not part of the current C++ Standard Library. It is part of the proposed C++0x Standard Library (most--all?--of the C99 Standard Library is part of C++0x). – James McNellis Nov 20 '10 at 22:12
  • 2
    @JamesMcNellis Now in C++11, http://en.cppreference.com/w/cpp/numeric/math/signbit – Maxim Egorushkin Jun 14 '18 at 11:02
12

Try

float s = copysign(1, f);

from <math.h>

Another helpful thing may be #including <ieee754.h>, if it's available on your system/compiler.

Vlad
  • 35,022
  • 6
  • 77
  • 199
  • 1
    `copysign()` is not part of the current C++ Standard Library. It is part of the proposed C++0x Standard Library (most--all?--of the C99 Standard Library is part of C++0x). – James McNellis Nov 20 '10 at 22:16
  • @Vlad: That doesn't magically make it part of the current standard. – GManNickG Nov 20 '10 at 22:21
  • @Vlad: Another widely used C++ compiler, Visual C++, does not include `copysign()` in its Standard Library implementation (and it doesn't have to, at least not until some time after C++0x is finished). – James McNellis Nov 20 '10 at 22:22
  • Yes, but using this function, if available, may be helpful to the OP. – Vlad Nov 20 '10 at 22:23
  • 2
    @Vlad: That's just moving the goalpost from "It's definitely usable" to "It might be usable". Please stick to the original argument that @James presented, which was "it's not standard", which you failed to refute. Be honest, accept that, and then move on. Don't just try to push it under the rug. – GManNickG Nov 20 '10 at 22:27
  • 2
    @GMan: that's why I say "try" and not "you can achieve this using". Despite of being non-standard, it's supported by some of the widely used compilers, therefore may be helpful to the OP. We are here to help to solve the problems, not to enforce the standards, right? – Vlad Nov 20 '10 at 22:30
  • I am using Visual C++ and therefore cannot use this. It may be beneficial to see the inner workings of the function though. – Rarge Nov 20 '10 at 22:33
  • 1
    @Vlad: Debatable. I'd rather give standard, clean, well-defined solutions to problems than non-portable solutions and guess-work. – GManNickG Nov 20 '10 at 22:34
  • @Rarge: you might want to have a look at – Vlad Nov 20 '10 at 22:34
  • @GMan: I fully agree with your last comment. However, what to do if a standard-compliant solution is not available? – Vlad Nov 20 '10 at 22:35
  • @Vlad: Then you wrap it up and treat it like it is. This isn't such a case. – GManNickG Nov 20 '10 at 22:39
  • 1
    @GMan: signbit/copysign are standard, clean, and well-defined. They are also not supported by one particular compiler not mentioned before this answer was posted. – Fred Nurk Nov 20 '10 at 22:40
  • @Rarge: sorry, extra `>` in my last link – Vlad Nov 20 '10 at 22:40
  • @GMan: you say it as if there were a better solution presented, allowing to distinguish between `+0.0f` and `-0.0f`. My proposal works correctly at least on current gcc. – Vlad Nov 20 '10 at 22:42
  • 1
    @Fred: Where in the standard? @Vlad: The problem doesn't need the functionality this answer provides, it needs the OP to fix his code. If he **actually** ends up needing this, then I'll write the function. Like you say, let's solve the problem; in this case, the OP is misguided. – GManNickG Nov 20 '10 at 23:10
  • @GMan: I trust the OP saying he actually needs to know the sign. – Vlad Nov 20 '10 at 23:20
  • @Vlad: Ah. Over time you'll learn when people ask questions, more often then not they don't actually know what steps they need to take. (After all, if they did they wouldn't have the problem.) This leads to the guideline, [ask the goal, not the step](http://www.catb.org/esr/faqs/smart-questions.html). This lets the answers tell the questioner the steps they need to take to solve the problem, which may or may not be the steps the asker was thinking. The OP hasn't done so, so I don't trust he needs this step without further argument; and that hasn't been provided. – GManNickG Nov 20 '10 at 23:25
  • That, of course, is supported by your very statement "We are here to help to solve the problems". I say "Exactly, so let's *actually* find out what the problem is, instead of make a fairly base-less assumption the OP knows he needs to take this step." These are, of course, guidelines. Sometimes people know exactly what they need to do, and just don't know how to do it. But that's not something you should assume if you really want to help them. – GManNickG Nov 20 '10 at 23:26
  • 1
    @GMan: Debatable :-) We can as well go ahead and ask if OP really wants to write a program. There should be a line where we stop in our assumptions. Anyway, different assumptions produce different answers, the more is the better. – Vlad Nov 20 '10 at 23:27
  • @Vlad: I didn't say we necessarily need to question everything the OP is trying to do. But it's a good idea to be skeptical about the problem prima facie, and question the rationale behind it. @Fred: Well? – GManNickG Nov 20 '10 at 23:34
  • @GMan: yes, but we could ask for example whether the OP needs dot product at all (I learned from the comments to the other answer that the dot product is what is being calculated), or whether he indeed needs to calculate the direction, or whether the graphics is the appropriate means of representation his information. The border line (that determines what we accept and what not) is vague. Ergo: help on any possible problem depth is good and valuable. – Vlad Nov 20 '10 at 23:39
  • @GMan: If you honestly can't find where they are defined, I might know a good Q&A site you can ask that question. If you're just trolling, well, shame on you, and shame on me for responding. – Fred Nurk Nov 21 '10 at 03:25
  • @Fred: Seriously? What the hell? I ask a simple question and you acuse me of trolling. Do you always do that? No, I'm not trolling. And I'm not going to ask a question for something that you could trivially give an answer to in a minute. So please: where are they defined in the standard? You said they are there, where? – GManNickG Nov 21 '10 at 06:02
  • @Fred: Bump. Please answer the question, I'd like to know where. – GManNickG Nov 22 '10 at 03:07
10

Assuming it's a valid floating point number (and not, for example, NaN):

float f;
bool is_negative = f < 0;

It is left as an exercise to the reader to figure out how to test whether a floating point number is positive.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 18
    This doesn't work in the case where f = -0.0f; – Rarge Nov 20 '10 at 22:05
  • 1
    @Rarge: What are you doing in which that matters? [Ask the goal, not the step.](http://www.catb.org/esr/faqs/smart-questions.html) – GManNickG Nov 20 '10 at 22:07
  • 1
    @Rarge: Why do you care whether a number is positive or negative zero? In any case, the number zero is a single value that has two representations in some floating point formats. This code does test the sign of a number; it does not test for a particular representation of the number, though. – James McNellis Nov 20 '10 at 22:08
  • I am calculating the perp dot product of two vectors to determine which way to rotate an object. When the number is -0 I must rotate one way, when it is 0 I must rotate it another. – Rarge Nov 20 '10 at 22:08
  • @Rarge: are you sure that the number does not get normalized after calculations? – Vlad Nov 20 '10 at 22:09
  • @Vlad Yes, if I print f it gives -0 – Rarge Nov 20 '10 at 22:09
  • 3
    @Rarge: If you're relying on being able to distinguish between `+0` and `-0` after a dot-product calculation, would it be fair to say that you have a highly-unstable algorithm? – Oliver Charlesworth Nov 20 '10 at 22:11
  • @Rarge: Will you be able to notice the difference? Zero is zero, will which way you rotate matter? – GManNickG Nov 20 '10 at 22:11
  • @GMan The direction of rotation is important as one way is shorter than the other – Rarge Nov 20 '10 at 22:13
  • 1
    @Rarge: What? There is no shorter between zero and "negative zero". Like I asked, how would you even notice? They are the **same number**. The direction is arbitrary between 0 and "-0". – GManNickG Nov 20 '10 at 22:18
  • 1
    @GMan I cannot explain the reasons as to why the computer is giving me -0 and +0 from my arithmetic but it gives me -0 when I need to rotate one way and +0 when I need to rotate the other. Whether or not the numbers are the same mathematically does not matter. – Rarge Nov 20 '10 at 22:22
  • 1
    @GMan: To be fair, `-0` can come as the result of some finite negative number that was subsequently rounded toward zero, in which case, there is a semantic difference. Even so, I have trouble imagining how this could be significant in the OP's situation; it's likely that there will be far greater sources of error that will dominate this result. – Oliver Charlesworth Nov 20 '10 at 22:22
  • 1
    I'd kind of like to see this code that computes dot products :-/ This doesn't sound right at all. – James McNellis Nov 20 '10 at 22:24
  • @Rarge: Do you mean the *only* possible results for your calculation are -0 and +0? That just sounds wrong. – GManNickG Nov 20 '10 at 22:25
  • I have a forward vector and a destination vector. I calculate the perp dot product like so: forward.x * dest.y - forward.y * dest.x; When the result is -0 I need to rotate it clockwise when it is +0 I need to rotate it anti-clockwise. – Rarge Nov 20 '10 at 22:31
  • @Rarge: Again: Are the only possible results -0 and +0? In any case, your problem is better solved be storing rotations an angle instead of a vector, and then just interpolating the angle. – GManNickG Nov 20 '10 at 22:39
  • 3
    @Rarge: The only situation where the perpendicular dot product will give 0 is when the two vectors are parallel (or so close as to be all but indistinguishable given the limitations of floating-point maths). It is not reasonable to expect the difference between `+0` and `-0` to be meaningful in this situation. – Oliver Charlesworth Nov 20 '10 at 22:41
  • It is giving +0 and -0 for all angles (with steps of 1 degree) between 0 and 359 degrees. The only explanation is the float point limitations. But it is still giving me -0 when I need to go one way and +0 when I need to go the other. – Rarge Nov 20 '10 at 22:47
  • After doing some experimenting, the point at which the sign switches is where it passes the point at which the two vectors are parallel. – Rarge Nov 20 '10 at 22:54
  • @Rarge: Your algorithm to compute the dot product is broken if it returns zero for any two vectors... – James McNellis Nov 20 '10 at 23:00
  • @Rarge: Then either you have vectors whose component values are close to floating-point epsilon (in which case the result will be close to meaningless), or you have a bug! The result of a dot product should not, in the general case, be zero! – Oliver Charlesworth Nov 20 '10 at 23:02
  • @James: If the vectors are *miniscule*, then the result of the cross-multiplication could underflow. The solution, of course, is to normalise the vectors first. – Oliver Charlesworth Nov 20 '10 at 23:03
  • Swapping the place of the vectors in the sum: e.g. dest.x * forward.y - dest.y * forward.x always gives me 0. Both vectors are normalized. – Rarge Nov 20 '10 at 23:04
  • @Oli: Yes, but the OP said that for all inputs the result is zero, which doesn't make sense at all. (Though, perhaps I misunderstood the OP's comment.) – James McNellis Nov 20 '10 at 23:05
  • 3
    William Kahan's paper ["How Java's Floating Point Hurts Everyone Everywhere"](http://www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf) includes some compelling examples (e.g. from fluid mechanics) that demonstrate why it's important to distinguish between +0.0 and -0.0. In a nutshell, it has to do with complex functions, branch cuts, and identities that fail to hold unless +0.0 and -0.0 are distinguished. – Jim Lewis Nov 20 '10 at 23:07
  • @Rarge: Then something is seriously wrong. You should not be getting 0 for anything other than angles infinitesimally close to zero (within the limitations of floating-point). – Oliver Charlesworth Nov 20 '10 at 23:10
  • @James When I said all values I meant rotating my vector 1 degree then finding the perp dot product, repeating that until the angle between the two vectors is ~0 – Rarge Nov 20 '10 at 23:10
  • 2
    @Jim: In esoteric (and that's not to downplay them) cases, yes, it matters. Taking the dot product of two vectors is not one of these cases. – Oliver Charlesworth Nov 20 '10 at 23:11
  • I understand that there must be a problem with my code somewhere for it to be always producing ±0 and so I will seek a different solution but it is evident that in some cases it is important to be able to distinguish the sign and so I am still seeking an answer to my problem. – Rarge Nov 20 '10 at 23:18
  • @Rarge: Since there is no way to do this in the C++ Standard Library yet (at least, not that I know of, and not that anyone else has demonstrated here), you can consider using functionality provided by your platform, for example, the Visual C++ C Run-Time Library includes [_copysign](http://msdn.microsoft.com/en-us/library/0yafk1hc.aspx). – James McNellis Nov 20 '10 at 23:22
  • @Oli: Agreed -- but, way upthread, the question was asked, "Why do you care whether a number is positive or negative zero?". I thought it was worth mentioning Kahan's take on it, especially in light of his role in the development of the IEEE-754 standard. – Jim Lewis Nov 20 '10 at 23:24
  • How is the condition `(f == -0.0 || f < 0)` different from or better than `(f <= 0.0)`? – Mark Dickinson Nov 20 '10 at 23:26
  • I found the problem with my code for those interested. I was taking the perp dot product between the wrong axis, I wanted it between x and z, not x and y. In my case the y value was always 0 for both vectors so it was effectively doing 0 - 0 the results open one's eyes towards a floating point arithmetic and how 0 - 0 can be ±0 and how trivial errors can be misleading! @James I shall read up on _copysign and it's implementation; Thank you. – Rarge Nov 20 '10 at 23:27
  • 1
    @Mark It is not. -0.0f == 0.0f and so neither answer the original question. – Rarge Nov 20 '10 at 23:31
  • `-0.0` and `0.0` are equal, but they are not representationally equal; you can test whether they are representationally equal by reinterpreting the `float` variables as `char` arrays and using `std::equal` to compare their contents. (I am not sure whether `float f = -0.0;` is required to result in `f` having a value of negative zero, though) – James McNellis Nov 20 '10 at 23:35
  • What's with all the crazy edits by @hhafez? The current answer (involving casting to long) is just plain wrong. – Mark Dickinson Nov 20 '10 at 23:38
  • @Mark: Oh man, thanks for the heads-up; with all the comments to this question showing up on my inbox page, I totally missed the edits :-| I've reverted the unwanted changes. – James McNellis Nov 20 '10 at 23:41
  • 1
    @Jim: Right, I asked that, though I didn't mean to imply that it was never necessary to differentiate between positive and negative zero, I just wanted to know what the OP's use case was for this so I could better address the question (had the OP had a real use for differentiating them, I'd have let someone else answer the question... advanced math isn't my thing :-D). Thanks for the link; I skimmed through it and found it interesting. – James McNellis Nov 21 '10 at 04:05
  • @Mark Dickinson: The condition `(f == -0.0 || f < 0)` is `true` when `f==+0.0`. That wasn't intended either. The fundamental problem is that `f==-f` when `f` is zero. – MSalters Nov 22 '10 at 10:55
  • @MSalters: Yes, that's exactly what I was trying to point out. My comment should have been written a bit more clearly; sorry. :-) (It looked to me at the time of that comment as though James McNellis was proposing the condition (f == -0.0 || f < 0) as a solution; I later realised that he wasn't: there was some rogue editing going on.) – Mark Dickinson Nov 22 '10 at 12:25
  • An example where this distinction matters is in aabb-ray tests. Dividing by the ray direction components will swap your intersected interval depending on +-0 – Eelco Hoogendoorn Jun 07 '17 at 07:40
  • 1
    I see the OP found his bug, but to answer the original question. If the target system doesn't have copysign() or similar, and printf() shows -0.0; I know it is ugly (and possibly non portable) but how about: sprintf(buf, "%f", val); if(buf[0] == '-'); – Don Mclachlan Jan 27 '18 at 20:31
2

1) sizeof(int) has nothing to do with it.

2) assuming CHAR_BIT == 8, yes.

3) we need MSB for that, but endianness affects only byte order, not bit order, so the bit we need to check is c[0]&0x80 for big endianness, or c[3]&0x80 for little, so it would be better to declare union with an uint32_t and checking with 0x80000000.

This trick have sense only for non-special memory operands. Doing it to a float value that is in XMM or x87 register will be slower than direct approach. Also, it doesn't treat the special values like NaN or INF.

ruslik
  • 14,714
  • 1
  • 39
  • 40
1

Coming to this late, but I thought of another approach.

If you know your system uses IEEE754 floating-point format, but not how big the floating-point types are relative to the integer types, you could do something like this:

bool isFloatIEEE754Negative(float f)
{
    float d = f;
    if (sizeof(float)==sizeof(unsigned short int)) {
        return (*(unsigned short int *)(&d) >> (sizeof(unsigned short int)*CHAR_BIT - 1) == 1);
    }
    else if (sizeof(float)==sizeof(unsigned int)) {
        return (*(unsigned int *)(&d) >> (sizeof(unsigned int)*CHAR_BIT - 1) == 1);
    }
    else if (sizeof(float)==sizeof(unsigned long)) {
        return (*(unsigned long *)(&d) >> (sizeof(unsigned long)*CHAR_BIT - 1) == 1);
    }
    else if (sizeof(float)==sizeof(unsigned char)) {
        return (*(unsigned char *)(&d) >> (sizeof(unsigned char)*CHAR_BIT - 1) == 1);
    }
    else if (sizeof(float)==sizeof(unsigned long long)) {
        return (*(unsigned long long *)(&d) >> (sizeof(unsigned long long)*CHAR_BIT - 1) == 1);
    }
    return false; // Should never get here if you've covered all the potential types!
}

Essentially, you treat the bytes in your float as an unsigned integer type, then right-shift all but one of the bits (the sign bit) out of existence. '>>' works regardless of endianness so this bypasses that issue.

If it's possible to determine pre-execution which unsigned integer type is the same length as the floating point type, you could abbreviate this:

#define FLOAT_EQUIV_AS_UINT unsigned int // or whatever it is

bool isFloatIEEE754Negative(float f)
{
    float d = f;
    return (*(FLOAT_EQUIV_AS_UINT *)(&d) >> (sizeof(FLOAT_EQUIV_AS_UINT)*CHAR_BIT - 1) == 1);
}

This worked on my test systems; anyone see any caveats or overlooked 'gotchas'?

mastick
  • 31
  • 3
  • I think float d = f; is redundant due to operator >> is not mutable for its operands, other than that is fine. I have come up to the same solution assuming my float has the same length as unsigned (4 bytes) – Anton Dyachenko Sep 02 '20 at 21:37
1

google the floating point format for your system. Many use IEEE 754 and there is specific sign bit in the data to examine. 1 is negative 0 is positive. Other formats have something similar, and as easy to examine.

Note trying to get the compiler to exactly give you the number you want with a hard coded assignment like f = -0.0F; may not work. has nothing to do with the floating point format but has to do with the parser and the C/C++ library used by the compiler. Generating a minus zero may or may not be that trivial in general.

old_timer
  • 69,149
  • 8
  • 89
  • 168
0

I've got this from http://www.cs.uaf.edu/2008/fall/cs441/lecture/10_07_float.html try this:

/* IEEE floating-point number's bits:  sign  exponent   mantissa */
struct float_bits {
    unsigned int fraction:23; /**< Value is binary 1.fraction ("mantissa") */
    unsigned int exp:8; /**< Value is 2^(exp-127) */
    unsigned int sign:1; /**< 0 for positive, 1 for negative */
};

/* A union is a struct where all the fields *overlap* each other */
union float_dissector {
    float f;
    struct float_bits b;
};

int main() {
    union float_dissector s;
    s.f = 16;
    printf("float %f  sign %u  exp %d  fraction %u",s.f, s.b.sign,((int)s.b.exp - 127),s.b.fraction);
    return 0;
}
quantum
  • 3,672
  • 29
  • 51
Walter
  • 1
0
(int)(x > 0) - (int)(x < 0);
Syscall
  • 19,327
  • 10
  • 37
  • 52
Ted
  • 83
  • 1
  • 5
-1

Why not if (f < 0.0)?

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680