How does your programming language handle “minus zero” (-0.0)?
lemire.me
lemire.me
I have a suspicion that on a fundamental level, floating point isn't actually good for games or machine learning. They're just used because existing computers are so good at floating point number crunching, especially GPUs.
And computing integers IS faster than computing floats, at least on a CPU.
In the abstract yes. In reality not so much. A lot of what floats are used for in games is vector math (you are often in a 2D/3D world), and vector math in fixed point requires a lot more work in integer math since you constantly need to shift down things in order to avoid overflow. Overflow bugs are a constant problem when doing dot products in fixed point integer math.
Another common operation in vector math is square root (to normalize vectors) Modern CPU's do that very fast, and you can also use clever approximations that use the way floating point's are represented in hardware.
Most of whats done in 32 bit floats in games, needs to be done with 64bit integers to manage these problems if you decide to use integers, and that means that your data grows, more cache misses, and things slow down.
On top of this everything you do in a game needs to be drawn on the GPU so you need to convert everything to floats anyway to show them on screen, and converting between integers and floats is also slow.
If you try to do with Fixed Point Arithmetic what was intended to be done with Floating Point, you're the problem.
A 32 bits integer can hold values up to 4 billions, if I have that kind of value in a simple game, then yes i will switch to Floating Point Arithmetic, but when does that use case happen if you're not writing a physic simulation game ?
> some game engines
and
> simple games
A 3D game is not a simple game.
> the approximation is generally good enough for a game
No, it's not generally good enough for a game – only for some games, and not good enough for any game with 3D graphics.
All the time. Let me give you an example from a game I made. During early R&D I used 32 bit numbers and a fixed point of 11 bits. That means that a normalized vector is between 1024 and -1023. You can do 2 dot products between vectors before it breaks it breaks. (10 + 10 + 10 bits plus one sign bit). That means that you have to do a lot of shifting down. the world can only be 20 bits large, because you need to be able to multiply world coordinates with vectors without getting overflow.
20 bis is very low resolution for a real-time world. You get problems with things not being able to move slow enough at high frame rates. (this was in 2D). Switching to 64 bit was the right move.
Using fixed point with 11 bits after the decimal, with numbers as low as 100 (18 bits) we already have errors as large as 100·(1/100)=0.977.
This goes so far, that Intel added an AVX512 instruction to expose this 52/53 bit multiplier for integer operations, to accelerate bigint cryptography calculations like RSA and ECC.
A separate, complicating factor is that floating point math often don't yield the same results with different levels of optimization turned on. The reason to use integer math for games is because you want it to be deterministic so can be an issue.
> All conforming implementations of this standard shall provide the operations listed in this clause for all supported arithmetic formats, except as stated below. Each of the computational operations that return a numeric result specified by this standard shall be performed as if it first produced an intermediate result correct to infinite precision and with unbounded range, and then rounded that intermediate result, if necessary, to fit in the destination’s format (see 4 and 7).
If you only use those (maybe including division, I'm not sure), and don't rely on hardware support for sqrt, sin/cos/tan, approximate-inverse, etc., you can totally use them deterministically between different architectures.
Basically you use the platform's native types (e.g. uint32) and decide which of the bits are for the integer part and which are for the fractional. uint32 can be interpreted as 20/12 for example. Your register went from representing "units" to representing "2^-12 increments of an unit". The ALU can do sum/comparison/subtractions transparently, but multiplications and (god forbid) divisions require shifting to return to the original representation.
The choice of how to split the bits is a tradeoff between range and precision. It can vary from one routine to another. It's a pain to compose code with it.
Short story: native float types are a blessing for programmer productivity and library interoperability.
1111.1111 is (1 + 2 + 4 + 8) + (1/2 + 1/4 + 1/8 + 1/16)
https://www.youtube.com/watch?v=VLTqIXZ1jQQ#t=15m00s
I never had a PS1 but it always bugged me when playing on someone elses.
Conversely I like the fact a lot that it does. And some others that probably have fond memories of PS1 too recreated this on modern computers using shaders.
Early PS1 games fit the entire world in to a 24 bit space, and you can tell that there is steping if you drive very slow in a game like Ridge Racer. Later games moved the world around in the coordinate system to manage precision.
Another reason why the graphics looks extra "popy" on PS1 (And early DirectX games) is that vertices where computed at a pixel accuracy. OpenGL, Doom, Quake and more modern hardware have sub pixel accuracy.
We made a simple 3d demo for uni using fixed math (and we were clueless then and weren't using macros because sometimes you could refactor some operations out that way and we wanted all the speed). There were lots of bugs caused by this.
Can't imagine how painful writing a whole game this way would be.
Similarly, fixed-width integers have a totally different algebra than true integers, and yet they're immensely useful.
Floating-point numbers behave like a physicist would do calculations rounding at every step, but doing everything in binary. Or hexadecimal if you prefer, it's equivalent, but still difficult to get your head around to. Then there additionally is + and - 0 and + and - infinity, and several settings for how rounding is done exactly.
This is incorrect. To count as an approximation, there have to be some accuracy bounds. This is impossible to define, as the reals don't have the same cardinality as any floating point number system.
Now, for many interesting and useful real-valued functions, a float-valued function can be defined that is an approximation. But there is no general way to map a function defined on the reals to a function defined on the floats with a known accuracy.
There's a bunch of work on discretization of models after you've done optimizing them - that works, you can get high-performance inference with low-bit fixed point numbers; but that's after the learning has been done using proper floating point (however, you don't necessarily need high accuracy floating point, e.g. single precision may work betted than double precision simply because it's twice less bytes to copy over).
That's the reason why they introduced https://en.wikipedia.org/wiki/Bfloat16_floating-point_format
It's similar to normal half precision float but the exponent is the same as with 32bit float and mantissa has been sacrificed to get room for it.
The most beautiful FP bug I remember was a denial of service in some webservers where by setting the header "Accepted Language: en-gb;q=0.3 en;q=0.8 ..." to a specific value you could send the official Java floating-point parser in an infinite loop (and this affected several Java webservers).
So at each webpage request, you were sending one CPU core of the webserver basically busy-looping. Hence crashing the webserver in a few requests at most.
Now it'd be heresy if I were to say that maybe, just maybe, had the standards mandated to use 0 to 100 for weighting instead of 0 to 1.0 we could have dodged a whole source of potential issues!?
No, no, I realize this is heresy: let's all keep using numbers that cannot even be represented correctly (except as strings), parse those strings into something approximately representing what's written in the string, then make more approximation errors while doing computation with these numbers, propagation errors, epsilon estimation errors, and keep insisting we all could have authored "What every computer scientist should known about floating-point numbers" (which is 80 pages long and, well, approaching a treaty) ; )
/rant off
Maybe using a float is overkill in this case, since you're never going to hit more than a hundred languages. But it's at least not setting any limitations.
NaNs/infs are particularly vicious, as they propagate through computations.
-- IEEE754/64-bits: Interfaces.IEEE_Float_64
-- And here we strip out non-numeric values.
Use Interfaces;
Subtype Numeric_Real is IEEE_Float_64 range IEEE_Float_64'Range;
Done.I also would invite you to find a similar instance where a parsing bug for integers crashes the server. Not throws an exception, but falls over in an uncatchable way. I'm not sure I have ever seen one.
qvalue = ( "0" [ "." 0*3DIGIT ] )
| ( "1" [ "." 0*3("0") ] )
(https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3....)There are 1001 valid values with 1117 representations. Only in practice, Firefox clamps to 2 decimal places, and it used to clamp to 1 decimal place (https://bugzilla.mozilla.org/show_bug.cgi?id=672448), and who knows what other software does.
And it gets worse: the grandparent suggests there are servers that parse it as floating point. Do they accept q=1e2? If they accept it, are there clients that send it? Do you need to be compatible with those clients?
I absolutely blame the protocol. Fixed point isn't widely available and used, but integers are. Someone below says protocol spec goes to three digits past the decimal. Using a constrained integer would have allowed the same range of values, but without the complexity of decimal numbers; that's a protocol mistake.
In addition to the ease of using floating point numbers to incorrectly represent decimals, there's also fun with formatting, comma vs period, number of digits to report, etc.
If the protocol had nothing to do with it, I don't think you'd see the same bug come up in as many implementations
I think a protocol that used integers instead of decimal point would have led to more correct implementations. So it would be a better protocol.
I'm fascinated by how common it is for programmers to hate floats. Yes, if you write business software for a living you may not have much use for them. But there's a lot more to computing than business software.
Not necessarily true. Theoretically you can replace floats by fixed-point representations given enough bits, and in practice this also works often.
It's a pity languages/hardware don't have good built-in support for fixed-point numbers though.
What did you gain from this, exactly?
I'm sure there are some special situations where fixed point is better, but for general purpose scientific and technical computing, floating point is obviously the right choice.
On modern CPUs, fixed point is slower and error prone (both in usage and the computations themselves) relative to floating point.
I don't think it's a pity that fixed point support is out the door. It sucks. Floats minus subnormals are the least surprising numerical representation out there, and the most versatile.
Do you really want to worry about whether your numbers are saturating or wrapping around? Keeping track of what your max/min are? Tracking decimal places? Implementing division by hand? Screw all that. Fixed point is awful and is essentially an academic exercise today.
Hate floats, love big decimals
Hate OOP, love functional
Hate RDBMS, love nosql
Hate html web pages, love SPA
In my opinion it's a confluence of influences.
1) the same social media attitudes poisoning society generally. "I've got an opinion, and it's worth just as much as yours". News flash - opinions are like arseholes, everyone has one.
2) Inexperience - devs who have only ever built chat apps and don't understand why relationships hence RDBMS are common and useful in line of business applications. Devs who have not tried to capture the water level in a tank or the voltage from a solar panel and don't get why floats are useful.
3) resume padding. nuff said
Although to go down that road, schemaless databases preceded RDBMS by decades, and then were replaced for good reasons.
Don't forget control systems.
From Microsoft BASIC to Javascript, many programmers have worked in languages that only have floats. Financial calculations involve exponential/log math much like scientific problems. There are issues with rounding and representation there (e.g. there is no such thing as $0.01 in floating point, only $0.50, $0.25, $0.125 and some sum of 1/2 fractions that comes very close to $0.01 and even reads in and writes out as $0.01 even though it isn't.)
That got fixed, but it seems a variation with signed NaN recently got fixed[1] in .Net Core (taking care to handle all the possible NaN values).
Floats are deceptively easy to use. Which is nice but kinda sucks too, given all the footguns they bring to the party.
I never thought about that (I avoid FP as much as I can) but, oh boy, the can of worms!
.Net (or Java) OOP where an object's hashcode needs to give the same (or not?) value for 0.0 or -0.0: this is the kind of stuff nightmares are made off.
I'm sure this can be turned into some very funny "Java puzzler".
Of course since NaN != x, whatever x is (including infinity and NaN), one could argue it's fine for different NaNs to return different hash codes. But I think most people think of NaN as one thing, and not the 9007199254740990 or so different NaN encodings[1] there are in a double.
// 0.0d / 0 equals 0x7ff8000000000000 (NaN)
// Math.sqrt(-1) equals 0xfff8000000000000 (NaN)
// 0x0p+0d is a funky way to specify 0x0000000000000000 (0)
// -0x0p+0d is a funky way to specify 0x8000000000000000 (-0)
// without 0xHEXp+NUMd my compiller optimizes "-0" literal to 0
// 0 == -0
0x0p+0d == -0x0p+0d
// hashCodes for 0 and -0 are different
Double.hashCode(0x0p+0d) != Double.hashCode(-0x0p+0d)
// hashCodes for different NaNs collapse to the same value
Double.hashCode(0.0d / 0) == Double.hashCode(Math.sqrt(-1))I can't find the original complaints about this (was back in early 2000s after all), but as I recall it someone was very surprised that something that compares as equality ended up two times in the hashmap, and that messed up their code bad.
(Edit: no idea whether this applies to the Apple watch, it's just a use case for -0 with regards to temperature.)
Air temperature is commonly measured at 2m above ground. An measurement of 0° air temperature does not imply 0° ground temperature. The more significant effect is that the ground has a higher thermal capacity than the air, so it changes temperature more slowly throughout the day. If it's been colder before and now it's 0°, the ground is still way below 0° and thus puddles are frozen. If it was warmer and now the air has cooled down to 0°, the ground is going to be a bit warmer still and thus puddles are liquid.
Denoting this difference as +0° and -0° does not seem very useful since the same effect is going to be nearly equally significant at 1° or -2°.
(Sidenote: The thermal capacity of the ground is also the reason why air temperature is not measured anywhere near the ground.)
$ printf '%.0f°\n' 0.3
0°
$ printf '%.0f°\n' -0.3
-0°If you've got two numbers, +0.0000001 and -0.0000001, but you can't represent that precision, can you see how it's less bad to round to +0.0000 and -0.0000 rather than to just 0.0000? It's encoding strictly more information.
There is the IEEE 754, and there is the language's standard. One should always look at the latter because it's often the case that the language doesn't fully conform to IEEE 754.
> According to the IEEE 754 standard, negative zero and positive zero should compare as equal with the usual (numerical) comparison operators, like the == operators of C and Java. In those languages, special programming tricks may be needed to distinguish the two values
This is very common advice, so common that it gets cargo-culted into situations where it is really quite poor.
Information storage, retrieval, and transmission systems should faithfully deliver floating-point values that are good to the last bit. Round-trips through databases, transmission over network protocols, etc should all give values back that are exactly identical to what was put into them.
Keep in mind that the ISO standard does require that floating point equality tests return false for values that have the exact same binary representation, and that not everything adheres to it and some environments may give you false for identical values even when the standard says it should be true. Also, != is not the negation of == for floating point. So even using those operators to test a round trip over those applications is iffy.
Many times I've tracked down the place in our stack where a double-precision value from user input accidentally goes through a single-precision variable in some C code somewhere and crashes some Python code later on because the values don't match "to the last bit" in the way that the programmer thought... But that's a bug in the C code - I agree completely the the system SHOULD give the value back that was put into it!
I've worked with highly experienced and accomplished software engineers that expected interchange through protobuf or sql to be inaccurate due to rounding. No! If you stick a finite number in, you should get the exact same finite number back out again. Direct equality is fine for most cases. The sign bit of zero and NaN should also be returned faithfully and tested using memcmp when required.
IMO, the payload bits of NaN should also be also returned faithfully, but too many systems in common practice drop them.
Yes that's true... but what's that got to do with using an equality operator?
Really good point. My approach to this was "if it’s not used in any mathematical algorithms, why do we need it in computers?". But in your example, you retain some information even though you can’t represent the whole truth. Thanks!
What just clicked for me was that in any system where we use finite precision to store exponents, we can't actually have zero as a normal value... we'll always underflow the exponent before we get to actual mathematical zero.
So +0/-0 is actually a convenient misnomer. They really are +/- epsilon.
The only way to have zero is if it's some specially handled value like NaN. Which IEEE doesn't do and that's entirely understandable.
Makes sense why you can never compare a subtraction of floats to zero (it's beyond just "rounding errors") and the existence of +0/-0 seems quite natural now.
Wait what? Am I missing something? 0 is absolutely part of the IEEE 754 spec thanks to the existence of denormalized floating point numbers. So I would certainly call it a "specially handled value", in a sense. The existence of +/- 0 has more to do with the implementation of the leading sign bit.
To give you an idea, the C99 standard does not require signed zeros, and if you have them, does not dictate the behavior you are describing. I once worked on a commercial C compiler and we produced a new version that resulted in a change of sign of zero for the exact same computation (and even "worse", would give a different sign on different machines for the same compiler version). We discussed this thoroughly and decided it was OK because our docs made it clear we conform to C99 (which provides no guarantees on signed zero).
> Does x*0 evaluate to 0.0
No, but it will compare equal, unless x is either infinite or a NaN.
> Does x+0 evaluate to x? Maybe!
Yes, unless x is either infinite or a NaN.
If you have a function that can experience underflow it can be useful to preserve the sign of the underflowing value.
Otherwise you'd need more checks to get that information.
As a sibling comment wrote, rounding numbers to -0 and +0 can provide extra information, though it may not be useful in all contexts.
You can end up with zeros and infinities pretty easily because you overflow or underflow the range of floating point precision, and generally you want something sensible to happen in typical cases, even if some common arithmetic identities necessarily break down.
I would actually like a true signed zero (or rather "epsilon" value ), so -0 and +0 as distinct from "normal" 0 which is truly unsigned, neither positive nor negative. The former two would only arise from underflow, and the reason this is useful that if you underflow from below zero and invert that you want to get -∞ and if you underflow from above zero and invert that you want to get +∞. Inverting a signless zero should give NaN (instead it gives +∞, which is nonsense in basically any case where the domain is not inherently the non-negative reals already and the 0 did not come about by and underflow; in particular 1/0 should be NaN).
If anyone knows why this design was not chosen and what fundamental downsides it has, I'd love to hear it. Obviously representing three zeros is a tad more annoying, but IEEE754 has a lot of stuff that's annoying implementation wise but was added for nicer numerical behavior (e.g. denormals, and of course various "global" rounding modes etc. which probably qualify as a mistake in retrospect).
While it wasn't immensely useful going forward, it helped to clarify the use of infinity and infinitesimals in earlier work.
Same with the extended real line.
Infinity is as much a number as it is useful to define it as such.
https://en.wikipedia.org/wiki/Extended_real_number_line
NB this is hardly nonstandard analysis.
Like many notational shortcuts, it's a hack supported by proof. ;-)
y = lim_{x->\inf} x
Basically, $y$ isn’t necessarily infinity, but just a number larger than you could ever write. The number at the end of the number line if it existed.Now I was merely an undergrad math major, which means I topped out before learning this stuff in a formal way. But at my primitive level of understanding, I think of a number as something that behaves like a number within a given system. What I learned in my courses was how different kinds of numbers behaved: Whole numbers, reals, complex, vectors and tensors, etc. I remember reading a definition of "tensor" that was to the effect of: A tensor is something that behaves like a tensor, meaning that the important thing is the behavior.
Another post in this page expressed that we should be particularly cautious when dealing with numbers, symbols, and IEEE floats, notably to beware that IEEE floats and real numbers don't always behave the same. That was treated in one of my math courses, "Numerical Analysis." You could also get CS credit for that course, suggesting its practical importance.
And you can usefully say things like "x is a number" without saying which particular number x is.
I get that not all have simple algebraic concepts.
I get the impression I'm corrupting parts of I am a Strange Loop? Would love to read more on this.
I also included .9_ as it is an easy trap to show we have two ways of writing 1 in standard decimal notation.
Please read this whole post as a question. I'm genuinely not clear on the distinction.
According to the finitists, this is a defining feature of a "number". Since the same can't be done for irrational numbers finitists conclude that irrational "numbers" aren't numbers. You probably agree that all numbers are (or can be represented by) symbols, but that not all symbols are numbers. So how do we distinguish symbols from numbers?
How do inequalities work for posit infinity? Is posit infinity both larger and smaller than any other posit?
No fundamental ones, but a few practical.
32-bit numbers have 2^32 unique values, the number is even. Your approach makes the range asymmetrical like it happens with integers.
The range for 8-bit signed integers is [ -128 .. +127 ]. On ARM NEON there’re two versions of integer negate and absolute instructions, some (like vqnegq_s8 or vqabsq_s8) do saturation i.e. transform -128 into +127, others (vnegq_s8, vabsq_s8) don’t change -128. Neither of them is particularly good: the saturated version violates -(-x) == x, non-saturated version violates abs(x)>=0. Same applies to the rest of the signed integers (16, 32, 64 bits), an all modern platforms.
With IEEE floats the range is symmetrical and none of that is needed. Moreover, PCs don’t have absolute or negate instructions, but they instead have bitwise instructions processing floats, like andps, orps, xorps, andnps, they allow to flip, clear or set just the sign bit, very fast.
Another useful property of IEEE representation is that for two intervals [ 0 .. FLT_MAX ] and [-FLT_MAX .. -0.0f ] sort order of floats corresponds to [inverted] sort order of 32-bit integers.
Not necessarily since you've got (a lot of different) NaNs anyway. For the sake of argument, you could give up one one of them and make it positive zero (since this representation would be unnatural, it would slow stuff down, just as non-finite values do on many CPUs. Wouldn't matter that much since signed zeros would only arise from underflow).
> Another useful property of IEEE representation is that for two intervals [ 0 .. FLT_MAX ] and [-FLT_MAX .. -0.0f ] sort order of floats corresponds to [inverted] sort order of 32-bit integers.
I'm aware, but as you correctly note this only works in the right direction for unsigned values. And it's just not that important a benefit, I'd much rather have my calculations come out right than being able to sort positive floating point numbers with an integer sort routine.
What do you expect to happen when you set the sign bit of that number? A possible answer to that is "negative zero", and now you have 2 separate encodings for negative zeroes: one of them with exponent 0, another one 0xFF, and they behave slightly differently.
Possible to do in 2 instruction, xorps to make zero, then subps to subtract. Combined, they gonna take 4-5 cycles of latency (xorps is 1 cycle, subps is 3 cycles on AMD, 4 cycles on Intel).
If you do that a lot, a single xorps with a magic number -0.0f gonna negate these floats 4-5 times faster. People don’t pay me because I’m a normal person, they do that because I write fast code for them :-)
On a serious note, I’d rather have the current +0.0 and -0.0 IEEE values to be equal and be the exact zero, and make another one with 0xFF exponent encoding inexact zeroes, +0.0f or -0.0f depending on the sign bit.
Or another option, redefine FLT_MIN to be 2.8E-45, and reuse the current FLT_MIN, which is 1.4E-45 / 0x00000001 bit pattern, as inexact zeroes.
So whether or not there's a -0 or +0 in some "math" isn't relevant, because computers aren't using "some math" but very specific mathematical constructs that must be understood on their own terms. I haven't seen very many mathematical systems that have a reified "NaN" object that can be explicitly passed around as a valid value to functions. (I know of many systems that have a "bottom" but I would say bottom is usually presented not as an object you can "have" but as a property of some mathematical object. Haskell for instance uses this idea; there are some trivial ways to have or produce something that has the property of being "bottom", like calling "error", but you can't just "have the bottom value".)
Moreover, there are mathematical systems in which such things can appear. There are many exotic and interesting number systems in the mathematical world, which even a bachelor's degree in mathematics may only scratch the surface of, depending on which junior/senior level courses you take. Real numbers are without a doubt the most studied, and they do not contain a -0 (though even then you'll still see it show up sometimes as a special notation in limits), but they are the beginning of number systems, not the end.
I mention all this because it's important; it's a very common misconception that computers use the numbers like you learned in school, and it will lead to nothing but pain. The next misconception is that, ok, sure, they aren't those numbers exactly, but they're so close that I don't have to worry about it. This works as long as you don't push your floats too hard, and a lot of us don't, but also breaks down surprisingly quickly. It's important for programming professionals to understand in general that IEEE floats are their own thing. Whenever I use them I do at least take a couple of seconds to double-check mentally that the sharp pointy bits aren't going to stab me, even for simple things like adding durations of a request to some floating point accumulator for metric purposes.
So strictly speaking this number system doesn't have a representation of zero at all. Tiny measurements either round up or round down to +/- 1.
Take 2d laminar flow around a circle. The flow field splits right in the middle.
Signed zeros ensure that, along with graceful underflow, the local solution is not wonky.
Lots of complex-arithmetic examples too.
https://gis.stackexchange.com/questions/211796/if-degrees-is...
I made the above post due to a bug in the GPS on our routers. We had a customer close to the meridian in England.
http://www.thegreenwichmeridian.org/tgm/articles.php?article...
Western hemisphere is negative longitude, eastern hemisphere is positive. If your degrees are 0 (within ~100km? I can't remember exactly), then you still need to know if you're east or west of the meridian.
Maybe when you're implementing floating point numbers in a way that's simple and widely applicable?
I'm only half joking, too, though I can't tell you what exactly having a distinct sign bit simplifies.
I can say off the bat that it is probably useful that a very small quantity that might otherwise lose enough precision to round to zero maintains its sign regardless.
php > $minus_zero = -0.0;
php > $plus_zero = +0.0;
php > var_dump(1.0 / $minus_zero);
PHP Warning: Uncaught DivisionByZeroError: Division by zero in php shell code:1The result of that division is a float with a value of -INF, INF being a constant that PHP treats as infinite.
But as of PHP 8 dividing by zero causes a fatal error and execution is halted.
This site is really good for comparing results in different PHP versions: https://3v4l.org/9Tl1I
I actually wasn't aware that PHP had an INF contant but seeing the warning in your output prompted me to dig a little bit deeper :)
"""Numeric constants represent exact values of arbitrary precision and do not overflow. Consequently, there are no constants denoting the IEEE-754 negative zero, infinity, and not-a-number values."""
minus_zero = -0.0
plus_zero = +0.0
parsed = float("-0.0")
print(1/minus_zero)
print(1/plus_zero)
print(1/parsed)
ZeroDivisionError Traceback (most recent call last)
<ipython-input-1-747f1b708c86> in <module>()
2 plus_zero = +0.0
3 parsed = float("-0.0")
----> 4 print(1/minus_zero)
5 print(1/plus_zero)
6 print(1/parsed)
ZeroDivisionError: float division by zero
edit: FormattingIt didn't die quickly though. The UNIVAC is still with us (in emulation) and that is likely the reason why the C standard addresses the question of negative zero integers. (Their handling is implementation specific, of course.)
>> 1 / -0.0
=> -Infinity
Although you have to put the 0.0, otherwise it treats it as an integer and makes it 0.> Depending on the programming environment and the type of number (e.g. floating point, integer) being divided by zero, it may generate positive or negative infinity by the IEEE 754 floating point standard.
E.g. you should hardly ever test floating point numbers for equality. Instead you usually check if they are "close enough" within an expected precision.
I prefer to store all my most important data in the sign bit of floats set to zero.
Isn’t that what bool is for?
How is the result of:
a = 0;
different from:
a = -0;
:-)
iex(1)> -0.0 === 0.0
true
there's also explicitly no infinity or NaN, there is a software throw for all (core) implemented function domain failures.This has, however, recently come up for Nx (numerical elixir) which had to implement and standardize ways to shim these IEEE concepts back into the VM for interop purposes.
0 !== 0.0
Of course 0 == 0.0
I imagine if they had chosen to follow IEEE, it would have been that 0.0 !== -0.0 and 0.0 == 0.0.
Is there a language that treats different float values of zero as unequal? That would be surprising to me. And possibly silly.
In Julia 0.0 == -0.0 but 0.0 === -0.0 evaluates to false.
Note that -0.0 and 0.0 are different at the binary representation level, which is what === checks for.
This would have been an excellent place to use Ada. The package "Interfaces" has types exactly for IEEE Floats: `Interfaces.IEEE_Float_64`.
You could then get rid of non-numeric representations (raising `Constraint_Error` instead) via the following subtype-definition:
subtype Float is Interfaces.IEEE_Float_64 range Interfaces.IEEE_Float_64'Range;
(I've thought about writing an Erlang in Ada, hoping to tie together Erlang's actors w/ Ada's Task where possible [ie focus on interop], but haven't found any Erlang language definition that's anywhere near recent.)* Do they compare equal?
* If they're convertible to boolean, do they both evaluate to false?
* If you serialize/deserialize the value (e.g., to JSON and back), does it maintain the distinction?
* If you send this value to a database (esp. one with stored procedures), how does its behavior compare to your language?
Its bitwise representation is different, but when compared as floats they are equal.
Therefore; -0.0 + 2.0 = 2.0 and 0.0 + 2.0 = 2.0
>>> 0.0+0.0
0.0
>>> 0.0+(-0.0)
0.0
>>> (-0.0)+0.0
0.0
>>> (-0.0)+(-0.0)
-0.0Hmm? Your examples show -0.0 being additively neutral in every case. +0.0 is the one that behaves weirdly.
The division by 0 in that article gives the same ArithmeticError in each case, notice the 0.0 in both errors:
iex(8)> 1.0 / -0.0
** (ArithmeticError) bad argument in arithmetic expression: 1.0 / 0.0
:erlang./(1.0, 0.0)
iex(8)> 1.0/0.0
** (ArithmeticError) bad argument in arithmetic expression: 1.0 / 0.0
:erlang./(1.0, 0.0)
It's also ignored when rounding: iex(10)> Float.round(-0.01, 1)
0.0
iex(11)> Float.round(0.01, 1)
0.0But I see from the standard that dividing by zero returns infinity with the xor of the dividend and divisor. This also makes no sense - since zero has no sign, its not possible to tell what kind of infinity would result. It's strange that the standard permits both the infinity result from dividing by zero, and simultaneously supports signed zero.
<edit: add xor>