Is it safe to assume floating point is represented using IEEE754 floats in C?
Asked Answered
W

6

25

Floating point is implementation defined in the C. So there isn't any guarantees.

Our code needs to be portable, we are discussing whether or not acceptable to use IEEE754 floats in our protocol. For performance reasons it would be nice if we don't have to convert back and forth between a fixed point format when sending or receiving data.

While I know that there can be differences between platforms and architectures regarding the size of long or wchar_t. But I can't seem to find any specific about the float and double.

What I found so far that the byte order maybe reversed on big endian platforms. While there are platforms without floating point support where a code containing float and double wouldn't even link. Otherwise platforms seem to stick to IEEE754 single and double precision.

So is it safe to assume that floating point is in IEEE754 when available?

EDIT: In response to a comment:

What is your definition of "safe"?

By safe I mean, the bit pattern on one system means the same on the another (after the byte rotation to deal with endianness).

Wolffish answered 12/8, 2015 at 13:44 Comment(10)
For most modern architectures you can assume that. But not all architectures use IEEE-754 Do any real-world CPUs not use IEEE 754?, Exotic architectures the standards committees care aboutCystoid
This assumption is safe if you care about producing code that works on common platforms. It is totally unsafe if you care about to-the-letter programming language standard compliance.Manisa
What is your definition of "safe"? For a life critical assumption, the assumption is not safe: various implementations use binary32 yet to not fully comply with NANs, sub-normals, etc. The FP math operations vary as to there correctness. In the end, you need to present a criteria for "safe", otherwise the answers are as nebulous as the question.Nanananak
@chux By safe I mean, the bit pattern on one system means the same on the another (after the byte rotation to deal with endianness).Wolffish
@Wolffish - what do you mean by portable? Will this code have to run on an Apple II, a Cray 1, ... ?Dita
@Dita This code needs to run on modern hardware: PCs, Macbooks, smartphones, and on wide variety of OSes: Windows 2K or higher, Many flavors of Linuxes, FreeBSD and Solaris.Wolffish
@Wolffish - Sorry to use old examples, I was trying to figure out the range of hardware involved. So modern hardware, but not low end devices (like some blue tooth devices), or high end systems like mainframes. Some of that modern hardware (like ARM processors) may not include native floating point support, so the libraries would be the issue. If most of the systems you plan to work with support IEEE-754, is the conversion overhead an issue for those platforms that don't support it?Dita
What if you used the direct approach by default (assuming IEEE754), but had a conversion algorithm to that also written, possible to be enabled by a compile time switch. For automating the build, you could write a set of tests which tests the binary representations of what your software / protocol has to support. If all tests pass, the direct approach can be used, if a test fails, the build script would compile the conversion algorithm in. So you have portability and retain performance on systems where the tests pass.Hennessy
The fundamental thing to consider is whether a) it is okay if your software only runs on hardware that you can get your hands on, or b) if it is supposed to run even on hardware that you may never get your hands on. If "a" is true, then fine, go with the recommendations below. If "b" is true, forget it: just play it safe and convert everything to 7-bit ascii.Rapid
Detail: IEEE Standard for Floating-Point Arithmetic (IEEE 754) includes both binary and decimal floating point formats. Certainly OP meant the binary IEEE754 float format.Nanananak
T
30

Essentially all architectures in current non-punch-card use, including embedded architectures and exotic signal processing architectures, offer one of two floating point systems:

  • IEEE-754.
  • IEEE-754 except for blah. That is, they mostly implement 754, but cheap out on some of the more expensive and/or fiddly bits.

The most common cheap-outs:

  • Flushing denormals to zero. This invalidates certain sometimes-useful theorems (in particular, the theorem that a-b can be exactly represented if a and b are within a factor of 2), but in practice it's generally not going to be an issue.
  • Failure to recognize inf and NaN as special. These architectures will fail to follow the rules regarding inf and NaN as operands, and may not saturate to inf, instead producing numbers that are larger than FLT_MAX, which will generally be recognized by other architectures as NaN.
  • Proper rounding of division and square root. It's a whole lot easier to guarantee that the result is within 1-3 ulps of the exact result than within 1/2 ulp. A particularly common case is for division to be implemented as reciprocal+multiplication, which loses you one bit of precision.
  • Fewer or no guard digits. This is an unusual cheap-out, but means that other operations can be 1-2 ulps off.

BUUUUT... even those except for blah architectures still use IEEE-754's representation of numbers. Other than byte ordering issues, the bits describing a float or double on architecture A are essentially guaranteed to have the same meaning on architecture B.

So as long as all you care about is the representation of values, you're totally fine. If you care about cross-platform consistency of operations, you may need to do some extra work.

EDIT: As Chux mentions in the comments, a common extra source of inconsistency between platforms is the use of extended precision, such as the x87's 80-bit internal representation. That's the opposite of a cheap-out, and (with proper treatment) fully conforms to both IEEE-754 and the C standard, but it will likewise cause results to differ between architectures, and even between compiler versions and following apparently minor and unrelated code changes. However: a particular x86/x64 executable will NOT produce different results on different processors due to extended precision.

Tibia answered 12/8, 2015 at 18:46 Comment(7)
Are you sure that IBM System Z - the indirect successor of IBM/370 - is using IEEE754?Catchall
@BasileStarynkevitch Well-remembered. :) IBM has indeed carried their crazy-ass base-16 system through to the current day, but only as a backwards-compatibility option. Unless you go out of your way to use it, you'll be using normal base-2 floats.Tibia
Embedded example "cheap-out": rather than inf/NaN, these bit patterns simple represent numbers with a "IEEE754_max_exponent+1" exponent. Also see non IEEE-754 example.Nanananak
@Tibia This answer is the basis for a very good answer. Some other thoughts, use as desired: The 1st 2 points address the FP format (OP's concern), and the later points bring in computation issues of which there are many. Another "cheap-out", though very niched, is +/- 0.0: Some platforms ignore the sign and never generate -0.0. Types of NaN (Signaling and Quiet) often fit the "except for blah" category. Meaning and values of NaN payload is another undefined area. MS use of 64-bit for long double vs MS historic 80-bit use and other's 128-bit use is another incompatible area.Nanananak
This is an excellent answer, I am wondering are there any references out there that cover this even partially? Seems like this applies equally to C++ as well.Calciferous
@ShafikYaghmour Well, Higham's "Accuracy and Stability of Numerical Algorithms" discusses the implications of guard digits and denormal numbers. (And, yes, also hexadecimal FP.) It's honestly a very interesting book.Tibia
@Tibia thank you for mentioning the Higham text. Some other helpful references: Wilkinson's "Rounding Errors in Algebraic Processes" and "The Algebraic Eigenvalue Problem"Selfoperating
O
8

There is a macro to check (since C99):

C11 §6.10.8.3 Conditional feature macros

__STDC_IEC_559__ The integer constant 1, intended to indicate conformance to the specifications in annex F (IEC 60559 floating-point arithmetic).

IEC 60559 (short for ISO/IEC/IEEE 60559) is another name for IEEE-754.

Annex F then establishes the mapping between C floating types and IEEE-754 types:

The C floating types match the IEC 60559 formats as follows:

  • The float type matches the IEC 60559 single format.
  • The double type matches the IEC 60559 double format.
  • The long double type matches an IEC 60559 extended format, 357) else a non-IEC 60559 extended format, else the IEC 60559 double format.
Offer answered 12/8, 2015 at 13:47 Comment(6)
That macro may not always be usefulCalciferous
That macro is not defined in the MSVC9 compiler.Wolffish
@Wolffish Probably because it doesn't support C99.Offer
That macro doesn't always give you the right answer.Manisa
Whether the macro gives the right answer is beside the point. That C provides a macro aimed at testing this feature implies that the standard supposes that it is unsafe to assume a specific answer without testing.Menarche
And C++ has std::numeric_limits<T>::is_iec559: https://mcmap.net/q/25072/-are-ieee-float-and-double-guaranteed-to-be-the-same-size-on-any-osTrelliswork
P
3

I suggest you need to look more carefully at your definition of portable.

I would also suggest your definition of "safe" is insufficient. Even if the binary representation (allowing for endianness) is okay, the operations on variables may behave differently. After all, there are few applications of floating point that don't involve operations on variables.

If you want to support all host architectures that have ever been created then assuming IEEE floating point format is inherently unsafe. You will have to deal with systems that support different formats, systems that don't support floating point at all, systems for which compilers have switches to select floating point behaviours (with some behaviours being associated with non-IEEE formats), CPUs that have an optional co-processor (so floating point support depends on whether an additional chip is installed, but otherwise variants of the CPU are identical), systems that emulate floating point operations in software (some such software emulators are configurable at run time), and systems with buggy or incomplete implementation of floating point (which may or may not be IEEE based).

If you are willing to limit yourself to hardware of post 2000 vintage, then your risk is lower but non-zero. Virtually all CPUs of that vintage support IEEE in some form. However you still (as with older CPUs too) need to consider what floating point operations you wish to have supported, and the trade-offs you are willing to accept to have them. Different CPUs (or software emulation) have less complete implementation of floating point than others, and some are configured by default to not support some features - so it is necessary to change settings to enable some features, which can impact on performance or correctness of your code.

If you need to share floating point values between applications (which may be on different hosts with different features, built with different compilers, etc) then you will need to define a protocol. That protocol might involve IEEE format, but all your applications will need to be able to handle conversion between the protocol and their native representations.

Performance answered 12/12, 2015 at 0:3 Comment(0)
H
2

Almost all common architectures now use IEEE-754, this is not required by the standard. There used to be old non IEE-754 architectures, and some could still be around.

If the only requirement is for exchange of network data, my advice is:

  • if __STDC_IEC_559__ is defined, only use network order for the bytes and assume you do have standard IEE-754 for float and double.
  • if __STDC_IEC_559__ is not defined, use a special interchange format, that could be IEE-754 - one single protocol - or anything else - need a protocol indication.
Handmaiden answered 12/8, 2015 at 14:21 Comment(0)
S
1

Strictly speaking, it's not safe to assume floating-point support; generally speaking, the vast majority of platforms will support it. Notable exceptions include (now deprecated) VMS systems running on Alpha chips

If you have the luxury of runtime checking, consider paranoia, a floating-point vetting tool written by William Kahan.

Edit: sounds like your application is more concerned with binary formats as they pertain to storage and/or serialization. I would suggest narrowing your scope to choosing a third-party library that supports this. You could do worse than Google Protocol Buffers.

Selfoperating answered 7/12, 2015 at 18:43 Comment(0)
C
1

Like others have mentioned, there's the __STDC_IEC_559__ macro, but it isn't very useful because it's only set by compilers that completely implement the respective annex in the C standard. There are compilers that implement only a subset but still have (mostly) usable IEEE floating point support.

If you're only concerned with the binary representation, you should write a feature test that checks the bit patterns of certain floating numbers. Something like:

#include <stdint.h>
#include <stdio.h>

typedef union {
    double   d;
    uint64_t i;
} double_bits;

int main() {
    double_bits b;
    b.d = 2.5;
    if (b.i != UINT64_C(0x4004000000000000)) {
        fprintf(stderr, "Not an IEEE-754 double\n");
        return 1;
    }
    return 0;
}

Check a couple of numbers with different exponents, mantissae, and signs, and you should be on the safe side. Since these tests aren't expensive, you could even run them once at runtime.

Chokecherry answered 7/12, 2015 at 21:57 Comment(4)
The hazard here arises from byte ordering issues. 754 doesn't specify whether floating point numbers are big-endian (with the sign and some of the exponent at the lowest memory address) or little-endian (with the LSB of the mantissa at the lowest memory address). IME, FP ordering matches integer ordering, but if you go down this road you need to make sure you do network byte order conversion going in/out.Tibia
@Tibia Of course you have to care about byte order. The user asking the question mentioned that he's aware of that. The test I posted works regardless of byte order and implicitly verifies that floating point numbers and integers use the same byte order.Chokecherry
@Tibia Floating point and integers have the same endianness aren't they? So I think the test above would pass on both platforms.Wolffish
@Wolffish It's not guaranteed, but I've never seen it otherwise. The risk is not that the test would spuriously fail, though, but that it would pass, but then writing the float to a network socket and reading it on a different OS would produce a different value. As nwellnhof points out, though, the OP is aware of byte-ordering issues.Tibia

© 2022 - 2024 — McMap. All rights reserved.