11

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."
Quantum_Oli
  • 7,964
  • 2
  • 21
  • 43

4 Answers4

8

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

Your test:

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."

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • Nice - you'd think this would be native by now though. – alancalvitti Dec 01 '16 at 01: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 01 '16 at 07:53
  • 1
    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: http://mathematica.stackexchange.com/q/133065/6588 – Quantum_Oli Dec 09 '16 at 11:29
6
f[{a_, b_}, prec_] := 
 Block[{x = {a, b}, y}, y = SetAccuracy[x, prec]; 
  ToString @ y[[1]] <> " ± " <> ToString @ y[[2]]]

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."

corey979
  • 23,947
  • 7
  • 58
  • 101
  • 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
4

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."

Quantum_Oli
  • 7,964
  • 2
  • 21
  • 43
  • Please see my updated answer. I borrowed your use of ExponentFunction to replace the poorly chosen InputForm. – Mr.Wizard Dec 02 '16 at 22:57
3

This extends Mr. Wizard's answer to handle large/small numbers:

printNumberError[a_, b_] := 
 Block[{exponent = Floor[Log[10, Abs[a]]], 
    sas = SetAccuracy[#1, Accuracy[SetPrecision[#2, 2]]] &}, 
   If[exponent < 6 && exponent > -4, 
    NumberForm[sas[Row[{a, "±", b}, " "], b], 
     ExponentFunction -> (Null &)], 
    Block[{aa = a/10^exponent, bb = b/10^exponent}, 
     Row[{"(", sas[aa, bb], "±", sas[bb, bb], ")", 
       "×", Superscript[10, exponent]}, " "]]]];

For example:

printNumberError[-6.390809*10^-6, 3.4485*10^-8]

Gives:

$$(-6.391\pm0.034)\times10^{-6}$$

  • Good addition; thanks. I suggest making the SetAccuracy[a, Accuracy[SetPrecision[b, 2]]] operation a function to reduce repetition in this code. I can edit that in if you like. – Mr.Wizard Jan 22 '20 at 15:51
  • Good point ... updated my code. – Brett van de Sande Jan 24 '20 at 06:09
  • @Mr.Wizard ... Also, I generally keep my Mma code in plain text (*.m) files so that I can use source control in a meaningful way. Thus, special characters like $\pm$ are problematic. – Brett van de Sande Jan 24 '20 at 06:17
  • There is a difference between "private" Mathematica characters and standard characters that Mathematica uses. ± is part of the latter and it should work fine in plain text. Have you actually experienced problems or did you assume it would not work? – Mr.Wizard Jan 24 '20 at 09:37
  • 1
    I did a test and git (on Ubuntu) does handle both "±" and "×" without issue. – Brett van de Sande Jan 25 '20 at 17:32