DEC64: Decimal Floating Point
dec64.com
dec64.com
The main problem with ordinary, IEEE 754 binary floating point for money is that even run-of-the-mill numbers like $123.45 (nowhere near astronomical) exhibit serious problems when added together, due to 0.45 not being representable exactly in binary, and causing cumulative rounding errors.
This is cured by decimal floating point, which represents $123.45 exactly.
If you deal with quantities that stay within the 16 digits of precision, you will nigh hardly have a problem.
1/ When a coder thinks about writing "if a <= b", then he writes "if a <= b", he easily forgets that he should write "if a <= b + epsilon()"
2/ When people are faced with "I have to have this number with 2 decimals", they sometimes write a = round(a * 100)/100; forgetting that 'a' is a float (or double), and then they completely forget that the new 'a' is just not accurate.
3/ Issues with floats arise on simple operations such as addition. And people tend to think that simple operations have no rounding issues.
It'd be perfectly valid to work with floating point for money; but then you have to take care of the rounding issues much more often than with fixed point (or BigDecimal if you're in the java world). Once you use those BigDecimal, addition becomes safe, and "manual" rounding works as expected (when you round to 2 decimals, you get exactly 2, not an approximation).
So, after project managing several teams of developers, I can safely say that although F.P. can be fine, most people can't handle the mental load of thinking about rounding issues on each math operation they do (and "most people" includes myself). That's a human risk management issue, not a theoritical one.
I think of floating point numbers as being good for representing real world quantities - things you can measure with an instrument. It never matters if you read 1.2999999999993 instead of 1.3 because every practical instrument is less accurate than that anyway. If you start needing to test for equality, that's a sign something's wrong in your design. You can never measure two lengths with a ruler in real life and check if they're equal. So maybe you should be using integers instead, or maybe you don't really need equality.
Your point 3 about rounding issues with addition disappears if you think of the value as being a physical measurement. You just don't care about those errors because they are always insignificant.
Isn't the problem with floats for money more a problem with the conventions of accountants? By rounding everything to 2 decimal places, they're obviously creating far more error than double precision's ~16 places. They just tolerate their specific type of error by convention.
Like what?
> When people are faced with "I have to have this number with 2 decimals", they sometimes write a = round(a * 100)/100; forgetting that 'a' is a float (or double), and then they completely forget that the new 'a' is just not accurate.
This is in fact perfectly accurate when decimal floating point is used. "float" and "double" aren't decmal, are they. But I repeat myself.
For round() you could even have some decimal-based rounding like banker's.
> Issues with floats arise on simple operations such as addition.
Not when they are decimal, repeating myself again.
So true! In a previous job, I had to work with code that did computational geometry, and I quickly learnt that lesson the hard way. I shared an office with my supervisor, who spent a lot of time on the phone, so I also learnt not to curse like a sailor, a lesson I appreciate, in retrospect, almost as much.
Even worse for extreme examples of inflation. Not that the individual unit is actually worth anything there, but calculating it with enough precision is probably easier than the regulation to deal with where money is allowed to disappear or to suddenly exist.
Nothing presented on that page made me thing that DEC64 would be useful for anything that integer arithmetic in the right units would not be better suited to.
For simpler cases this is fine, but for more complex ones I suspect the mental overhead for developers would get problematic.
kwacha_t my_kwacha = 100000; // 1000 Kwacha
euro_t my_euro = EU_from_kwacha(my_kwacha);
printf("My Kwacha in Euros: %d", my_euro / 100);
Most of the problems discussed seem to resolve around trying to have some universal monetary unit, which isn't possible in practice anyway. Just settle on a universal currency for international trade (as the $ effectively is) and convert from/to as necessary for local representation.Of course you're going to have to select an appropriate "minimum value" of your selected currency to suit your expected conversions but how is that different from selecting an appropriate epsilon?
It since has been implemented in some CPUs: IBM z9 upwards, POWER 6 upwards
Additionally, I don't know if there are enough compelling problems that require decimal numbers, can't take the hit of software emulation, and are willing to bind their performance to hardware that would take a while to be adopted, if at all.
I could be wrong though.
Can't store floating point numbers when you really need a ratio because it won't even work after you round it unless you reconcile different settings for precision. Decimal won't help here either.
So outside of niche scientific uses or something like graphics where you need performance, what do you really need floating point numbers for? I haven't had a need for floating point numbers outside of some mathematical formulae.
For representing real (i.e., potentially irrational or transcendental) numbers. Irrational and transcendental numbers are actually very common in nature.
But you're right that representing rational numbers with floating point is stupid.
Instead of inventing new one-size-fits-all coding schemes, people should focus instead on making the idea of a rational number type mainstream.
This wouldn't even be an issue worth discussing if popular programming languages and DBMS's supported rational numbers.
This is... a weird thing to say. Obviously floats are going to be most useful for math, and not useful for much else. They're number types.
1. Game score display where fractional points are possible
2. Currency
3. Any amount displayed to humans (like pounds of beef sold)
because decimal number correspond to what you're going to display to the user, while binary numbers are only useful for the result they produce inside an algorithm
Let's try that again: "not a good impedance match"
https://github.com/jhallen/joes-sandbox/tree/master/lib/farb
(There are much faster alternatives, this was just for the fun of it).
It's good to understand what's going on behind the scenes, how memory is allocated and managed, and the floating point quirks and their binary nature, but for many general applications It'd be nice to have peace of mind that (0.1 + 0.2 == 0.3) returns true.
Why?
In that case, making (NaN == NaN) == false also defeats it. false isn't NaN either. If true or false propagates out of a function whose inputs are NaN, NaN has failed to propagate.
With that logic, you could say making (1 == 1) == true is wrong, because 1 isn't true.
> If true or false propagates out of a function whose inputs are NaN, NaN has failed to propagate.
He meant Nan propagates through calculation operation, not comparison operation. As in, result of calculating Nan + 1 is NaN.
So if you have "A = NaN" and "B = A + 1", you have the situation that both A and B is NaN but A does not equal B.
1 == 1 isn't erroneous. Producing a boolean is the type of the operation: number x number -> bool.
(bool's domain could be extended with additional "nonbool" values, which are produced if some of the operands to a numeric comparison are not numbers.)
> calculation operation, not comparison operation
What's the difference? It's all computation. The comparison calculates a boolean result.
> As in, result of calculating Nan + 1 is NaN.
No it isn't. Proof:
NaN + 1 == NaN // yields false
As a hacky workaround, have to use some special predicate to test for NaN-ness, because the above doesn't work.What would make sense would be to allocate a new NaN, so that it is legitimate that NaN + 1 != NaN. Neither side is a number, but the express different, individual ways of not being a number through their different identities. And the "isnan(X)" predicate tests whether X is an element of the set of all NaNs.
If that's not palatable, throw exceptions: don't allow NaN + 1 (why allow addition on "not a number"? Why even allow "not a number" to be stored into a variable of number type, in a static language?)
#include <math.h>
#include <stdio.h>
void main()
{
float x = NAN;
float y = NAN;
printf("x == y is %d\n", (x == y) ? 1 : 0);
}which should print "x == y is 0".
Also, NAN is similar concept to NULL in databases (pretty much "unknown value") and that also does not believe (NULL == NULL).
#include <math.h>
#include <string.h>
#include <stdio.h>
int main()
{
float x = NAN;
float y = NAN;
printf("x == y is %d\n", (x == y) ? 1 : 0);
printf("memcmp(&x, &y, ...) is %d\n",
memcmp(&x, &y, sizeof x));
return 0;
}
Output: x == y is 0
memcmp(&x, &y, ...) is 0
memcmp says they are the same bit pattern.In fact, even x == x will fail. Direct violation of "A is A".
> NAN is similar concept to NULL in databases
But not similar in concept to NULL in C, where NULL == NULL.
The idea of NaN is that it represents an unknown value, because it essentially says "we don't really know what the result is, but it surely is not a number". And if you have two unknown values, you can hardly check they are equal.
That's why NaN is similar to NULL in SQL, because the concept is basically the same. The difference of course is that C does not have NaN for all the other data types, so it's impossible to say (NaN == NaN) is unknown, because bool, char (or whatever type you use for true/false) does not have the equivalent for NaN. So the next sane option is "false".
The fact that the representation for NaN is the same (so memcmp of two NaN values) is a completely different thing.
If you want two instances of unknown not to be equal, then you can arrange that by actually instantiating different objects with a different identity to represent these unknowns (and print them differently to make it clear: NaN0, NaN1, ... or whatever.
(Yet, if one instance of those objects is propagated into two variables, those variables should still compare equal: NaN0 != NaN1, but NaN0 == NaN0.)
> The fact that the representation for NaN is the same (so memcmp of two NaN values) is a completely different thing.
It tells us it's the same object. In fact, if x is NaN, even x == x fails, and then we have do not even have two bit patterns to compare: it's just one bit pattern. One object at one address, not equal to itself.
In other words, this is garbage that violates "A is A": an object is not itself.
This is the work of imbeciles who chose to be ignorant of the philosophical underpinnings of what they are doing.
That some features of SQL were produced by similar such is neither here nor there.
Really, the answer for any comparison to NaN shouldn't be true or false, it should be something like NaB, Not A Boolean. This is basically what R does:
> 1 == 1 [1] TRUE > NaN == NaN [1] NA
But the major disadvantage of that is that a boolean now has 3 possible states, so it can't be represented in one bit.
What language is this?
"In a freestanding environment (in which C program execution may take place without any benefit of an operating system), the name and type of the function called at program startup are implementation-defined."
So, there, the entry point could have prototype
void main();
and if it doesn't, that makes that name available as the name of a user-defined function.It is valid ISO C syntax in any environment, which has no meaning according to ISO C.
To have meaning in a hosted environment, the implementation must supply one by accepting "void main" as a startup function in addition to the required forms.
In a freestanding environment, for the program to have meaning, the freestanding implementation must ... supply one by accepting "void main" as a startup function as one of its implementation-defined startup function forms.
It's the same thing: the program is not defined by ISO C, but implementations can give it a local definition of meaning.
If both kinds of implementations support "void main(void)" as an extension, they have to documented it, because an implementation must document all extensions. I'm looking at C99, section 4:
An implementation shall be accompanied by a document that defines all implementationdefined and locale-specific characteristics and all extensions.
So, if "void main(void)" is officially accepted by a freestanding implementation, that means it is one of the implementation-defined startup function forms, and must be documented.
If "void main(void)" is officially accepted by a hosted implementation, it constitutes an extension and must be documented.
Basically, the status of void main appears identical, whether hosted or not, with regard to the definedness of behavior (as far as ISO C is concerned), and the requirement for documentation if it is supported.
Well, if you don't have an OS, a lot of things change.
My question comes from all of the C-like Algol-like languages which look like C but... aren't C. You know, like how Java/C# isn't technically one language, but two, for legal reasons.