Why doesn't C have unsigned floats?

102,080

Solution 1

Why C++ doesn't have support for unsigned floats is because there is no equivalent machine code operations for the CPU to execute. So it would be very inefficient to support it.

If C++ did support it, then you would be sometimes using an unsigned float and not realizing that your performance has just been killed. If C++ supported it then every floating point operation would need to be checked to see if it is signed or not. And for programs that do millions of floating point operations, this is not acceptable.

So the question would be why don't hardware implementers support it. And I think the answer to that is that there was no unsigned float standard defined originally. Since languages like to be backwards compatible, even if it were added languages couldn't make use of it. To see the floating point spec you should look at the IEEE standard 754 Floating-Point.

You can get around not having an unsigned floating point type though by creating a unsigned float class that encapsulates a float or double and throws warnings if you try to pass in a negative number. This is less efficient, but probably if you aren't using them intensely you won't care about that slight performance loss.

I definitely see the usefulness of having an unsigned float. But C/C++ tends to chose efficiency that works best for everyone over safety.

Solution 2

There is a significant difference between signed and unsigned integers in C/C++:

value >> shift

signed values leave the top bit unchanged (sign extend), unsigned values clear the top bit.

The reason there is no unsigned float is that you quickly run into all sorts of problems if there are no negative values. Consider this:

float a = 2.0f, b = 10.0f, c;
c = a - b;

What value does c have? -8. But what would that mean in a system without negative numbers. FLOAT_MAX - 8 perhaps? Actually, that doesn't work as FLOAT_MAX - 8 is FLOAT_MAX due to precision effects so things are even more screwy. What if it was part of a more complex expression:

float a = 2.0f, b = 10.0f, c = 20.0f, d = 3.14159f, e;
e = (a - b) / d + c;

This isn't a problem for integers due to the nature of the 2's complement system.

Also consider standard mathematical functions: sin, cos and tan would only work for half their input values, you couldn't find the log of values < 1, you couldn't solve quadratic equations: x = (-b +/- root (b.b - 4.a.c)) / 2.a, and so on. In fact, it probably wouldn't work for any complex function as these tend to be implemented as polynomial approximations which would use negative values somewhere.

So, unsigned floats are pretty useless.

But that doesn't mean to say that a class that range checks float values isn't useful, you may want to clamp values to a given range, for example RGB calculations.

Solution 3

(As an aside, Perl 6 lets you write

subset Nonnegative::Float of Float where { $_ >= 0 };

and then you can use Nonnegative::Float just like you would any other type.)

There's no hardware support for unsigned floating point operations, so C doesn't offer it. C is mostly designed to be "portable assembly", that is, as close to the metal as you can be without being tied down to a specific platform.

[edit]

C is like assembly: what you see is exactly what you get. An implicit "I'll check that this float is nonnegative for you" goes against its design philosophy. If you really want it, you can add assert(x >= 0) or similar, but you have to do that explicitly.

Solution 4

I believe the unsigned int was created because of the need for a larger value margin than the signed int could offer.

A float has a much larger margin, so there was never a 'physical' need for an unsigned float. And as you point out yourself in your question, the additional 1 bit precision is nothing to kill for.

Edit: After reading the answer by Brian R. Bondy, I have to modify my answer: He is definitely right that the underlying CPUs did not have unsigned float operations. However, I maintain my belief that this was a design decision based on the reasons I stated above ;-)

Solution 5

I think Treb is on the right track. It's more important for integers that you have an unsigned corresponding type. Those are the ones that are used in bit-shifting and used in bit-maps. A sign bit just gets into the way. For example, right-shifting a negative value, the resulting value is implementation defined in C++. Doing that with an unsigned integer or overflowing such one has perfectly defined semantics because there is no such bit in the way.

So for integers at least, the need for a separate unsigned type is stronger than just giving warnings. All the above points do not need to be considered for floats. So, there is, i think, no real need for hardware support for them, and C will already don't support them at that point.

Share:
102,080
Nils Pipenbrinck
Author by

Nils Pipenbrinck

nah - won't tell anything about me..

Updated on July 08, 2022

Comments

  • Nils Pipenbrinck
    Nils Pipenbrinck almost 2 years

    I know, the question seems to be strange. Programmers sometimes think too much. Please read on...

    In C I use signed and unsigned integers a lot. I like the fact that the compiler warns me if I do things like assigning a signed integer to an unsigned variable. I get warnings if I compare signed with unsigned integers and much much more.

    I like these warnings. They help me to keep my code correct.

    Why don't we have the same luxury for floats? A square-root will definitely never return a negative number. There are other places as well where a negative float value has no meaning. Perfect candidate for an unsigned float.

    Btw - I'm not really keen about the single extra bit of precision that I could get by removing the sign bit from the floats. I'm super happy with floats as they are right now. I'd just like to mark a float as unsigned sometimes and get the same kind of warnings that I get with integers.

    I'm not aware of any programming language that supports unsigned floating-point numbers.

    Any idea why they don't exist?


    EDIT:

    I know that the x87 FPU has no instructions to deal with unsigned floats. Lets just use the signed float instructions. Misuse (e.g. going below zero) could be considered undefined behaviour in the same way as overflow of signed integers is undefined.

    • Admin
      Admin over 15 years
      Interesting, can you post an example of a case where signedness typechecking was helpful?
    • Admin
      Admin over 15 years
      litb, was your comment directed at me? if so, i dont get it
    • Johannes Schaub - litb
      Johannes Schaub - litb over 15 years
      Iraimbilanja yeah :) fabs can't return a negative number, because it returns the absolute value of its argument
    • Admin
      Admin over 15 years
      Right.i didnt ask how a hypothetical unsignedfloat could help corectness.what i asked was:in what situation did pipenbrinck find Int signedness typechecking helpful(leading him toseek the same mechanism for floats).the reason i ask is that i find unsigneds entirely useless with regards to typesafety
    • Skizz
      Skizz over 15 years
      There is an unsigned micro-optimisation for point-in-range check: ((unsigned)(p-min))<(max-min), which only has one branch, but, as always, it's best to profile to see if it really helps (I mostly used it on 386 cores so I don't know how modern CPUs cope).
    • stephen
      stephen about 15 years
      I think you are asking for some static analysis of your code. Doesn't mean that it has to be implemented by the type system.
    • chux - Reinstate Monica
      chux - Reinstate Monica over 6 years
      Corner: "A square-root will definitely never return a negative number." is a good math truism, yet not necessarily true with sqrt() in C. 1) Many platforms will sqrt(-0.0) --> -0.0 a negative number, in one sense, although not a negative value. 2) sqrt(-1.0) --> implementation defined behavior - which could return -1.0.
    • Moher
      Moher over 6 years
      It really does not make a difference based on what you are asking. because in most cases, there is a run-time error, so the compiler won't catch it, and you have to check for it anyway. really I don't see how that helps with anything. signed floats doesn't make that much of a difference in my opinion.
    • Peter Cordes
      Peter Cordes about 4 years
      @Skizz: modern compilers know that range-check optimization for signed or unsigned integers, and use it when possible because it's great. But it depends on the wrapping behaviour of unsigned integer subtraction overflow and/or a negative 2's complement bit-pattern representing (as unsigned) a large positive value. But FP isn't like that; floating point math saturates (to -Inf). Even if you had hypothetical hardware support for unsigned FP, you couldn't use it for the unsigned-range-check trick if it saturated underflow / negative overflow to 0.0.
    • Peter Cordes
      Peter Cordes about 4 years
      @Skizz: what you could do on modern x86 is two subtractions (subss xmm,xmm) and then XOR the resulting floats together (xorps). Then just check that sign bit with pmovmskb / test. I have in practice used a trick like that for a significant SIMD speedup optimizing checking many points for being inside a polygon with the ray-projection algorithm. Instead of vcmpps ymm against 0.0 and XORing those boolean results, I actually just used _mm256_xor_ps on the FMA results and looked at the sign bit at the end. Only difference is that -0.0 counts as < 0 instead of >=0
  • Nils Pipenbrinck
    Nils Pipenbrinck over 15 years
    Yes, but you call a function with a different name if you work with complex numbers. Also the return type is different. Good point though!
  • David Thornley
    David Thornley over 15 years
    Did the underlying processors have a good way of dealing with signed floating-point numbers? C was getting popular when floating-point auxiliary processors were idiosyncratic and hardly universal.
  • ephemient
    ephemient over 15 years
    svn.perl.org/parrot/trunk/languages/perl6/docs/STATUS says yes, but of ... doesn't parse.
  • quinmars
    quinmars over 15 years
    The result of sqrt(i) is a complex number. And since the complex numbers aren't ordered, you can't say a complex number is negativ (i.e. < 0)
  • Brian Ensink
    Brian Ensink over 15 years
    I don't know all the historical timelines but there was emerging hardware support for signed floats, although rare as you point out. Language designers could incorporate support for it while compiler backends had varying levels of support depending on the targeted architecture.
  • quinmars
    quinmars over 15 years
    Indeed, I was talking about math. I've never dealt with the complex numbers in c.
  • ephemient
    ephemient over 15 years
    The PDP-7, C's first platform, had an optional hardware floating point unit. The PDP-11, C's next platform, had a 32-bit floats in hardware. 80x86 came a generation later, with some technology that was a generation behind.
  • ephemient
    ephemient over 15 years
    Also, addition and subtraction of integers is the same signed or unsigned -- floating point, not so much. Who would do the extra work to support both signed and unsigned floats given the relatively low marginal utility of such a feature?
  • Skizz
    Skizz over 15 years
    C/C++ does not require specific machine code operations to implement the language. Early C/C++ compilers could generate floating point code for the 386 - a CPU with no FPU! The compiler would generate library calls to emulate FPU instructions. Therefore, a ufloat is could be done without CPU support
  • Anthony
    Anthony about 15 years
    Skizz, while that is correct, Brian already addressed this - that because there is no equivalent machine code the performance will be horrible by comparison.
  • Lazer
    Lazer about 14 years
    @Brian R. Bondy: I lost you here: "because there is no equivalent machine code operations for the CPU to execute...". Can you please explain, in simpler terms?
  • Lazer
    Lazer about 14 years
    @Skizz: if representation is a problem, you mean if someone can devise a method to store floats that is as efficient as 2's complement, there will be no problem with having unsigned floats?
  • Dan
    Dan over 12 years
    value >> shift for signed values leave the top bit unchanged (sign extend) Are you sure about that? I thought that was implementation-defined behavior, at least for negative signed values.
  • Skizz
    Skizz over 12 years
    @Dan: Just looked at the recent standard and it does indeed state that it is implementation defined - I guess that's just in case there's a CPU that has no shift right with sign extend instruction.
  • Joe F
    Joe F about 11 years
    The reason OP wanted support for unsigned floats was for warning messages, so really it has nothing to do with the code generation phase of the compiler - only to do with how it does the type checking beforehand - so support for them in machine code is irrelevant and (as has been added to the bottom of the question) normal floating point instructions could be used for actual execution.
  • Rok Kralj
    Rok Kralj almost 9 years
    The point is to save one bit, encapsulation does not help here.
  • chux - Reinstate Monica
    chux - Reinstate Monica over 8 years
    " square-root will definately never return a negative number." --> sqrt(-0.0) often produces -0.0. Of course -0.0 is not a negative value.
  • xanderflood
    xanderflood about 7 years
    I'm not sure I see why this should affect performance. Just like with int's, all the sign-related type-checking could happen at compile time. OP suggests that unsigned float would be implemented as a regular float with compile-time checks to ensure that certain non-meaningful operations are never performed. The resulting machine code and performance could be identical, regardless of whether your floats are signed or not.
  • phuclv
    phuclv about 5 years
  • phuclv
    phuclv about 5 years
    C was introduced long before the IEEE-754 standard appeared
  • Tobias Wärre
    Tobias Wärre about 5 years
    @phuclv Neither were commonplace floating point hardware. It was adopted into standard C "a few" years later. There is probably some documentation floating around the internet about it. (Also, the wikipedia article mentions C99).
  • phuclv
    phuclv about 5 years
    I don't understand what you mean. There's no "hardware" in your answer, and IEEE-754 was born after C, so floating-point types in C can't depend on IEEE-754 standard, unless those types was introduced into C much later
  • Tobias Wärre
    Tobias Wärre about 5 years
    @phuclv C is/was also known as portable assembly, so it can be pretty close to hardware. Languages gains features over the years, even if (before my time) float was implemented in C, it was probably a software based operation and quite expensive. At the time of answering this question, I obviously had a better grasp of what I was trying to explain than I do now. And if you look at the accepted answer you might understand why I mentioned IEE754 standard. What I do not understand is that you nitpicked on a 10 year old answer which isn't the accepted one?
  • Peter Cordes
    Peter Cordes about 4 years
    floating point traditionally saturates (to -/+Inf) instead of wrapping. You might expect unsigned subtraction overflow to saturate to 0.0, or possibly Inf or NaN. Or just be Undefined Behaviour, like the OP suggested in an edit to the question. Re: trig functions: so don't define unsigned-input versions of sin and so on, and make sure to treat their return value as signed. The question wasn't proposing replacing float with unsigned float, just adding unsigned float as a new type.