Significant Figures

Does anyone here know of an addition that handles SigFig? I didn’t get a single hit, searching the forum archives, so I thought I’d ping the group before I embarked upon writing this myself. I’m looking for something that would function like this:
http://ostermiller.org/calc/significant_figures.html

As far as I can see from your link, all SigFig does is round your number up or down to the number of significant figures you’ve given. Nigel Garvie’s aRounderRound is a script embedding virtually every way there is to do that.

Hi, Adam. It looks like there is some construction going on at the mirror site that hosts the download, so I can’t preview Nigel’s software directly. There is slightly more to it than simple rounding, because, unlike specifying decimal accuracy, there are positional rules as to when a figure is considered significant. For instance, there are only three SigFigs in .0000000000123 but 123.00 has five. Perhaps Nigel could comment on whether or not aRounderRound is aware of these rules. Otherwise, I’ll begin tackling it.

Hi, Marc.

I’m afraid aRounderRound doesn’t do the “SigFig” stuff you describe.

But it is an interesting problem, nonetheless.

The rules I can find are:

  1. ALL non-zero numbers (1,2,3,4,5,6,7,8,9) are ALWAYS significant.
  2. ALL zeroes between non-zero numbers are ALWAYS significant.
  3. ALL zeroes which are SIMULTANEOUSLY to the right of the decimal point
    AND at the end of the number are ALWAYS significant.
  4. ALL zeroes which are to the left of a written decimal point and are in a number >= 10 are ALWAYS significant.

Wikipedia also has an article

I think it’s important to know the rule of significant figures. The rule of a significant number in a number is by telling it’s precision.

If I say 20 it can be 19.5 till 20.49999999… with a signiticant number I say how precise my number is. When I say 20 with a significant number of 8 it means that my value can be 19.9999995 - 20.00000049999…

Normally when we say I have 88 keys on my piano we know it is 88. When we say on a pack of juice say 1.5 L we actually don’t know how much juice exactly is in the carton. 1,500ml would be a much more precise notation than 1.5 L. To keep the same precision between different notations we need 4 significant numbers. We could say 1,500ml = 1.500 L = 1.50010^3 ml but they have all the same precision. The precision gets lost when we change it to 1.510^3 (Most C programmers are familiar with this error message with number casting).

To answer Marc’s question: AppleScript doesn’t support significant figures (floats), a real is a significant digit (52) but you can’t tell a real to change it’s significant.

To achieve the same as the ‘calculator’ on ostermiller’s site you should work with strings (The post data on his site is string as well)

. though I suppose one of the quantising handlers could be useful:

on SigFig(n, f) -- Render number n to f significant digits, result as text.
	set decimalPoint to character 2 of (0 as real as text)
	
	set n to n as number
	set negative to (n < 0)
	if (negative) then set n to -n
	
	if (n = 0) then
		-- Special-case zero, which doesn't respond to and doesn't need the mantissa manipulation below.
		set output to {"0", "0"}
		set exponent to 0
	else
		-- Get the mantissa and exponent from the number's rendition as text. If it's not in "scientific notation", multiply it by 10 ^ 8 to make it so and adjust the result's exponent.
		set nText to n as real as text
		set ePos to (offset of "E" in nText)
		if (ePos is 0) then
			set nText to (n * (10 ^ 8)) as text
			set ePos to (offset of "E" in nText)
			set exponent to (text (ePos + 1) thru -1 of nText) - 8
		else
			set exponent to (text (ePos + 1) thru -1 of nText) as integer
		end if
		set mantissa to (text 1 thru (ePos - 1) of nText)
		
		-- If the mantissa has more digits than the required significance, round it appropriately.
		if ((count mantissa) - 1 > f) then set mantissa to quantiseIEEE(mantissa as real, 10 ^ (1 - f)) as text
		
		-- Make a list of the mantissa's digit characters.
		set output to {character 1 of mantissa} & characters 3 thru -1 of mantissa
	end if
	
	-- Append trailing zeros (if required) to make up the number of significant digits.
	repeat (f - (count output)) times
		set end of output to "0"
	end repeat
	
	-- Append/prepend zeros to ensure enough digits to satisfy the exponent and insert the decimal point at the right location.
	if (exponent < 0) then
		repeat (f - (count output)) times
			set end of output to "0"
		end repeat
		repeat -exponent times
			set beginning of output to "0"
		end repeat
		set item 1 of output to item 1 of output & decimalPoint
	else
		repeat (exponent - (count output) + 2) times
			set end of output to "0"
		end repeat
		set item (exponent + 2) of output to decimalPoint & item (exponent + 2) of output
	end if
	
	-- Make any necessary adjustments to match integer or negative input.
	if ((n's class is integer) and ((count output) > f)) then set item -1 of output to ""
	if (negative) then set beginning of output to "-"
	
	-- Coerce the list of bits to a single text.
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to ""
	set output to output as text
	set AppleScript's text item delimiters to astid
	
	return output
end SigFig

-- Round n to the nearest multiple of q, rounding halves to the nearest even.
on quantiseIEEE(n, q)
	if (n mod (q * 2)) ^ 2 > (q / 2) ^ 2 then return (n * 2 div q - n div q) * q
	n div q * q
end quantiseIEEE

SigFig(123456789, 5)
--> "123460000"

SigFig(-pi, 7)
--> "-3.141593"

SigFig(1.234567E-5, 10)
--> "0.00001234567000"

Edit: Revamped to do without the gargantuan numToText handler. Also, with integer input, the output no longer has point-zero on the end unless it’s part of the significance. In extreme cases, the results from the quantiseIEEE handler my be subject to floating-point errors.
Further edit: Zero special-cased. Mantissae only rounded where they have more digits than the required significance (to reduce the likelihood of floating-point errors). Comments and a couple of variable names altered.

This snippet just counts the significant figures given. I’m still looking at truncation to a given.


(* 
This is raw code -- no error checking that the entry is valid -- just a first pass. Because doing this with numbers will limit the result to the resolution AppleScript can handle, this approach  uses text.
RULES:
1) ALL non-zero numbers (1,2,3,4,5,6,7,8,9) are ALWAYS significant.
2) ALL zeroes between non-zero numbers are ALWAYS significant.
3) ALL zeroes which are SIMULTANEOUSLY to the right of the decimal point 
   AND at the end of the number are ALWAYS significant.
4) ALL zeroes which are to the left of a written decimal point and are in a number >= 10 are ALWAYS significant.
*)

set numIn to text returned of (display dialog "Enter a number. It will not be checked for validity" default answer "")

-- split input at decimal point
set tid to AppleScript's text item delimiters
set AppleScript's text item delimiters to "."
set tParts to text items of numIn
set AppleScript's text item delimiters to tid

-- eliminate leading zeros before the first non-zero digit to left of dp
tell item 1 of tParts to set leading to it as number as text

-- reassemble number
set sigNum to leading & "." & item 2 of tParts

set SigFigGiven to (length of sigNum) - 1 -- skip DP.

Does same as adam’s code but with some minor error checking.

  • it checks if the given string contains only numbers, periods and commas
  • it ignores the comma notation is higher numbers (as integer as text depends on localized settings in system preferences) EU number notation is x.xxx,xx while US switches the command with the period x,xxx.xx. This function will work with US notation no matter your localize settings.
sigFigOfStringNumber("063,728.000000")

on sigFigOfStringNumber(__StrNum)
	if (do shell script "echo " & quoted form of __StrNum & " | tr -d [0-9,.]")'s length > 0 then
		return 0
	end if
	return (do shell script "echo " & quoted form of __StrNum & " | tr -d [,.] | sed s/^[0]*//")'s length
end sigFigOfStringNumber

EDIT: changed the removing zero’s for larger number like “0000000634782683298164127946794637294631931768924673”

Thanks, everyone, for your thoughts and code samples. Damn, Nigel, you certainly got a lot further than I did. Did you sleep last night? :wink: I’ll take a look at each of your contributions, and either see how I can add this to my code or make replacements.

This is as far as I got. So far, it just divides the elements into the insignificant/significant portions.


--------------------------------------------------------------------------------------------------
isolateSignificance(".001609")
--------------------------------------------------------------------------------------------------

on encounterDecimal(this) --record position for later use
	try
		offset of "." in this
	on error
		--something about appending explicit decimals
	end try
end encounterDecimal

on isolateSignificance(|numbers|)
	set irrelevant to {}
	set significant to {}
	set floodgate to 0 --no significant instances is "closed"
	repeat with this in |numbers|
		try
			tell this as number to if it ≠ 0 or floodgate = 1 then
				set significant's end to it --nonzeroes or zeroes in an open gate
				if significant ≠ {} then set floodgate to 1 --encountered is "open"
			else --could be zero
				set irrelevant's end to it
			end if
		on error --could be a decimal
			--encounterDecimal(|numbers|)
		end try
	end repeat
	{irrelevant as text, significant as text}
end isolateSignificance

FWIW, you can control significant figures using AppleScriptObjC:

set fmt to current application's NSNumberFormatter's alloc()'s init()
fmt's setUsesSignificantDigits_(true)
fmt's setMaximumSignificantDigits_(6)
fmt's setMinimumSignificantDigits_(3)
fmt's stringFromNumber_(2.3)
--> 2.30
fmt's stringFromNumber_(2.3456789)
--> 2.34568

I did it this morning. :slight_smile: But only the main handler. quantiseIEEE and numToStr already existed.

I’ve now rewritten the script to do without numToStr and the trailing point-zero is no longer included in the result from an AppleScript integer input unless it’s part of the significance.

17th February 2012: Further edits to address weaknesses which have subsequently come to light.