home *** CD-ROM | disk | FTP | other *** search
/ Columbia Kermit / kermit.zip / ckscripts / twoscomplementv3 < prev    next >
Text File  |  2020-01-01  |  14KB  |  411 lines

  1. # TWOSCOMPLEMENT with Large Numbers...
  2. #
  3. # Kermit macros to convert signed decimal numbers to two's complement
  4. # hexadecimal notation and vice versa:
  5. #
  6. # DECTOHEX d n
  7. #   Converts the decimal argument d to n-bit two's complement hex format;
  8. #   n should be 4, 8, 16, 32, 64, or 128.
  9. #   Example: "DECTOHEX -1 32" returns "FFFFFFFF".
  10. #
  11. # HEXTODEC h
  12. #   Converts the hexadececimal two's-complement argument to signed decimal. 
  13. #   Example: "HEXTODEC FFFFFFFF" returns "-1".
  14. #
  15. # This version does the big calculations with strings, rather than machine
  16. # integers, so is able (in principle) to handle integers of any size.  As
  17. # written it accommodates word sizes of 4, 8, 16, 32, 64, and 128.  The main
  18. # application is on 32-bit hardware or Kermit versions (notably K95) built
  19. # with 32-bit memory models when it is necessary to do decimal/hexadecimal
  20. # conversions of larger numbers.  Since this version uses strings and not
  21. # built-in machine arithmetic, it is considerably slower than the
  22. # straightforward version would be, but still tolerable on modern hardware.
  23. #
  24. # Requires C-Kermit 8.0 or later or Kermit 95 2.0 or later.
  25. #
  26. # Search terms:
  27. #   BIGNUM decimal/hexadecimal format conversion macro implementation;
  28. #   string arithmetic; manipulation of large numbers as strings.
  29. #   Decimal/binary conversion of big numbers.
  30. # F. da Cruz, Columbia University, Dec 2007 - Jan 2008
  31. # Last update: Wed Jan  9 12:21:06 2008
  32.  
  33. # Largest positive integer for the supported word sizes
  34.  
  35. .maxint<4> = 7
  36. .maxint<8> = 127
  37. .maxint<16> = 32767
  38. .maxint<32> = 2147483647
  39. .maxint<64> = 9223372036854775807
  40. .maxint<128> = 340282366920938463463374607431768211455
  41.  
  42. # Ditto plus one (because we can't necessarily do arithmetic on big numbers)
  43.  
  44. .maxplusone<4> = 8
  45. .maxplusone<8> = 128
  46. .maxplusone<16> = 32768
  47. .maxplusone<32> = 2147483648
  48. .maxplusone<64> = 9223372036854775808
  49. .maxplusone<128> = 340282366920938463463374607431768211456
  50.  
  51. # Powers of 16 up to 32 (128 bits)
  52.  
  53. def POWEROF16 {  # This is a macro to keep the array private.
  54.     local &p
  55.     dcl \&p[] = 16 -
  56.      256 -
  57.      4096 -
  58.      65536 -
  59.      1048576 -
  60.      16777216 -
  61.      268435456 -
  62.      4294967296 -
  63.      68719476736 -
  64.      1099511627776 -
  65.      17592186044416 -
  66.      281474976710656 -
  67.      4503599627370496 -
  68.      72057594037927936 -
  69.      1152921504606846976 -
  70.      18446744073709551616 -
  71.      295147905179352825856 -
  72.      4722366482869645213696 -
  73.      75557863725914323419136 -
  74.      1208925819614629174706176 -
  75.      19342813113834066795298816 -
  76.      309485009821345068724781056 -
  77.      4951760157141521099596496896 -
  78.      79228162514264337593543950336 -
  79.      1267650600228229401496703205376 -
  80.      20282409603651670423947251286016 -
  81.      324518553658426726783156020576256 -
  82.      5192296858534827628530496329220096 -
  83.      83076749736557242056487941267521536 -
  84.      1329227995784915872903807060280344576 -
  85.      21267647932558653966460912964485513216 -
  86.      340282366920938463463374607431768211456
  87.     .\&p[0] = 1 # Because initialers start at 1.
  88.     if not integer \%1 end 1 "\%0: NON-NUMERIC ARGUMENT '%1'"
  89.     if ( < \%1 0 || > \%1  32 ) \%1 end 1 "\%0: ARGUMENT OUT OF RANGE '\%1'"
  90.     return \&p[\%1]
  91. }
  92.  
  93. # Macro BINTOHEX converts a binary string to hex.
  94. #   \%1 = binary string to be converted
  95. #   \%2 = word size in bits
  96. #   Returns hex string
  97. #
  98. define BINTOHEX {
  99.     undef \%6                                # Result accumulator
  100.     .\%1 := \flpad(\%1,\%2,0)                # Pad to size if necessary
  101.     for \%9 1 \%2 4 {                        # Do four bits at at a time
  102.         .\%8 := \fsubstr(\%1,\%9,4)          # Get chunk of 4
  103.         if not def \%8 break                 # Make sure we have one
  104.         .\%7 := \fradix(\%8,2,16)            # Convert to Hex digit
  105.         .\%6 := \%6\%7                       # Accumulate
  106.     }
  107.     return \%6
  108. }
  109.  
  110. # HEXTOBIN converts hex string to binary
  111. define HEXTOBIN {
  112.     undef \%7
  113.     for \%9 1 \flen(\%1) 1 {
  114.         .\%7 := \%7\flpad(\fradix(\:(\%1[\%9:1]),16,2),4,0)
  115.     }
  116.     return \%7
  117. }
  118.  
  119. # Macro DTOB converts a decimal string to binary.
  120. #  \%1 = decimal string to be converted
  121. #  Returns binary string
  122. #
  123. def DTOB {                                   # Convert decimal string to binary
  124.     local div2 result remainder
  125.     def DIV2 {                               # Divide decimal string by two
  126.         local \%i
  127.         undef \%6 \%7 result
  128.         for \%i 1 \flen(\%1) 1 {             # One digit at a time.
  129.             .\%9 := \%7\:(\%1[\%i:1])        # Get a digit.
  130.             .\%8 ::= \%9/2                   # Divide it by 2
  131.             .\%7 ::= \%9%2                   # Get remainder
  132.             .\%6 := \%6\%8                   # Accumulate result
  133.         }
  134.         .result := \%6                       # These are just
  135.         .remainder := \%7                    # for clarity...
  136.     }
  137.     while true {                             # Convert by repetitive division.
  138.         div2 \%1                             # Divide decimal number by 2
  139.         .\%6 := \m(remainder)\%6             # Accumulate result...
  140.         .\%1 := \m(result)
  141.         if not \fverify(0,\%1) break         # Quit when there's nothing left.
  142.     }
  143.     return \%6
  144. }
  145.  
  146. # Macro TWOSCOMPLEMENT converts binary string into its two's complement
  147. #  \%1 = binary string
  148. #  \%2 = number of bits (if not given, length of \%1 is used)
  149. #  Returns the 2's complement of the given binary string
  150. #
  151. define TWOSCOMPLEMENT {
  152.     if not def \%2 .\%2 = \flen(\%1)         # Supply length if not given
  153.     .\%9 := \flpad(\%1,\%2,0) }              # Left-pad to desired length
  154.     .\%8 ::= \frindex(1,\%9) - 1             # Find first 1 from the right
  155.     if == \%8 -1 return \frepeat(0,\%2 / 4)  # Watch out for negative 0
  156.     .\%7 := \fsubstr(\%9,1,\%8)              # Split string here
  157.     .\%6 := \fsubstitute(\%7,01,10)          # Complement bits in left part
  158.     .\%5 := \%6\fsubstr(\%9,\%8+1)           # Put back with right part
  159.     return \%5
  160. }
  161.  
  162. # Macro DECTOHEX converts a signed decimal number to 2's complement hexadecimal
  163. # using the macros defined above as workers.
  164. #   \%1 = decimal number string (default 0)
  165. #   \%2 = word size in bits
  166. #         (must be a power of two, 4 or greater, default 32, max 128)
  167. #   Returns the hex result of the requested size.
  168. #
  169. define DECTOHEX {
  170.     local digits legal
  171.     .legal = :4:8:16:32:64:128:              # Legal word sizes for dec-to-hex
  172.     if not def \%1 .\%1 = 0                  # Supply default if no arg given
  173.     if not numeric \%1 return NOT_A_NUMBER:\%1  # Check that arg is a number
  174.     if not def \%2 .\%2 := 32                   # Use 32 bits if no second arg
  175.     if not \findex(:\%2:,\m(legal)) end 1 "UNSUPPORTED WORD SIZE - \%2"
  176.     .digits := \flen(\m(maxint<\%2>))           # Number of digits in it
  177.  
  178.     if eq "\fsubstr(\%1,1,1)" "+" .\%1 := \fsubstr(\%1,2) # strip any + sign
  179.     if not eq "\fsubstr(\%1,1,1)" "-" {      # If argument is not signed...
  180.         if lgt \flpad(\%1,\m(digits),0) \m(maxint<\%2>) return OVERFLOW
  181.         dtob \%1                             # Convert from decimal to binary
  182.         bintohex \v(return) \%2              # And from binary to hex
  183.         return \flpad(\v(return),(\%2/4),0)  # Return padded result
  184.     }
  185.     .\%1 := \fsubstr(\%1,2)                  # Negative number - remove sign
  186.     .\%1 := \flpad(\%1,\flen(\m(maxint<\%2>)),0) # Must use lexical comparison
  187.     if llt \m(maxplusone<\%2>) \%1 return UNDERFLOW # Check magnitude
  188.     dtob \%1                                 # Convert to binary
  189.     .\%5 := \fexec(twoscomplement \v(return) \%2) # Get two's complement
  190.     .\%4 := \fexec(bintohex \%5 \%2)         # Convert to hex
  191.     return \%4
  192. }
  193.  
  194. # IS32BIT checks if positive number fits in 32 bits.
  195. # Could be used for optimization but not worth it.
  196. #
  197. def IS32BIT {
  198.     .\%1 := \flpad(\ftrim(\%1,0),10,0)
  199.     if lgt \%1 2147483647 return 0
  200.     return 1
  201. }
  202.  
  203. # Macro DECIMALADD adds two unsigned decimal strings regardless of magnitude.
  204. # \%1, \%2 are decimal strings.
  205. # Returns sum as decimal string.
  206. #
  207. def DECIMALADD {
  208.     local \%s \%c
  209.     .\%9 := \fmax(\flen(\%1),\flen(\%2))       # Get length of longest arg
  210.     .\%1 := \flpad(\%1,\%9,0)                  # Pad both to same length
  211.     .\%2 := \flpad(\%2,\%9,0)
  212.     .\%c = 0                                   # Carry
  213.     undef \%s                                  # Sum accumulator
  214.     for \%9 \flen(\%1) 1 -1 {                  # Loop through digits RTL
  215.         .\%6 := \:(\%1[\%9:1])                 # Current digit
  216.         .\%7 := \:(\%2[\%9:1])                 # of each
  217.         increment \%6 \%7+\%c                  # Form sum + carry
  218.         .\%5 ::= \%6 % 10                      # Separate sum+carry 
  219.         .\%c ::= \%6 / 10                      # into digits.
  220.         .\%s := \%5\%s                         # Accumulate sum
  221.     }
  222.     if \%c .\%s := \%c\%s                      # Final carry (if any)
  223.     return \%s                                 # Return result
  224. }
  225.  
  226. # Macro DECIMALTIMES
  227. # Multiplies two unsigned decimal strings regardless of magnitude by
  228. # repetitive addition.  Practical only when one of the factors is small and
  229. # the other one is (or the result would be) bigger than the hardware integer
  230. # size.  \%1, \%2 are the factors.  Returns product as decimal string.
  231. #
  232. # Order of args doesn't matter.  The smaller one is automatically chosen
  233. # as the loop counter.  Note the comparison of argument lengths rather than
  234. # values, since the > operator won't work with numbers that are too big for
  235. # the word size, nor can big numbers be used as loop variables.
  236. #
  237. def DECIMALTIMES {
  238.     local \%s \%c
  239.     if > \flen(\%1) \flen(\%2) { .\%9 := \%1, .\%1 := \%2, .\%2 := \%9 }
  240.     .\%s = 0
  241.     for \%9 1 \%1 1 {                         # Add X to itself Y times
  242.         .\%s := \fexec(decimaladd \%2 \%s)
  243.     }
  244.     return \%s
  245. }
  246.  
  247. # Macro HEXTODEC
  248. # Converts a two's complement hexadecimal string into a signed decimal string.
  249. # \%1 = hexadecimal argument.
  250. # Returns signed decimal string.
  251. #
  252. define HEXTODEC {
  253.     local digits \%b \%d \%p
  254.     .digits := \flen(\%1)
  255.     if not \m(digits) end 1 "\%0: NO ARGUMENT GIVEN"
  256.     .\%1 := \fupper(\%1)
  257.     if \fverify(0123456789ABCDEF,\%1) end 1 "\%0: '\%1' NOT A HEX STRING"
  258.     if not \findex(\:(\%1[1:1]),89ABCDEF) {  # Positive number
  259.         .\%d = 0
  260.         .\%p = 0
  261.         for \%9 \flen(\%1) 1 -1 {  # Loop through digits right to left
  262.             .\%6 := \:(\%1[\%9:1])                    # Hex digit
  263.             .\%6 := \fradix(\%6,16,10)                # Decimal value
  264.             .\%4 := \fexec(powerof16 \%p)             # Current power of 16
  265.             .\%6 := \fexec(decimaltimes \%4 \%6)      # Times power of 16
  266.             .\%d := \fexec(decimaladd \%6 \%d)        # Add to result
  267.             increment \%p                             # Next power of 16
  268.         }
  269.         return \%d
  270.     }
  271.     # Negative number (bit 0 set)
  272.     if = 1 \findex(\%1,800000000000000000000000000) { # Special case
  273.         .\%b ::= \flen(\%1) * 4         # Look this one up in our table
  274.         .\%b := \m(maxplusone<\%b>)     # to avoid infinite recursion
  275.         if not def \%b .\%b = ERROR
  276.         return -\%b
  277.     }
  278.     .\%b := \fexec(hextobin \%1)        # Normal case - convert to binary
  279.     .\%7 := \flen(\%b)                  # Get length of binary
  280.     .\%b := \fexec(twoscomplement \%b)  # Get two's complement
  281.     .\%b := \fexec(bintohex \%b \%7)    # Convert to hex
  282.     .\%d := \fexec(hextodec \%b)        # Call ourselves to convert to decimal
  283.     return -\%d
  284. }
  285.  
  286. # Test HEXTODEC...
  287.  
  288. .t0 := \v(ftime)                        # Start time for test suite
  289.  
  290. echo ****************
  291. echo TESTING HEXTODEC...
  292. echo ****************
  293. def try {
  294.     local result
  295.     .t1 := \v(ftime)                    # Current time
  296.     .result := \fexec(hextodec \%1)
  297.     .t2 := \v(ftime)                    # New time
  298.     (setq t3 (- t2 t1))                 # Difference
  299.     echo HEX \%1 = DEC \m(result) [\ffpround(\m(t3),2) sec] # Print
  300. }
  301.  
  302.  
  303. try 0
  304. try 7
  305. try 8
  306. try F
  307. try 1234
  308. try 80
  309. try 83
  310. try xxx
  311. try ffff
  312. try 8000
  313. try 8001
  314. try 5555
  315. try 12345
  316. try fffffffffffffffe
  317. try 0000000000000000
  318. try 00002ee000000000
  319. try 0000123456789abc
  320. try 00002ee000012345
  321. try 2ee0000000000000
  322. try aee0000000000000
  323. try f0000000fffffffe
  324. try 20000000fffffffe
  325. try 7fffffffffffffffffffffffffffffff
  326.  
  327. # TEST DECTOHEX...
  328.  
  329. echo ****************
  330. echo TESTING DECTOHEX...
  331. echo ****************
  332. def try {
  333.     local result
  334.     # Note \v(time) lacks the fractional part in Windows for some reason.
  335.     .t1 := \v(ftime)                    # Current time
  336.     .result := \fexec(dectohex \%1 \%2) # Do the conversion
  337.     .t2 := \v(ftime)                    # New time
  338.     (setq t3 (- t2 t1))                 # Difference
  339.     echo \%1[\%2] = \m(result) [\ffpround(\m(t3),2) sec] # Print
  340. }
  341. try 7          # No word size specified
  342.  
  343. try  7 4       # 4-bit word
  344. try  8 4
  345. try -8 4
  346. try -9 4
  347. try 99 4
  348.  
  349. try  0 8        # 8-bit word
  350. try -0 8
  351. try  1 8
  352. try +1 8
  353. try  2 8
  354. try  3 8
  355. try  4 8
  356. try  5 8
  357. try  6 8
  358. try  7 8
  359. try -1 8
  360. try -2 8
  361. try -3 8
  362. try -4 8
  363. try -5 8
  364. try -6 8
  365. try -7 8
  366. try -8 8
  367. try 64 8
  368. try 65 8
  369. try -128 8
  370.  
  371. try 0 16       # 16-bit word
  372. try 64 16
  373. try 65 16
  374. try -128 16
  375. try -32768 16
  376. try 99999 16
  377. try -99999 16
  378.  
  379. try 0 32       # 32-bit word
  380. try 1 32
  381. try 16383 32
  382. try 2147483647 32
  383. try -1 32
  384. try -2 32
  385. try -2147483647 32
  386. try -2147483648 32
  387.  
  388. try 0 64       # 64-bit word
  389. try 2147483647 64
  390. try -1 64
  391. try -2 64
  392. try  1234567890 64
  393. try -2147483647 64
  394. try -2147483648 64
  395. try  8224373093854475231 64
  396.  
  397. try 0 128      # 128-bit word
  398. try 1 128
  399. try -1 128
  400. try -2 128
  401. try 317282366902934863643347307341786875499 128
  402.  
  403. .t3 := \v(ftime)                        # End time for test suite
  404. (setq t3 (- t3 t0))
  405. echo TOTAL TIME: \ffpround(\m(t3),2) sec
  406.  
  407. # Don't exit in K95 or else the window will disappear and the results too.
  408.  
  409. if c-kermit exit
  410.