Format a floating point number exactly as decimal

9

Any binary floating point can be formatted exactly in decimal. The resulting string might be somewhat long, but it is possible. In my article on floating point I cover the importance of precision, and now I want this function. This challenge is to write a program, or function, that takes a floating point value as input and formats an exact decimal string as output.

To ensure we are working with the correct floating point numbers a precise format must be provided as input to the program. This format will be two integers Significand Exponent, where the actual floating point value is Significand * 2 ^ Exponent. Note that either value can be negative.

Specifics:

  • The range and precision of at least a 32-bit float must be supported (no input will go beyond that)
  • The decimal formatted value must be an exact representation (simply close enough to guarantee a correct round-tip back to float is not good enough)
  • We don't trust standard library floating point formatting functions to be correct enough nor fast enough (ex: printf), and thus they may not be used. You must do the formatting. Integral formatting/conversion functions are allowed.
  • There may not be any leading or trailing zeros, except for the required one leading zero in front of the . if there is no whole number component
  • A function, or whole program, is allowed.

Examples:

1 -2 => 0.25
17 -3 => 2.125
-123 11 => -251904
17 50 => 19140298416324608
23 -13 => 0.0028076171875
3 120 => 3987683987354747618711421180841033728
3 -50 => 0.00000000000000266453525910037569701671600341796875
-3 -50 => -0.00000000000000266453525910037569701671600341796875
10 -2 => 2.5
-12345 -3 => -1543.125
0 0 => 0
161 -4 => 10.0625
512 -3 => 64

Shortest code wins.

edA-qa mort-ora-y

Posted 2015-07-08T04:40:49.170

Reputation: 193

3Is the use of unlimited precision floating point arithmetic allowed? – Dennis – 2015-07-08T04:55:09.680

2If the exponent is nonnegative, can we end with .0? – Sp3000 – 2015-07-08T04:55:21.687

@Dennis: Yes, unlimited or high fixed precision arithmetic is allowed. – edA-qa mort-ora-y – 2015-07-08T05:19:55.133

@Sp3000: No, that's a trailing zero and is disallowed. I think a trailing . should also be disallowed. – edA-qa mort-ora-y – 2015-07-08T05:20:21.580

May I suggest adding the test case -3 -50? There are no test cases where both numbers are negative and my first submission broke on it. – Dennis – 2015-07-08T06:28:27.967

Added the test case. If anybody else has any test cases that trip up their code please list them in case they can also trip up other solutions as well. – edA-qa mort-ora-y – 2015-07-08T07:43:42.340

1I think that's inconsistent. If 0.abc is not a leading zero, then abc.0 isn't a trailing one. – orlp – 2015-07-08T15:43:31.297

I suggest adding cases 10 -2 and -12345 -3 – aditsu quit because SE is EVIL – 2015-07-08T17:00:51.693

@orlp: It's just convention when writing numbers to not lead with . but to always put a 0 in the front. – edA-qa mort-ora-y – 2015-07-08T17:10:45.467

1It's also convention to always end with .0 for whole numbers when dealing with floating point numbers. See for example Python: str(1.0) == '1.0' versus str(1) == '1'. Your logic is still inconsistent. – orlp – 2015-07-08T17:58:37.510

@orlp Maybe the OP prefers JavaScript style formatting :) Anyway, he has the right to define the requirements (and you have the right to raise your concern too) – aditsu quit because SE is EVIL – 2015-07-08T18:08:47.487

Another case suggestion: 0 0 (not sure if other exponents are needed) – aditsu quit because SE is EVIL – 2015-07-08T19:09:02.510

And another case: 161 -4 (zero before and after decimal point but not starting with 0) – aditsu quit because SE is EVIL – 2015-07-08T21:37:22.920

Proposed test case: 512 -3. Negative exponent, but result has no fractional part. – Reto Koradi – 2015-07-09T14:57:37.607

Answers

3

CJam, 43

r_'-&\ize999rim<s1e3'0e[W%999/(i_L?\+'.*sW%

Try it online

Explanation:

The program works with exponents up to ±999, close to double precision (64 bit). It separates the minus sign (if present) from the significand, multiplies it by 10999 then does a bit shift with the exponent, which is now an exact calculation. It then pads to the left with zeros if the result has less than 1000 digits, separates the last 999 digits as the fractional part, removes trailing zeros by converting its reverse to integer, adds a decimal point if needed, and puts everything back together.

r_         read and duplicate the significand in string form
'-&        keep only the minus sign, if present
\          swap with the other copy of the significand
iz         convert to integer and get absolute value
e999       multiply by 10^999
ri         read the exponent and convert to integer
m<         shift left by it; negative values will shift right
            the result is an exact non-negative integer
s          convert to string
1e3'0e[    pad to the left with zero characters up to length 1000
            longer strings will be left intact
            we need 1 more than 999 for the 0.xxx case
W%         reverse the string
999/       split into slices of length 999
(          take out the first slice (reversed fractional part)
i          convert to integer
            this removes the leading zeros (trailing in reverse)
_L?        if it's zero, replace with an empty string
\+         concatenate back (to the left) with the second slice
'.*        join the with the dot character
            if the fractional part was zero, we only have the second slice
            (reversed integer part) and there is nothing to join
s          convert to string; this is the reversed result without the sign
W%         reverse back

At the end, the minus sign (if any) and the final string are automatically printed together.

aditsu quit because SE is EVIL

Posted 2015-07-08T04:40:49.170

Reputation: 22 326

2

CJam, 50 bytes

q~A1$z#\_0>K5?\z:E#@_s'-&oz*\md_sE'0e[W%isW%'.\+Q?

This is a full program that reads from STDIN. Try it online in the CJam interpreter.

Verify all test cases at once.

Dennis

Posted 2015-07-08T04:40:49.170

Reputation: 196 637

Based on your comment I assume CJam has unlimited precision and you've used that here? Is it correct then that this answer covers any input, not just 32-bit float? Also, can we get an explanation of how it works? – edA-qa mort-ora-y – 2015-07-08T05:23:08.277

CJam has unlimited precision for integers, but only double precision floats. I multiply by a power of 20 for positive exponents and a power of 5 for negative ones, the cast to string and insert the dot. I'll add a detailed explanation in a few hours. – Dennis – 2015-07-08T05:27:33.430

And yes, given enough memory, this should work for any input. – Dennis – 2015-07-08T05:33:29.937

10 -2 seems to have a trailing zero – aditsu quit because SE is EVIL – 2015-07-08T11:38:15.033

@aditsu: Ah yes, one trailing zero for every power of 2... – Dennis – 2015-07-08T14:15:09.157

@aditsu: Fixed at the cost of 5 bytes. :( – Dennis – 2015-07-08T14:28:58.420

Can you check -12345 -3 ? – aditsu quit because SE is EVIL – 2015-07-08T17:01:35.840

@aditsu: Checked and fixed at the cost of 0 bytes. – Dennis – 2015-07-08T17:10:52.507

2

GNU sed + dc, 65

Score includes +1 for sed's -r option.

y/-/_/
s/.*/dc -e"C8k& 2r^*p"/e
s/\\\n//
s/0+$//
s/^(-?)\./\10./

I was tempted to claim this dc-only answer C8k& 2r^*p for a score of 10, but dc has some formatting quirks:

  • the -ve sign is _ instead of -
  • long lines are broken with backslashes
  • trailing zeros must be removed
  • leading 0 for |n| < 1 must be added

So the dc expression is wrapped and evaled by sed to take care of the above.

Test output:

$ echo "1 -2
17 -3
-123 11
17 50
23 -13
3 120
3 -50
-3 -50
8388608 127
1 -127" | sed -rf float.sed
0.25
2.125
-251904
19140298416324608
0.0028076171875
3987683987354747618711421180841033728
0.00000000000000266453525910037569701671600341796875
-0.00000000000000266453525910037569701671600341796875
1427247692705959881058285969449495136382746624
0.0000000000000000000000000000000000000058774717541114375398436826861112283890933277838604376075437585313920862972736358642578125
$ 

Digital Trauma

Posted 2015-07-08T04:40:49.170

Reputation: 64 644

Hmm, I'm thinking that dc kind of violates my rule on using a standard formatting function. – edA-qa mort-ora-y – 2015-07-08T17:21:54.087

1@edA-qamort-ora-y I figured that use of dc is ok, given "unlimited or high fixed precision arithmetic is allowed". dc's p command is not a "floating point formatting function" - it is an arbitrary precision print function. I am setting the precision to 128 decimal places (C8k), which I think is more than enough for any 32bit float. – Digital Trauma – 2015-07-08T17:26:15.317