29

According to this article: german C64 Wiki article about the POS() command POS(π) is 20 % faster.
Although in my experience it is circa 28 % faster. Is there a specific reason for this immense speed boost?

  • 9
    Probably because the constant 0 needs to be converted into floating-point, while Pi is already stored in ROM in floating-point form. – Ross Ridge Sep 27 '17 at 15:13
  • 7
    How does it compare to POS(0.0)? – wizzwizz4 Sep 27 '17 at 15:51
  • 17
    If it helps others with context: POS returns the cursor column. It takes an argument but doesn't use it — it's a dummy. – Tommy Sep 27 '17 at 17:28
  • 2
    @traal It's an implementation detail, also known as a feature. – wizzwizz4 Sep 27 '17 at 18:20
  • 2
    I think each digits to left of the decimal point entails a multiply by ten, and each digit to the right requires a divide by ten. A bare decimal point doesn't require either. And π simply instructs the compiler to load a hard-coded constant with no further logic required. – supercat Sep 27 '17 at 18:23
36

That interpreter apparently parses the source text, or at least the numerical literal values, at every execution. π is a single-byte magic token, therefore, as soon as it is recognized, it is immediately substituted with the value of Pi, and nothing else needs to be done.

When a byte that might occur in a number is parsed, the number parsing routine begins. Its execution time will depend on the number of characters in the string representing the number. When parsing a number, each digit in the integer part requires multiplying the number parsed so far by 10 and adding the value of the digit. The fractional part is usually parsed as an integer, then divided by the appropriate power of 10. The division operation is expensive, and it makes sense to check if the dividend is 0 before dividing — that's why .0 is much faster than .1.

That's why, as the German Wiki article says, the next best execution time is for POS(.): the number parsing routine starts, but there are no digits to parse, and it devolves to skipping the period and adding the initial (zero) values of the integer and the fractional part, then for a variable with a single-character name (name lookup), and then for the number 0 (will cause the computation of 0 * 10 + 0). Longer numbers will take even longer.

  • 2
    I'm pretty sure there was an HP BASIC that, after each RUN, would tokenise each line the first time it is encountered, replacing the original in memory, then detokenise only when the program ended. It's a shame Microsoft et al didn't learn from that. – Tommy Sep 27 '17 at 18:23
  • 3
    you definitely can, and some BASICs do. But they're the sort that tokenise as you type, never keeping a plain-text copy around. – Tommy Sep 27 '17 at 18:29
  • 4
    @Tommy: Lines are tokenized in MS-BASIC, but sequences of digits are kept as written. In some BASIC dialects, if one writes 10 X=1.20 and then types LIST the program would list back as 10 X=1.2, but Commodore BASIC generally tries to list programs in such a way as to match what was typed. – supercat Sep 27 '17 at 18:29
  • 3
    @Kaz Or an interpreter optimized to fit in the available memory, when the choice is between a slow ("crap") interpreter or no interpreter at all. I'll rephrase that statement. – Leo B. Sep 27 '17 at 22:31
  • 8
    Don't forget that Microsoft BASIC was designed to fit in a very small menory footprint because that's all there was. The original PET had 4 or 8K RAM and BASIC had to fit into 8K ROM. The Altair BASIC from which Commodore BASIC was derived had to fit in even less space. You can visualise the entire C64 memory map losslessly with a 256 x 256 GIF image. People today forget how constrained memory was back in the early 80's. – JeremyP Sep 28 '17 at 9:01
1

The C64 BASIC uses a floating point as basic format for everything. So even if a function requires an integer argument, the number is parsed into a floating point number first and then converted into an integer. When giving the constant π, a pre-stored floating point value of 3.14159265 is used, so the time for the conversion of the number to floating point is omitted. For POS(), the argument is a dummy argument, so its value does not matter to the result, which makes POS(π) easily possible. But even for other functions like POKE giving π as an argument (which will then be converted to 3) would be a bit faster than writing a 3.

  • I wonder how much extra code would have been required to say that whenever the exponent byte is zero, the next four bytes will be interpreted as a signed integer, and operations whose operands and results are both signed integers yield signed integer results? Operations that can't be handled that way would simply need to start with a call to a function that would convert operands to normalized format, but most operations would execute much faster because they could skip the normalization steps. – supercat Jan 14 at 20:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.