Converting exponential numbers to real numbers

Even if I ask it to display as meters, I gel a decimal period while it’s supposed to be a comma here in France.

{-1.00364941532576E+12, “-1003649415325.764”}

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) dimanche 10 février 2019 11:58:15

When the input’s a very precise real, the different methods can produce different levels of precision in their results. The following script is a version of the ASObjC script from post #15 which doesn’t impose a fixed number of decimal places and which returns a list containing the original number as displayed in an editor, the ASObjC result, and the as-yards-as-text result:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

------------------------GENERATE A RANDOM NUMBER------------------------
set nmbr to (random number from -9.9E+24 to 9.9E+24) / ((random number (9.9E+12 - 1)) + 1)

------------------------CONVERT THE NUMBER TO READABLE TEXT------------------------
set theFormatter to current application's class "NSNumberFormatter"'s new()
tell theFormatter to setNumberStyle:(current application's NSNumberFormatterDecimalStyle)
tell theFormatter to setMinimumFractionDigits:(0)
tell theFormatter to setMaximumFractionDigits:(100)
tell theFormatter to setLocalizesFormat:(true)
return {nmbr, (theFormatter's stringFromNumber:(nmbr)) as text, nmbr as yards as text}

Ignoring the formatting, I’m finding in Mojave that the ASObjC result usually contains all the same digits as the displayed original number, but may occasionally end with a group of digits that can be rounded to the last digit of the AS version. On the other hand, the as-yards-as-text result more commonly ends with a roundable group and only occasionally has all the same digits as the displayed original.

Very occasionally, an as-yards-as-text result will appear to break the “half-step to nearest even” rounding rule:

This is probably because the results are all being rounded from an even more precise figure like 220109764541.18747, which rounds up to 220109764541.1875, but down to 220109764541.187.

But it could be that the “extra precision” is a manifestation of the imprecision of reals! I don’t know.

Here’s a script containing both vanilla and ASObjC handlers. With the given parameters, they return identical results. But with ridiculously high input numbers, or with ten or more decimal places, the vanilla one gives the impression of being more precise:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

------------------------GENERATE A RANDOM NUMBER------------------------
set nmbr to (random number from -9.9E+24 to 9.9E+24) / ((random number (9.9E+12 - 1)) + 1)

------------------------CONVERT THE NUMBER TO READABLE TEXT------------------------
set decimalPlaces to 2 -- Adjust to taste.

return {numberToTextAS(nmbr, decimalPlaces), numberToTextASObjC(nmbr, decimalPlaces)}

-- Handler with vanilla code.
on numberToTextAS(nmbr, decimalPlaces)
	set decimalPlaces to decimalPlaces div 1 -- In case the user's a smartarse.
	-- Get (ie. guess) the user's decimal point and thousands separator.
	if (character 2 of (1 / 2 as text) is ".") then
		set {decimalPoint, separator} to {".", ","}
	else
		set {decimalPoint, separator} to {",", " "} -- Not guaranteed to be correct for all non-point countries.
	end if
	-- Get the number's sign and work with positives for convenience.
	if (nmbr < 0) then
		set {nmbr, sign} to {-nmbr, "-"}
	else
		set {nmbr, sign} to {nmbr, ""}
	end if
	-- Get the number's whole-number and fractional parts.
	set {wholeNumberPart, fractionalPart} to {nmbr div 1, nmbr mod 1}
	
	set collector to {}
	-- Add 1 to the fractional part (to preserved any leading zeros) and left-shift it by the required number of decimal places.
	set fractionalPart to (1 + fractionalPart) * (10 ^ decimalPlaces)
	-- Round the result to the nearest integer. It may be too big to coerce to integer directly at this stage.
	set fractionalPart to fractionalPart div 1 + (fractionalPart mod 1 as integer)
	if (decimalPlaces > 0) then
		-- If it contains more than eight digits, coerce eight of them to text at a time and add the results to the collector.
		repeat until (fractionalPart < 100000000)
			set beginning of collector to text 2 thru -1 of (100000000 + fractionalPart mod 100000000 as integer as text)
			set fractionalPart to fractionalPart div 100000000
		end repeat
		-- Coerce and collect whatever's left over at the end, prepending a decimal point character.
		set fractionalPart to fractionalPart as text
		set beginning of collector to decimalPoint & text 2 thru -1 of fractionalPart
		-- If the 1 added above increased to 2 during the rounding, add the carry to the whole-number part.
		if (fractionalPart begins with "2") then set wholeNumberPart to wholeNumberPart + 1
	else
		-- Deal with any carry from the rounding this way.
		if (fractionalPart as integer is 2) then set wholeNumberPart to wholeNumberPart + 1
	end if
	
	-- Now add text versions of each group of three whole-number digits to the collector, interspersed with the separator character.
	repeat until (wholeNumberPart < 1000)
		set beginning of collector to text 2 thru -1 of (1000 + wholeNumberPart mod 1000 div 1 as text)
		set beginning of collector to separator
		set wholeNumberPart to wholeNumberPart div 1000
	end repeat
	set beginning of collector to wholeNumberPart
	
	-- Coerce the collector's contents to a single text and return the result.
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to ""
	set numberText to sign & collector
	set AppleScript's text item delimiters to astid
	
	return numberText
end numberToTextAS

-- Handler with ASObjC code.
on numberToTextASObjC(nmbr, decimalPlaces)
	set decimalPlaces to decimalPlaces div 1
	set theFormatter to current application's class "NSNumberFormatter"'s new()
	tell theFormatter to setNumberStyle:(current application's NSNumberFormatterDecimalStyle)
	tell theFormatter to setMinimumFractionDigits:(decimalPlaces)
	tell theFormatter to setMaximumFractionDigits:(decimalPlaces)
	tell theFormatter to setLocalizesFormat:(true)
	
	return (theFormatter's stringFromNumber:(nmbr)) as text
end numberToTextASObjC

Thank you Nigel

“Funny” feature, If I inserts the result of the first handler in cell A1 of a table in Numbers then inserts the formula = A1 * 1 in cell B1, I get the value returned bt the second handler.

18 409 688 740 693,76 → 18409688740693,8
In an other attempt,
-20 843 851 413 465,18 → -20843851413465,18 while the 2nd handler returned “-20 843 851 413 465,20”

I tried to do the same in Libre Office where both values were displaid as : 18409688740693,8

Are we reaching the limits of doublevalue() ?

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) dimanche 10 février 2019 16:18:10

Nigel,

Doubles are supposed to be accurate to be between 15 and 17 decimal digits. Would trusting only the first 15 digits explain your results?

Here’s a new top to your script, in case it makes things any clearer.

------------------------GENERATE A RANDOM NUMBER------------------------
set x to (random number from -9.9E+24 to 9.9E+24)
set y to ((random number (9.9E+12 - 1)) + 1)
set nmbr to x / y
set decNmbr to nmbrFromDecimals(x, y)
set decNmbr2 to numberToDecimalNumber(nmbr)

------------------------CONVERT THE NUMBER TO READABLE TEXT------------------------
set decimalPlaces to 2 -- Adjust to taste.

return {numberToTextAS(nmbr, decimalPlaces), numberToTextASObjC(nmbr, decimalPlaces), numberToTextASObjC(decNmbr, decimalPlaces), decNmbr's stringValue() as text, numberToTextASObjC(decNmbr2, decimalPlaces), decNmbr2's stringValue() as text, nmbr}

on numberToDecimalNumber(nmbr)
	return current application's NSDecimalNumber's numberWithDouble:nmbr
end numberToDecimalNumber

on nmbrFromDecimals(x, y)
	set xd to current application's NSDecimalNumber's numberWithDouble:x
	set yd to current application's NSDecimalNumber's numberWithDouble:y
	return xd's decimalNumberByDividingBy:yd
end nmbrFromDecimals

Hi Shane.

I see what you mean! All the approaches begin to produce different figures from or after the fifteenth digit. And coercing a 14- or 15-digit AppleScript E number directly to text produces an E number string rounded to thirteen digits!

The NSNumberFormatter approach seems to be the most reliable for the current purpose, with NSDecimalNumber being used for calculations where the precision may go beyond the capability of AS reals and where that precision is actually needed in the final string.

Ironically, the one real-world place where doubles lack sufficient precision in AppleScript is in accurately representing large integers.

I was just looking up the same topic (not sure why I never did in the last 15 years).

I came upon at a much easier solution on Ask Different - it looks like we can just convert a number to a measurement then to string:

1.2345E+9 as inches as string

will return “1234500000”.

Any measurement can be used (kilograms, gallons etc.)

Looks like it saves a few lines of code.

Or is it well known by now?

To give proper credit credit - here’s the link (the answer at the bottom):

https://apple.stackexchange.com/questions/304371/how-do-i-prevent-applescript-from-using-short-numbers

You’ll find it back in message #19 of this thread.

oops missed it - thanks Shane