How to format a number with an error?

I'm having a surprising amount of difficulty in trying to format some numbers with errors. I can't find any previous attempts to do this here or elsewhere and it would surely be of use to the community.

I have some number-error pairs: {12345.6789, 0.0012345} which I would like to convert to strings with both the number and the error rounded to the same number of decimal places, such that the error has a precision of two significant figures.

f[{12345.6789, 0.03456}]
f[{12345.6789, 3.456}]
f[{12345.6789, 345}]

"12345.679 ± 0.035"

"12345.7 ± 3.5"

"12340 ± 350"

I'm struggling to get this done with RealDigits, N, NumberForm, StringRiffle and others.

Can anyone produce a robust working implementation of f?

Edit: Additional test cases

There are quite a few subtly different behaviours when handling different number, including rounding, adding trailing zeros, handling precision = 0, and probably others. Therefore for the sake of comprehensively testing implementations here are a list of inputs and the required outputs (if you can think of any additional useful test cases then let me know / add them):

f[{12.3456, 0.0123}]     ->     "12.346 ± 0.012"
f[{12.3456, 0.123}]      ->     "12.35 ± 0.12"
f[{129.3456, 1.23}]      ->     "129.3 ± 1.2"
f[{-129.3456, 12.3}]     ->     "-129. ± 12."
f[{12.9999, 0.02}]       ->     "13.000 ± 0.020"
f[{1, 100}]              ->     "0 ± 100."
f[{-1, 100}]             ->     "0 ± 100."
f[{12345.6789, 345}]     ->     "12350. ± 340."
f[{12345.6789, 3456}]    ->     "12300. ± 3500."

Update

This seems to do what you want a bit more simply than your own solution.

f[{a_, b_}] :=
NumberForm[
SetAccuracy[a ± b, Accuracy @ SetPrecision[b, 2]],
ExponentFunction -> (Null &)
] // ToString // Quiet

f[{12.3456, 0.0123}]
f[{12.3456, 0.123}]
f[{129.3456, 1.23}]
f[{-129.3456, 12.3}]
f[{12.9999, 0.02}]
f[{1, 100}]
f[{-1, 100}]
f[{12345.6789, 345}]
f[{12345.6789, 3456}]

"12.346 ± 0.012"

"12.35 ± 0.12"

"129.3 ± 1.2"

"-129. ± 12."

"13.000 ± 0.020"

"0. ± 100."

"0. ± 100."

"12350. ± 340."

"12300. ± 3500."

• Nice - you'd think this would be native by now though. – alancalvitti Dec 1 '16 at 1:19
• Thanks, that seems to get most of the way there. In a couple of cases the output is not quite as I would want, for example f[{12.6789, 0.00123}] -> "12.6789 \[PlusMinus] 0.0012000000000000001" and although the answer for f[{12.9999, 0.02}] is correct ("13. \[PlusMinus] 0.02"), I'd like to include the relevant trailing zeros, eg "13.000 \[PlusMinus] 0.020". I'll have a proper look with this and @corey979's code later and try to form a fully robust solution, there are a lot of edge cases! Also the 345 being rounded to 340 I'm ok with, that's the normal banker's rounding of Round. – Quantum_Oli Dec 1 '16 at 7:53
• Thanks Mr.W, this works nicely. The only time I've spotted it fail is with an input along the lines of {0,0.01} which produces an output " -4\n0. \[Times] 10 \[PlusMinus] 0.010", however this is due to a failing in NumberForm ask discussed and circumvented here: mathematica.stackexchange.com/q/133065/6588 – Quantum_Oli Dec 9 '16 at 11:29
f[{a_, b_}, prec_] :=
Block[{x = {a, b}, y}, y = SetAccuracy[x, prec];
ToString @ y[] <> " ± " <> ToString @ y[]]

f[{12345.6789, 0.0012345}, 4]

"12345.679 ± 0.001"

f[{12345.6789, 3.456}, 2]

"12345.7 ± 3.5"

f[{12345.6789, 345}, 1]

"12346. ± 345."

f[Round[#, 5]& @ {12345.6789, 345}, 1]

"12345. ± 345."

• I hadn't come across SetAccuracy before. I would want trailing zeros where needed, for example f[{12345.6789, 0.0012345}, 4] should be "12345.6790 ± 0.0010". Also it struggles with some precisions, eg: f[{12345.6789, 12.345}, 0] comes out at a mess. – Quantum_Oli Nov 30 '16 at 19:36
• Then do f[Round[#, 0.001] &@{12345.6789, 0.0012345}, 5] - yields "12345.6790 ± 0.0010". Rounding is one thing that can be done on the input itself; I focused on adressing the issue of displaying given number of digits. Also, SetAccuracy is probably designed differently than what you think. – corey979 Nov 30 '16 at 19:44

Building on @Mr.Wizard♦ 's answer I have come up with the following. I use SetPrecision to set the significant figures of the error to two (ala Mr.W). I then use RealDigits, which tells me about the number of significant figures either side of the decimal place, to calculate the necessary precision for the number, a. I can the use SetPrecision again, instead of Round, but I have to catch, using If, the cases where the required precision is $\le 0$.

I avoid using InputForm as I think this is what leads to the 0.0012000000000000001 problem. But I do use NumberForm to convert 1.*10^2 to 100.. The Quiet is there to turn off the warning about the presence of trailing zeros - which I actually want!

My function:

f[{a_, b_}] := Module[{aa, bb, p},
bb = SetPrecision[b, 2];
p = Last[RealDigits[a]] - Last[RealDigits[bb]] + 2;
aa = If[p > 0, SetPrecision[a, p], 0];
Quiet[
ToString@
NumberForm[PlusMinus @@ {aa, bb}, ExponentFunction -> (Null &)],
{NumberForm::sigz}
]
]

This passes all the test cases I have outlined so far above, please let me know if you find any tests you think it fails. And obviously if you find a more elegant implementation!

f[{12.3456, 0.0123}]
f[{12.3456, 0.123}]
f[{129.3456, 1.23}]
f[{-129.3456, 12.3}]
f[{12.9999, 0.02}]
f[{1, 100}]
f[{-1, 100}]
f[{12345.6789, 345}]
f[{12345.6789, 3456}]

"12.346 ± 0.012"

"12.35 ± 0.12"

"129.3 ± 1.2"

"-129. ± 12."

"13.000 ± 0.020"

"0 ± 100."

"0 ± 100."

"12350. ± 340."

"12300. ± 3500."

• Please see my updated answer. I borrowed your use of ExponentFunction to replace the poorly chosen InputForm. – Mr.Wizard Dec 2 '16 at 22:57