PLEAC examples - 2. Numbers

2.1. Checking if a String Is a Valid Number

PicoLisp has only a single numeric type, the bignum.

The PicoLisp function 'format' returns NIL for invalid numbers.

You want to check whether a string represents a valid number.

2.1.1. Check "Integer" numbers

   : (format "12345")
   -> 12345

   : (format "123a5")
   -> NIL
References format

2.1.2. Check "fixed point decimal" numbers

   : (format "1234.5678")
   -> 1235

   : (format "1234,5678" 2 ",")
   -> 123457

   : (format "-1,234.5" 2 "." ",")
   -> -123450
References format

2.2. Comparing Floating-Point Numbers

Floating-point arithmetic isn't precise.

You want to compare two floating-point numbers and know if they're equal when carried out to a certain number of decimal places.

Most of the time, this is the way you should compare floating-point numbers for equality.

Note: PicoLisp has no real floating point numbers; only scaled (fixpoint) integers.

2.2.1. Comparing Floating-Point Numbers

   : (= (format "1234.5678" 2) (format "1234.572" 2))
   -> T
References = format

2.3. Rounding Floating-Point Numbers

You want to round a floating-point value to a certain number of decimal places.

This problem arises as a result of the same inaccuracies in representation that make testing for equality difficult, as well as in situations where you must reduce the precision of your answers for readability.

Note: this is not a problem with PicoLisp fixpoint numbers!

2.3.1. Straight rounding

   : (format "1234.5678")
   -> 1235

   : (format "1234.5678" 2)
   -> 123457
References format

2.3.2. Using a scaling factor

   : (scl 3)
   -> 3

   : (setq A 0.255)
   -> 255

   : (prinl "Unrounded: " (format A *Scl) "^JRounded: " (round A 2))
   Unrounded: 0.255
   Rounded: 0.26
   -> "0.26"
References format scl prinl round setq

2.3.3. A few examples in one

   : (scl 1)
   -> 1

   : (let Fmt (7 7 7 7)
      (tab Fmt "number" "int" "floor" "ceil")
      (for N (3.3 3.5 3.7 -3.3)
         (tab Fmt
            (format N *Scl)
            (format (* 1.0 (/ N 1.0)) *Scl)
            (format (* 1.0 (*/ (- N 0.5) 1.0)) *Scl)
            (format (* 1.0 (*/ (+ N 0.5) 1.0)) *Scl) ) ) )
    number    int  floor   ceil
       3.3    3.0    3.0    4.0
       3.5    3.0    3.0    4.0
       3.7    3.0    3.0    4.0
      -3.3   -3.0   -4.0   -3.0
   -> NIL
References * / for format let scl tab

2.4. Converting Between Binary and Decimal

You have an integer whose binary representation you'd like to print out, or a binary representation that you'd like to convert into an integer.

You might want to do this if you were displaying non-textual data, such as what you get from interacting with certain system programs and functions.

2.4.1. Example

   : (bin 54)
   -> "110110"

   : (bin "110110")
   -> 54
References bin

2.5. Operating on a Series of Integers

You want to perform an operation on all integers between X and Y, such as when you're working on a contiguous section of an array or in any situations where you want to process all integers within a range.

2.5.1. Example

   : (prin "Infancy is: ") (for N 3 (printsp (dec N))) (prinl)
   Infancy is: 0 1 2
   -> NIL

   : (prin "Toddling is: ") (println 3 4)
   Toddling is: 3 4
   -> 4

   : (prin "Childhood is: ") (apply println (range 5 12))(prin "Childhood is: ") (apply println (range 5 12))
   Childhood is: 5 6 7 8 9 10 11 12
   Childhood is: 5 6 7 8 9 10 11 12
   -> 12

   : (prin "Childhood is: ") (mapc printsp (range 5 12)) (prinl)
   Childhood is: 5 6 7 8 9 10 11 12
   -> NIL

   : (prin "Childhood is: ") (for (N 5 (>= 12 N) (inc N)) (printsp N)) (prinl)
   Childhood is: 5 6 7 8 9 10 11 12
   -> NIL
References apply dec for inc mapc prin prinl println printsp range

2.6. Working with Roman Numerals

You want to convert between regular numbers and Roman numerals.

You need to do this with items in outlines, page numbers on a preface, and copyrights for movie credits.

2.6.1. Convert 10 based number to Roman number

   : (de roman (N)
      (pack
         (make
            (mapc
               '((C D)
                  (while (>= N D)
                     (dec 'N D)
                     (link C) ) )
               '(M CM D CD C XC L XL X IX V IV I)
               (1000 900 500 400 100 90 50 40 10 9 5 4 1) ) ) ) )
   -> roman

   : (prinl "Number 15 in Roman is " (roman 15))
   Number 15 in Roman is XV
   -> "XV"
References de dec link make mapc pack while

2.6.2. Convert Roman number to 10 based number

   : (de arabic (R)
      (let N 0
         (for (L (chop (uppc R))  L)
            (find
               '((C D)
                  (when (head C L)
                     (cut (length C) 'L)
                     (inc 'N D) ) )
               '`(mapcar chop '(M CM D CD C XC L XL X IX V IV I))
               (1000 900 500 400 100 90 50 40 10 9 5 4 1) ) )
         N ) )
   -> arabic

   : (prinl "Roman number XV is " (arabic (roman 15)))
   Roman number XV is 15
   -> 15
References chop cut de find for head inc length let mapcar prinl uppc when

2.7. Pseudo Random Numbers (or Booleans)

You want to make random numbers in a given range, inclusive, such as when you randomly pick an array index, simulate rolling a die in a game of chance, or generate a random password.

The first example below will give exact the same values for each new PicoLisp run, because there is no seed given.

2.7.1. Example

   : (rand)
   -> 643875838651014379

   : (rand 1 6)  # Dice
   -> 3

   : (rand 900000 999999)
   -> 989901

   : (rand T)  # Generate Random Boolean (T or NIL)
   -> NIL

   : (rand T)
   -> T

   : (setq Password
      (pack
         (head 8
            (by '(NIL (rand)) sort
               (conc
                  (chop "!@$%^&*")
                  (mapcar char
                     (conc
                        (range `(char "A") `(char "Z"))
                        (range `(char "a") `(char "z"))
                        (range `(char "0") `(char "9")) ) ) ) ) ) ) )
   -> "pqo9wZi^"
References by char chop conc head mapcar pack rand range setq sort

2.8. Generate Different Random Numbers / Booleans

Every time you run your program you get the same set of "random" numbers (as in the previous example).

You want to produce different random numbers each time. This is important in nearly every application of random numbers, especially games.

2.8.1. Example

   : (seed 42)
   -> -2104627054

   : (seed "Hello world")
   -> -223492616

   : (seed (time))
   -> 1820154933
References seed time

2.9. Making Numbers Even More Random

You want to generate numbers that are more random than shown before.

Limitations of your random number generator seeds will sometimes cause problems.

The sequence of pseudo-random numbers may repeat too soon for some applications.

2.9.1. Example

   : (in "/dev/urandom" (rd 12))
   -> 50416291644794614409246112035
References in rd

2.10. Generating Biased Random Numbers

You want to pick a random value where the probabilities of the values are not equal (the distribution is not even).

You might be trying to randomly select a banner to display on a web page, given a set of relative weights saying how often each banner is to be displayed.

Alternatively, you might want to simulate behavior according to a normal distribution (the bell curve).

2.10.1. Example

   : (load "@lib/math.l")
   -> atan2

   : (de rand2 ()
      (rand `(inc -1.0) `(dec 1.0)) )
   -> rand2

   : (de gaussianRand ()
      (use (U1 U2 W)
         (while
            (>=
               (setq W
                  (+
                     (*/ (setq U1 (rand2)) U1 1.0)
                     (*/ (setq U2 (rand2)) U2 1.0) ) )
               1.0 ) )
         (setq W (sqrt (*/ 1.0 -2.0 (log W) W)))
         (*/ U2 W 1.0) ) )
   -> gaussianRand

   : (prinl
      "You have been hired at $"
      (round (+ 25.0 (* 2 (gaussianRand))) 2) )
   You have been hired at $21.09
   -> "21.09"
Library functions used: math:log

References + * */ >= de dec inc load prinl rand round setq sqrt use while

2.11. Doing Trigonometry in Degrees, not Radians

You want your trigonometry routines to operate in degrees instead of radians.

2.11.1. Example

   : (load "@lib/math.l")
   -> atan2

   : (de deg2rad (Deg)
      (*/ Deg pi 180.0) )
   -> deg2rad

   : (de rad2deg (Rad)
      (*/ Rad 180.0 pi) )
   -> rad2deg

   : (deg2rad 90)
   -> 2

   # Well, that seems incorrect ... why is this?
   # It has to do with the "fixpoint" numbers in PicoLisp
   # To correctly use "fixpoint" numbers, you do need a decimal point!

   : (deg2rad 90.0)
   -> 1570797

   # See? Now it looks a lot more than the correct answer!
   # But hey, I thought the answer should be much smaller??

   : (format (deg2rad 90.0) *Scl)
   -> "1.570797"

   # Wow! That does the trick!
   # Why is this?
   # Well, the "fixpoint" numbers use a scaling factor, which is stored
   #   in a special (global) variable called *Scl.
   # Depending on its value the correct "fixpoint" number can be displayed.

   : *Scl
   -> 6

   # In this case *Scl has a value of 6, which means that the decimal point
   # should be shifted 6 positions to the left.
   # Hence the outcome of 1570797 is now shown mathematically correct as: 1.570797.

   # You can change the value of *Scl by using 'setq'

   : (setq *Scl 5)
   -> 5

   : *Scl
   -> 5

   # Or by using the function 'scl'

   : (scl 4)
   -> 4

   : *Scl
   -> 4
References *Scl */ format load scl

2.12. A few Trigonometric Functions

You want to calculate values for trigonometric functions like sine, tangent, or arc-cosine.

2.12.1. Example

   : (load "@lib/math.l")
   -> atan2

   : (format (cos 0.333333) *Scl)
   -> "0.944957"

   : (format (acos 0.944957) *Scl)
   -> "0.333333"

   : (format (tan pi/2) *Scl)
   -> "3060023.306953"
The functions 'acos', 'cos' and 'tan' will be referenced in the future.

References format load

2.13. Logarithms

You want to take a logarithm in various bases.

2.13.1. Example

   : (load "@lib/math.l")
   -> atan2

   # Function 'log' takes the NATURAL logarithm (base 'e')!

   : (format (log 10.0) *Scl)
   -> "2.302585"

   : (de logBase(Base Val)
      (*/ (log Val) 1.0 (log Base)) )
   -> logBase

   # Take the 10 based logarithm of 10000 using function 'logbase'

   : (format (logBase 10.0 10000.0) *Scl)
   -> "4.000000"
The function 'log' will be referenced in the future.

References *Scl */ de format load

2.14. Matrix multiplication

You want to multiply a pair of two-dimensional arrays. Mathematicians and engineers often need this.

2.14.1. Example

   : (de mmult (Mat1 Mat2)
      (unless (= (length Mat1) (length (car Mat2)))
         (quit "IndexError: matrices don't match") )
      (mapcar
         '((Row)
            (apply mapcar Mat2
               '(@ (apply + (mapcar * Row (rest)))) ) )
         Mat1 ) )
   -> mmult

   : (mmult
      '((3 2 3) (5 9 8))
      '((4 7) (9 3) (8 8)) )
   -> ((54 51) (165 126))
References = + * apply car de length mapcar rest unless

2.15. Complex numbers

Your application must manipulate complex numbers, as are often needed in engineering, science, and mathematics.

2.15.1. Example

   : (load "@lib/math.l")
   -> atan2

   : (de addComplex (A B)
      (cons
         (+ (car A) (car B))        # Real
         (+ (cdr A) (cdr B)) ) )    # Imag
   -> addComplex

   : (de mulComplex (A B)
      (cons
         (-
            (*/ (car A) (car B) 1.0)
            (*/ (cdr A) (cdr B) 1.0) )
         (+
            (*/ (car A) (cdr B) 1.0)
            (*/ (cdr A) (car B) 1.0) ) ) )
   -> mulComplex

   : (de invComplex (A)
      (let Denom
         (+
            (*/ (car A) (car A) 1.0)
            (*/ (cdr A) (cdr A) 1.0) )
         (cons
            (*/ (car A) 1.0 Denom)
            (- (*/ (cdr A) 1.0 Denom)) ) ) )
   -> invComplex

   : (de negComplex (A)
      (cons (- (car A)) (- (cdr A))) )
   -> negComplex

   : (de sqrtComplex (A)
      (let
         (R (sqrt (+ (* (car A) (car A)) (* (cdr A) (cdr A))))
            Y (sqrt (* (- R (car A)) 0.5))
            X (*/ (cdr A) 0.5 Y) )
         (cons  # Return both results
            (cons X Y)
            (cons (- X) (- Y)) ) ) )
   -> sqrtComplex

   : (de fmtComplex (A)
      (pack
         (round (car A) (dec *Scl))
         (and (gt0 (cdr A)) "+")
         (round (cdr A) (dec *Scl))
         "i" ) )
   -> fmtComplex

   # Calculate: (3 + 5i) * (2 + -2i) = 16 + 4i

   : (let (A (3.0 . 5.0)  B (2.0 . -2.0))
      (prinl "c = " (fmtComplex (mulComplex A B))) )
   c = 16.00000+4.00000i

   # Calculate: sqrt(3 + 4i) = 2 + 1i

   : (let D (3.0 . 4.0)
      (prinl "sqrt(" (fmtComplex D) ") = " (fmtComplex (car (sqrtComplex D)))) )
   sqrt(3.00000+4.00000i) = 2.00000+1.00000i
References + - * */ and car cdr de dec cons gt0 let load pack prinl round sqrt

2.16. Converting Between Decimal, Octal and Hexadecimal

You want to convert a string (e.g. "0x55" or "0755") containing an octal or hexadecimal number to the correct decimal number and vice versa.

2.16.1. Example

   : (de conv ()
        (println "Please enter a number:")
        (println "   - in decimal (e.g. 23)")
        (println "   - in octal   (e.g. 023)")
        (println "   - or in hex  (e.g. 0x23)")
        (let Num (in NIL (clip (line)))
           (setq Num
              (if2 (= "0" (car Num)) (= "x" (cadr Num))
                 (hex (cddr Num))
                 (oct (cdr Num))
                 NIL
                 (format Num) ) )
           (prinl Num " " (hex Num) " " (oct Num)) ) )
   -> conv

   : (conv)
   "Please enter a number:"
   "   - in decimal (e.g. 23)"
   "   - in octal   (e.g. 023)"
   "   - or in hex  (e.g. 0x23)"
   23
   23 17 27
   -> "27"

   : (conv)
   "Please enter a number:"
   "   - in decimal (e.g. 23)"
   "   - in octal   (e.g. 023)"
   "   - or in hex  (e.g. 0x23)"
   023
   19 13 23
   -> "23"

   : (conv)
   "Please enter a number:"
   "   - in decimal (e.g. 23)"
   "   - in octal   (e.g. 023)"
   "   - or in hex  (e.g. 0x23)"
   0x23
   35 23 43
   -> "43"
References = car cadr de format hex if2 in let oct prinl println setq

2.17. Put commas in numbers as 1000th separators

You want to output a number with commas in the right place.

People like to see long numbers broken up in this way, especially in reports.

2.17.1. Example

   : (let Cnt -1740525205
      (prinl
         "Your web page received "
         (format Cnt 0 "." ",")
         " accesses last month." ) )
   Your web page received -1,740,525,205 accesses last month.
   -> " accesses last month."
References format let prinl

2.18. Printing Correct Plurals

You're printing something like "It took $time hours", but "It took 1 hours" is ungrammatical.

You would like to get it right.

2.18.1 Plural forms of time difference

   # With a time unit of 1 in variable Time

   : (setq Time 1)
   -> 1

   : (prinl "It took " Time " hour" (unless (= 1 Time) "s"))
   It took 1 hour
   -> NIL

   : (prinl
      Time
      " hour" (unless (= 1 Time) "s")
      (if (= 1 Time) " is" " are")
      " enough." )
   1 hour is enough.
   -> " enough."

   : (prinl "It took " Time " centur" (if (= 1 Time) "y" "ies"))
   It took 1 century
   -> "y"

   # With a time unit of 2 in variable Time

   : (setq Time 2)
   -> 2

   : (prinl "It took " Time " hour" (unless (= 1 Time) "s"))
   It took 2 hours
   -> "s"

   : (prinl
      Time
      " hour" (unless (= 1 Time) "s")
      (if (= 1 Time) " is" " are")
      " enough." )
   2 hours are enough.
   -> " enough."

   : (prinl "It took " Time " centur" (if (= 1 Time) "y" "ies"))
   It took 2 centuries
   -> "ies"
References = if prinl setq unless

2.18.2 Plurals of nouns

   : (de nounPlural (Str)
      (let (S (chop Str)  @A)
         (cond
            ((find tail '((s s) (p h) (s h) (c h) (z)) (circ S))
               (pack Str "es") )
            ((tail '(f f) S) (pack S "s"))
            ((match '(@A f) S) (pack @A "ves"))
            ((tail '(e y) S) (pack S "s"))
            ((match '(@A y) S) (pack @A "ies"))
            ((match '(@A i x) S) (pack @A "ices"))
            ((or (tail '(s) S) (tail '(x) S))
               (pack S "es") )
            (T (pack S "s")) ) ) )
   -> nounPlural

   : (for S
      (quote
         fish fly ox
         species genus phylum
         cherub radius jockey
         index matrix mythos
         phenomenon formula )
      (prinl "One " S ", two " (nounPlural S) ".") )
   One fish, two fishes.
   One fly, two flies.
   One ox, two oxes.
   One species, two specieses.
   One genus, two genuses.
   One phylum, two phylums.
   One cherub, two cherubs.
   One radius, two radiuses.
   One jockey, two jockeys.
   One index, two indexes.
   One matrix, two matrices.
   One mythos, two mythoses.
   One phenomenon, two phenomenons.
   One formula, two formulas.
   -> "."
References chop cond de find for let match or pack prinl quote tail

2.19. Calculate Prime Factors

The following program takes one or more integer arguments and determines the prime factors.

2.19.1. Example

First create a script called 'bigfact' as shown here:
#!/usr/bin/picolisp /usr/lib/picolisp/lib.l

(load "@lib/misc.l")

(de factor (N)
   (make
      (let (D 2  L (1 2 2 . (4 2 4 2 4 6 2 6 .))  M (sqrt N))
         (while (>= M D)
            (if (=0 (% N D))
               (setq M (sqrt (setq N (/ N (link D)))))
               (inc 'D (pop 'L)) ) )
         (link N) ) ) )

(while (opt)
   (let? N (format @)
      (let Factors (factor N)
         (tab (-11 1 -60)
            N
            " "
            (ifn (cdr Factors)
               "PRIME"
               (glue " "
                  (mapcar
                     '((L)
                        (if (cdr L)
                           (pack (car L) "**" (length L))
                           (car L) ) )
                     (by prog group Factors) ) ) ) ) ) ) )

(bye)
Execute the following commands in the Linux shell:
chmod 777 bigfact
./bigfact 17 60 125 239322000000000000000000
The ouput should be similar to:
17          PRIME
60          2**2 3 5
125         5**3
239322000000000000000000 2**19 3 5**18 39887
References % / =0 >= by bye car cdr de format glue group if ifn inc length let let? link load make mapcar pack pop prog setq sqrt tab while

https://picolisp.com/wiki/?pcepleacnumbers

23jun18    ArievW