Sunday, November 15, 2026

Fast Float printString in Squeak too

Squeak is using a variant of quite ancient Guy Steele Dragon4 algorithm for printing Float accurately.
Accurately means the shortest decimal representation that would round trip to the receiver when re-evaluated.
The implementation is quite easy thanks to LargeInteger arithmetic. But that's also a weakness.
LargeInteger arithmetic is slow. Unlike SmallInteger, it also puts lots of pressure on garbage collector.
Since that time, Float printing has made tremendous progress that Squeak/Smalltalk did not leverage yet.
Fortunately, Serge Stinckwich has recently published an awsome package for Pharo:

https://github.com/SergeStinckwich/pharo-ryu

That's fantastic, because on 64 bits images, with SmallInteger in the range $[-2ˆ61,2ˆ60-1]$, they run abysmally faster than Dragon4, particularly the DragobBox algorithm achieve up to 35x for large, tiny or random floats.
While the speed-up is less impressive for short Float entered by humans, it is enornous when serializing results of scientific computations.
We absolutely need that in Squeak. In any Smalltalk dialect in fact.

It's been an occasion to play with Squeak again. Asking Gemini, I discovered another interesting variant known as RussCox. There is an excellent publication explaining the algorithm in details:

https://research.swtch.com/fp

And also a proof of exactness https://research.swtch.com/fp-proof
The algorithm is very elegant and simple.
And it can be extended for printing a fixed number of decimal places too, which makes it a good candidate for more general purpose.

The algorithm is simple, but we do not have the fast 64 bits arithmetic used underneath available thru OpenSmalltalk VM.
For not spoiling efficiency, we need to split the integers into limbs and perform the arithmetic by ourself, this is a boring part that we are happy to delegate to a bot, so I tried to sketch an implementation quickly with help of Gemini web chat.
With the code sitting on github, simply giving the link to Gemini is enough for it to create a subclass of ShortFloatPrinter.
What I like in Gemini is its ability to cite sources. Apart from that, the number of errors in the code produced is rather high compared to Claude Sonnet.
It ranges from off-by-one errors in arithmetics - for example the shifts - to incorrect syntax at worse, like spitting condition ifTrue: ifFalse: without arguments.
But that's bearable, mostly errors that humans could as well produce, if I omit the later.

Gemini did propose a version with 28 bits limbs, with 5 limbs for storing each power of ten approximation with enough precision.
With some iterations to make the algorithm work, then my own optimizations, cherry picking Serge's (and probably Claude's) work, I could obtain a decent speed placing RussCox performance somewhere between Schubfach and DragonBox.
Gemini were not able to improve that speed, the answers overall fall apart and worsen the benchmarks.
So once again, I had to use my own brain and think, thanks to AI to let me do that...
Since the proof tells us that 114 bits of precision is enough for the powers of ten, and since we can use 29-bits limbs without fear of SmallInteger maxVal overflow in multiplications, this enables reducing the powers of ten to 4 limbs 4x29=116.
We can then transform the original 64-bit integer right padding of mantissa, to a more Squeakish friendly 58-bit integer right padding.
This way, we only have to handle a 2 limbs x 4 limbs product in the core uscale function, and with appropriate heuristics ignore the least significant limbs completely.
I again asked the AI to assit me on modifying the code, which it did with quite a lot of confusion mixing 58 and 59 bits everywhere. Deviating from a well known algorithm that certainly were fed into the AI learning phase makes the errors a bit more proeminent.
However, interaction by review and questioning makes the AI correct most of its errors, the rest of corrections is obtained by injecting failing examples: I executed those failures in Squeak debugger and checked in parallel the expectations given by the AI chat, and this revealed to be efficient enough.
Anyway, the debugger is a powerfull tool for diving into implementation details, it is too often underused in other languages. An area where 50 years later, Smalltalk still really shines!

With this variant on 58bits arithmetic that I named RussCox58Printer, the printer now beats DragonBox by about 10% in most areas, with the exception of Float with 2 fractional digits which is about 20% slower than DragonBox.
Well, those marginal costs do not really matter, because 80% of time spent now goes into streaming the outputs...
What is more interesting is simplicity and extensibility for printing with fixed precision as written above.
There is still quite some work before we integrate that in trunk, but we certainly will.

I'm conscious that AI orientation of this blog may upset some members of our community, but I think that it illustrates how the outcomes could have been positive...
At least, the AI retro-contributed a tiny bit to the open source that nurtured it massively, something we might better profit by, before the industry makes the decision to kill the golden goose!
I've put the material on squeaksource for easier squeaking, it is at https://wwww.squeaksource.com/Ryu.html.
I'll try and retro-contribute to the source repository if I find time, but consider this post as a first step of retro-contribution.

No comments:

Post a Comment