We don’t know how big integer values get, you do know, but using a do shell script has the same boundaries as my first example. meaning that integer values become 2 ^ 53 (2 ^ 63 for bash) or larger it will lose it’s precision.
First of all, let me say that when I wanted to break up the number, then it was because I assumed the number to be used in a series for catalouging something, and not say a “running-number” used to say; indexing a transaction.
As such, I saw that the number be used more as a symbolic number to denote, some kind of order, and then I felt, and still feel that it is better to use another datastructure than an integer to represent that “model-object”.
By the way: the Ariadne incident, (we should all be happy for the fact that there weren’t humans on board) due to using a library built for 32-bit on a 64 bit platform actually is something that I (like many others) have learned something from, when it comes to having old software run on a new platform.
Back to topic: I think Nigel’s solution is the best when done in AppleScript since he throws an error, I have just tried Nik’s expr, and that works with large integers, and is probably the best solution if the numbers that needs to be incremented is more likely to be above the 530 million magnitude, there is however a certain price to pay with regards to speed.
I haven’t tried this, but maybe the seq command are up for the job, and can deliver faster, but then again: you have to precompute the end value.
Why not make a composite solution Nik, where you implement your do shell script solution where Nigel’s throws an error?
The main difference between Nigel’s version and mine is that I’ll make “99” + 1 just into “100” while Nigel’s version will throw an error. However Nigel’s script, like mine, can handle larger numbers than bash, so there is still gap in best of both worlds. To make Nigel’s script work like mine you can simply add a 1 to the beginning of the string when s is not 0 instead of throwing an error, but that only works when you increment the value by 1.
Do you know the reason? If you take a look at Nik’s script you’ll see that Nik doesn’t want the error to be thrown for that particular reason.
It’s an 64 bit integer limitation but it’s still an limitation. But if the TS is okay with these boundaries, or if the given number is a 64 bit integer (which I doubt because it has leading zeros), then the problem is solved. Still I think it’s good to show a version like mine and Nigel that has no boundaries whatsoever, only the length of an string or list meaning the same boundaries as the given value type.
I agree totally, about the goodness to see the solution. On the other hand, you should get about 18 digits or numbers up around this magnitude: (2^62) 4611686018427387904 and seq works in this area, I think it ceases to work at 2^63 -1.
IMHO the OP should have enough options to implement a suitable solution.
Edit
bc is the option we haven’t touched, I can’t recall it, but maybe if you set the scale factor great enough, you can have arbitrarily large numbers with leading zeroes returned. Another path well worth investigating.
Edit: I’ve updated my post #7 so it only works with string objects to stick to the same boundaries as the given string value. And removed some duplicate code.
Here’s an alternative using an ASObjC library. It uses the NSDecimalNumber class, which is designed to work around the problems DJ mentioned. It still has a limit, but it’s 38 digits (so input should be limited to 37 digits, excluding leading zeros).
Put this in the lib:
use framework "Foundation"
on incrementString:numberString
set theLength to length of numberString -- needed to calculate number of leading zeros
set theNum to current application's NSDecimalNumber's decimalNumberWithString:numberString -- make decimal number
set theNum to theNum's decimalNumberByAdding:(current application's NSDecimalNumber's one()) -- add 1
set newString to theNum's description() -- turn back to string
set newStringLength to newString's |length|() as integer
set leadingZeros to current application's NSString's |string|() -- build leading zero string
set leadingZeros to leadingZeros's stringByPaddingToLength:(theLength - newStringLength) withString:"0" startingAtIndex:0
return (leadingZeros's stringByAppendingString:newString) as text -- add leading zeros to string
end incrementString:
And call it like this:
use theLib : script "<name of lib>"
set myVar to "00336203400000000000000000000000987654321"
theLib's incrementString:myVar
--> "00336203400000000000000000000000987654322"
That is a lot faster than bc, which can handle integers up to 40 digits. Shane’s solution gets my vote, now that I have seen it. (I guess blend3 is familiar with using an ASOC library.)
use framework "Foundation"
on incrementString:numberString
set theBits to {}
repeat
set theLength to length of numberString
if theLength > 38 then
set lastBit to text -37 thru -1 of numberString
set numberString to text 1 thru -38 of numberString
set lastBitLength to 37
set lastString to false
else
set lastBit to numberString
set numberString to ""
set lastBitLength to theLength
set lastString to true
end if
set theNum to current application's NSDecimalNumber's decimalNumberWithString:lastBit -- make decimal number
set theNum to theNum's decimalNumberByAdding:(current application's NSDecimalNumber's one()) -- add 1
set newString to theNum's description() as text -- turn back to string
set newStringLength to length of newString
if newStringLength > lastBitLength then -- need to carry one
set carryOne to true
set beginning of theBits to text 2 thru -1 of newString
else
set carryOne to false
set leadingZeros to current application's NSString's |string|() -- build leading zero string
set leadingZeros to leadingZeros's stringByPaddingToLength:(lastBitLength - newStringLength) withString:"0" startingAtIndex:0
set beginning of theBits to (leadingZeros's stringByAppendingString:newString) as text -- add leading zeros to string
end if
if lastString then exit repeat
if not carryOne then
set beginning of theBits to numberString
exit repeat
end if
end repeat
set saveTID to AppleScript's text item delimiters
set AppleScript's text item delimiters to {""}
set theBits to theBits as text
set AppleScript's text item delimiters to saveTID
return theBits
end incrementString:
Called like:
use theLib : script "<lib name>"
use scripting additions
set myVar to "0999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
set x to theLib's incrementString:myVar
-->"1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
‘description’ has to be changed to ‘|description|’ for Shane’s script to compile in AppleScript Editor.
Comparing the performances of DJ’s script (post #7, in the version present as I write), mine (post #13), and Shane’s (post #26):
¢ With blend3’s example text “0033620340000”, DJ’s is the fastest, consistently just beating mine by a tiny amount. Shane’s takes about four-and-a-half to five-and-a-quarter times as long.
¢ With the pathological text example given with Shane’s script, Shane’s script wins hands down for speed ” BUT, interestingly, it crashes with error number -4960 if it’s tested over too many iterations of the timing loop. It survives being repeated 52 times, it may or may not survive 53 to 55 repeats, it always crashes with 56 or more repeats. With the shorter text, the crash point’s somewhere between 5000 and 10000 iterations. (Only the call to the handler is included in the repeat in my tests.)
It is awkward not to be fully fluent in ASOC, because Shane’s first handler, can be shortened in Objective-C. But I have no idea if this is possible with ASOC.
I will stay tuned because I have a script using ASObjC in a library which issue the same error numbers -4960 at first run then behave flawlessly at 2nd and higher attempts.
Yvan KOENIG (VALLAURIS, France) mardi 11 mars 2014 17:43:10
I’m not surprised there’s a big difference. Assuming you’re incrementing by 1 in a repeat loop, 90% of cases will never need to look beyond the last character, whereas the ASObjC method will take pretty much the same time regardless – “0999999999999” and “000000000000” will take the same time. So 90% of the time there’s a lot of unnecessary overhead. If I add one line like this at the beginning:
if character -1 of numberString is in "012345678" then return (text 1 thru -2 of numberString & (((character -1 of numberString) as integer) + 1))
the times drop drastically, because only 10% of numbers get any further. A second line for character -2 would reduce it to 1%.
Very odd. I just ran this with no problem:
use theLib : script "NSNumbers"
set myVar to "0999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
repeat 100000 times
set myVar to theLib's incrementString:myVar
end repeat
But you’re hard-coding the length with the 12 there. And if you use a variable to build the format string, you’re going to get compiler warnings about what a dangerous thing that is to do.
It was just a quick demo, so I didn’t specify an argument for the length, a * is a placeholder for the length in a format string, so, it could look like something like below, in order to handle variable length. (The intLen, could be passed in as a parameter for the length.) I’m not sure if how you do this, if you take the integerValue from the passed in number, or whatever, in order to make this to work with ASOC. :rolleyes:
Edit
I use -Wevertyhing, and I don’t get any compiler warnings with clang (when compiling as Objective-C).