I need to parse floating-point literals in C code using OCaml.
OCaml's float type is 64 bit. I have the string of the literal, its numeric value rounded to 64 bits and its kind (float, double or long double).
The problem are literals with a numeric value bigger than 64 bit:
long double literals
float literals with 'f'-suffix for which double rounding errors would occur if they wouldn't have the suffix.
OCaml's arbitrary-precision module can parse rational numbers from strings like "123/123", but not "123.123", "123e123", "0x1.23p-1" like they might appear in C.
Background: I do value analysis of C programs using CIL.
Double literals of any size and float literals with a numeric value that fits into 64 bit are always correctly represented. By rounding from double- to single-precision I can also reproduce double rounding errors.
I wrote my answer in the form of a blog post
To summarize some of the points here: you could interface strtold() and strtof() from OCaml. For the former, you would have to consider how you are going to store the result it produces, since there only is a point if long double is larger than double on your host architecture. There remains the problem that these functions are buggy in one of the most widely used C library. Very slightly buggy, but buggy for exactly the examples that are going to be of interest if you are doing this to study double rounding.
Another way is to write your own function, starting from another post in the blog you refer to.
Finally, the phrase "Even getting single-precision floats right requires me to parse literals with values bigger than 64 bit" that you use in the comments is still a strange way to put it. The intermediate format(s) in which you can parse the representation of a single-precision float before you round it to single-precision have to be lossless, otherwise there will be double rounding. Double rounding may be more or less difficult to exhibit depending on the precision of the lossy intermediate format, but using 80 bits or 128 bits binary floating-point formats is not going to remove the problem, just make it more subtle. In the simple algorithm that I recommend, the intermediate format is a fraction of two multiprecision integers.
I don't see the question in this question :)
Assuming that you need an ocaml parser for "C float literals" - the answer is - write one yourself, it is not very hard and you will have strict control on the implementation details and what "C float literal" actually means.
Related
I'm getting a rounding oddity in Delphi 2010, where some numbers are rounding down in roundto, but up in formatfloat.
I'm entirely aware of binary representation of decimal numbers sometimes giving misleading results, but in that case I would expect formatfloat and roundto to give the same result.
I've also seen advice that this is the sort of thing "Currency" should be used for, but as you can see below, Currency and Double give the same results.
program testrounding;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,Math;
var d:Double;
c:Currency;
begin
d:=534.50;
c:=534.50;
writeln('Format: ' +formatfloat('0',d));
writeln('Roundto: '+formatfloat('0',roundto(d,0)));
writeln('C Format: ' +formatfloat('0',c));
writeln('C Roundto: '+formatfloat('0',roundto(c,0)));
readln;
end.
The results are as follows:
Format: 535
Roundto: 534
C Format: 535
C Roundto: 534
I've looked at Why is the result of RoundTo(87.285, -2) => 87.28 and the suggested remedies do not seem to apply.
First of all, we can remove Currency from the question, because the two functions that you use don't have Currency overloads. The value is converted to an IEEE754 floating point value and then follows the same path as your Double code.
Let's look at RoundTo first of all. It is quick to check, using the debugger, or an additional Writeln that RoundTo(d,0) = 534. Why is that?
Well, the documentation for RoundTo says:
Rounds a floating-point value to a specified digit or power of ten using "Banker's rounding".
Indeed in the implementation of RoundTo we see that the rounding mode is temporarily switched to TRoundingMode.rmNearest before being restored to its original value. The rounding mode only applies when the value is exactly half way between two integers. Which is precisely the case we have here.
So Banker's rounding applies. Which means that when the value is exactly half way between two integers, the rounding algorithm chooses the adjacent even integer.
So it makes sense that RoundTo(534.5,0) = 534, and equally you can check that RoundTo(535.5,0) = 536.
Understanding FormatFloat is quite a different matter. Quite frankly its behaviour is somewhat opaque. It performs an ad hoc rounding in code that differs for different platforms. For instance it is assembler on 32 bit Windows, but Pascal on 64 bit Windows. The overall approach appears to be to take the mantissa of the floating point value, convert it to an integer, convert that to text digits, and then perform the rounding based on those text digits. No respect is paid to the current rounding mode when the rounding is performed, and the algorithm appears to implement the round half away from zero policy. However, even that is not implemented robustly for all possible floating point values. It works correctly for your value, but for values with more digits in the mantissa the algorithm breaks down.
In fact it is fairly well known that the Delphi RTL routines for converting between floating point values and text are fundamentally broken by design. There are no routines in the Delphi RTL that can correctly convert from text to float, or from float to text. In fact, I have recently implemented my own conversion routines, that do this correctly, based on existing open source code used by other language runtimes. One of these days I will get around to publishing this code for use by others.
I'm not sure what your exact needs are, but if you are wishing to exert some control over rounding, then you can do so if you take charge of the rounding. Whilst RoundTo always uses Banker's rounding, you can instead use Round which uses the current rounding mode. This will allow you to perform the round using the rounding algorithm of your choice (by calling SetRoundMode), and then you can convert the rounded value to text. That's the key. Keep the value in an arithmetic type, perform the rounding, and only convert to text at the very last moment, after the correct rounding has been applied.
In this case, the value 534.5 is exactly representable in Double precision.
Looking into source code, reveals that the FormatFloat function rounds upwards if the last pending digit is 5 or more.
RoundTo uses the Banker's rounding, and rounds to nearest even number (534) in this case.
A simple question that turned out to be quite complex:
How do I turn a float to a String in GForth? The desired behavior would look something like this:
1.2345e fToString \ takes 1.2345e from the float stack and pushes (addr n) onto the data stack
After a lot of digging, one of my colleagues found it:
f>str-rdp ( rf +nr +nd +np -- c-addr nr )
https://www.complang.tuwien.ac.at/forth/gforth/Docs-html-history/0.6.2/Formatted-numeric-output.html
Convert rf into a string at c-addr nr. The conversion rules and the
meanings of nr +nd np are the same as for f.rdp.
And from f.rdp:
f.rdp ( rf +nr +nd +np – )
https://www.complang.tuwien.ac.at/forth/gforth/Docs-html/Simple-numeric-output.html
Print float rf formatted. The total width of the output is nr. For
fixed-point notation, the number of digits after the decimal point is
+nd and the minimum number of significant digits is np. Set-precision has no effect on f.rdp. Fixed-point notation is used if the number of
siginicant digits would be at least np and if the number of digits
before the decimal point would fit. If fixed-point notation is not
used, exponential notation is used, and if that does not fit,
asterisks are printed. We recommend using nr>=7 to avoid the risk of
numbers not fitting at all. We recommend nr>=np+5 to avoid cases where
f.rdp switches to exponential notation because fixed-point notation
would have too few significant digits, yet exponential notation offers
fewer significant digits. We recommend nr>=nd+2, if you want to have
fixed-point notation for some numbers. We recommend np>nr, if you want
to have exponential notation for all numbers.
In humanly readable terms, these functions require a number on the float-stack and three numbers on the data stack.
The first number-parameter tells it how long the string should be, the second one how many decimals you would like and the third tells it the minimum number of decimals (which roughly translates to precision). A lot of implicit math is performed to determine the final String format that is produced, so some tinkering is almost required to make it behave the way you want.
Testing it out (we don't want to rebuild f., but to produce a format that will be accepted as floating-point number by forth to EVALUATE it again, so the 1.2345E0 notation is on purpose):
PI 18 17 17 f>str-rdp type \ 3.14159265358979E0 ok
PI 18 17 17 f.rdp \ 3.14159265358979E0 ok
PI f. \ 3.14159265358979 ok
I couldn't find the exact word for this, so I looked into Gforth sources.
Apparently, you could go with represent word that prints the most significant numbers into supplied buffer, but that's not exactly the final output. represent returns validity and sign flags, as well as the position of decimal point. That word then is used in all variants of floating point printing words (f., fp. fe.).
Probably the easiest way would be to substitute emit with your word (emit is a deferred word), saving data where you need it, use one of available floating pint printing words, and then restoring emit back to original value.
I'd like to hear the preferred solution too...
For simple uses, such as tracking weight values like 65.1kg, is there any benefit of going with NSDecimal/NSDecimalNumber over double?
My understanding here is double (or even float) provides more than enough precision in such cases. Please correct me if I'm wrong.
First, read Josh Caswell's link. It it especially critical when working with money. In your case it may matter or may not, depending on your goal. If you put in 65.1 and you want to get exactly 65.1 back out, then you definitely need to use a format that rounds properly to decimal values like NSDecimalNumber. If, when you put in 65.1, you want "a value that is within a small error of 65.1," then float or double are fine (depending on how much error you are willing to accept).
65.1 is a great example, because it demonstrates the problem. Here in Swift because its so easy to demonstrate, but ObjC is the same:
1> 65.1
$R0: Double = 65.099999999999994
2>
1/10 happens to be a repeating decimal in binary, just like 1/3 is a repeating decimal in decimal. So 65.1 encoded as a double is "close to" 65.1, but not exact. If you need an exact representation of decimal-encoded number (i.e. what most humans expect), use NSDecimalNumber. This isn't to say that NSDecimalNumber is more accurate than double. It just imposes different rounding errors than double. Which rounding errors you prefer depends on your use case.
R15b on Windows gives:
>trunc(1.9999999999999999999).
2
For that matter, just typing the float returns:
> 1.9999999999999999999.
2.0
AFAIK, the truncate function should just drop the fractional portion (at least that's what I need, anyway). A floor function might also do the trick AFAIK, but the floor implementations I've seen posted online use... you guessed it... trunc.
I'm not nitpicking this, I actually need this to be correct for a program I'm developing.
Any ideas on this?
Thanks.
Your problem is decimal numbers are represented as IEEE compliant binary representation (32, 64 or 128 bit).
If you really need precision you should use other numerical data structures as Binary Coded Decimal or fixed-point arithmetic.
Hope this helps!
if you want to make a TRUNC to float, maybe this one can help:
select substring (convert(varchar(14), CAST (20160303013458 as varchar(14))) , 1 , 8)
Given a double value like 1.00500000274996E-8, how do I convert it to it's non scientific format with a maximum number of digits after the decimal point - in this case with 8 digits it would be 1.00500000?
The conversion should not pad with zeros, so 2007 would come out as 2007, and 2012.33 and 2012.33.
I've tried lots of combinations using Format, FormatFloat, FloatToStrF but can't quite seem to hit the jackpot. Many thanks for any help.
Edit: I should clarify that I am trying to convert it to a string representation, without the Exponent (E) part.
FormatFloat('0.######################', 1.00500000274996E-8) should do the trick.
Output is: 0,0000000100500000274996
It will not output more digits than absolutely necessary.
See John Herbster's Exact Float to String Routines in CodeCentral. Perhaps not exactly what youre after but might be good starting point... CC item's description:
This module includes
(a) functions for converting a floating binary point number to its *exact* decimal representation in an AnsiString;
(b) functions for parsing the floating point types into sign, exponent, and mantissa; and
(c) function for analyzing a extended float number into its type (zero, normal, infinity, etc.)
Its intended use is for trouble shooting problems with floating point numbers.
His DecimalRounding routines might be of intrest too.