575 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Factor
		
	
	
			
		
		
	
	
			575 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Factor
		
	
	
| ! Copyright (C) 2009 Joe Groff, 2013 John Benediktsson
 | |
| ! See http://factorcode.org/license.txt for BSD license.
 | |
| USING: accessors byte-arrays combinators kernel kernel.private
 | |
| layouts make math math.private namespaces sbufs sequences
 | |
| sequences.private splitting strings strings.private ;
 | |
| IN: math.parser
 | |
| 
 | |
| <PRIVATE
 | |
| PRIMITIVE: (format-float) ( n format -- byte-array )
 | |
| PRIVATE>
 | |
| 
 | |
| : digit> ( ch -- n )
 | |
|     {
 | |
|         { [ dup CHAR: 9 <= ] [ CHAR: 0 -      dup  0 < [ drop 255 ] when ] }
 | |
|         { [ dup CHAR: a <  ] [ CHAR: A 10 - - dup 10 < [ drop 255 ] when ] }
 | |
|                              [ CHAR: a 10 - - dup 10 < [ drop 255 ] when ]
 | |
|     } cond ; inline
 | |
| 
 | |
| : string>digits ( str -- digits )
 | |
|     [ digit> ] B{ } map-as ; inline
 | |
| 
 | |
| : >digit ( n -- ch )
 | |
|     dup 10 < [ CHAR: 0 + ] [ 10 - CHAR: a + ] if ; inline
 | |
| 
 | |
| ERROR: invalid-radix radix ;
 | |
| 
 | |
| <PRIVATE
 | |
| 
 | |
| ! magnitude is used only for floats to avoid
 | |
| ! expensive computations when we know that
 | |
| ! the result will overflow/underflow.
 | |
| ! The computation of magnitude starts in
 | |
| ! number-parse and continues in float-parse.
 | |
| TUPLE: number-parse
 | |
|     { str read-only }
 | |
|     { length fixnum read-only }
 | |
|     { radix fixnum }
 | |
|     { magnitude fixnum } ;
 | |
| 
 | |
| : <number-parse> ( str radix -- i number-parse n )
 | |
|     [ 0 ] 2dip [ dup length ] dip 0 number-parse boa 0 ; inline
 | |
| 
 | |
| : (next-digit) ( i number-parse n digit-quot end-quot -- n/f )
 | |
|     [ 2over length>> < ] 2dip
 | |
|     [ [ 2over str>> nth-unsafe >fixnum [ 1 fixnum+fast ] 3dip ] prepose ] dip if ; inline
 | |
| 
 | |
| : require-next-digit ( i number-parse n quot -- n/f )
 | |
|     [ 3drop f ] (next-digit) ; inline
 | |
| 
 | |
| : next-digit ( i number-parse n quot -- n/f )
 | |
|     [ 2nip ] (next-digit) ; inline
 | |
| 
 | |
| : inc-magnitude ( number-parse -- number-parse' )
 | |
|     [ 1 fixnum+fast ] change-magnitude ; inline
 | |
| 
 | |
| : ?inc-magnitude ( number-parse n -- number-parse' )
 | |
|     zero? [ inc-magnitude ] unless ; inline
 | |
| 
 | |
| : (add-digit) ( number-parse n digit -- number-parse n' )
 | |
|     [ dup radix>> ] [ * ] [ + ] tri* ; inline
 | |
| 
 | |
| : add-digit ( i number-parse n digit quot -- n/f )
 | |
|     [ (add-digit) [ ?inc-magnitude ] keep ] dip next-digit ; inline
 | |
| 
 | |
| : add-exponent-digit ( i number-parse n digit quot -- n/f )
 | |
|     [ (add-digit) ] dip next-digit ; inline
 | |
| 
 | |
| : digit-in-radix ( number-parse n char -- number-parse n digit ? )
 | |
|     digit> pick radix>> over > ; inline
 | |
| 
 | |
| : ?make-ratio ( num denom/f -- ratio/f )
 | |
|     [ / ] [ drop f ] if* ; inline
 | |
| 
 | |
| TUPLE: float-parse
 | |
|     { radix fixnum }
 | |
|     { point fixnum }
 | |
|     { exponent }
 | |
|     { magnitude } ;
 | |
| : inc-point-?dec-magnitude ( float-parse n -- float-parse' )
 | |
|     zero? [ [ 1 fixnum-fast ] change-magnitude ] when
 | |
|     [ 1 fixnum+fast ] change-point ; inline
 | |
| 
 | |
| : store-exponent ( float-parse n expt -- float-parse' n )
 | |
|     swap [ >>exponent ] dip ; inline
 | |
| 
 | |
| : ?store-exponent ( float-parse n expt/f -- float-parse' n/f )
 | |
|     [ store-exponent ] [ drop f ] if* ; inline
 | |
| 
 | |
| : ((pow)) ( base x -- base^x )
 | |
|     [ 1 ] 2dip
 | |
|     [ dup zero? ] [
 | |
|         dup odd? [ [ [ * ] keep ] [ 1 - ] bi* ] when
 | |
|         [ sq ] [ 2/ ] bi*
 | |
|     ] until 2drop ; inline
 | |
| 
 | |
| : (pow) ( base x -- base^x )
 | |
|     integer>fixnum-strict
 | |
|     dup 0 >= [ ((pow)) ] [ [ recip ] [ neg ] bi* ((pow)) ] if ; inline
 | |
| 
 | |
| : add-mantissa-digit ( float-parse i number-parse n digit quot -- float-parse' n/f )
 | |
|     [ (add-digit)
 | |
|         dup [ inc-point-?dec-magnitude ] curry 3dip
 | |
|     ] dip next-digit ; inline
 | |
| 
 | |
| ! IEE754 doubles are in the range ]10^309,10^-324[,
 | |
| ! or expressed in base 2, ]2^1024, 2^-1074].
 | |
| ! We don't need those ranges to be accurate as long as we are
 | |
| ! excluding all the floats because they are used only to
 | |
| ! optimize when we know there will be an overflow/underflow
 | |
| ! We compare these numbers to the magnitude slot of float-parse,
 | |
| ! which has the following behavior:
 | |
| ! ... ; 0.0xxx -> -1; 0.xxx -> 0; x.xxx -> 1; xx.xxx -> 2; ...;
 | |
| ! Also, take some margin as the current float parsing algorithm
 | |
| ! does some rounding; For example,
 | |
| ! 0x1.0p-1074 is the smallest IE754 double, but floats down to
 | |
| ! 0x0.fffffffffffffcp-1074 are parsed as 0x1.0p-1074
 | |
| CONSTANT: max-magnitude-10 309
 | |
| CONSTANT: min-magnitude-10 -323
 | |
| CONSTANT: max-magnitude-2 1027
 | |
| CONSTANT: min-magnitude-2 -1074
 | |
| 
 | |
| : make-float-dec-exponent ( float-parse n/f -- float/f )
 | |
|     over [ exponent>> ] [ magnitude>> ] bi +
 | |
|     {
 | |
|         { [ dup max-magnitude-10 > ] [ 3drop 1/0. ] }
 | |
|         { [ dup min-magnitude-10 < ] [ 3drop 0.0 ] }
 | |
|         [ drop
 | |
|             [ [ radix>> ] [ point>> ] [ exponent>> ] tri - (pow) ]
 | |
|             [ swap /f ] bi*
 | |
|         ]
 | |
|     } cond ; inline
 | |
| 
 | |
| : base2-digits ( digits radix -- digits' )
 | |
|     {
 | |
|         { 16 [ 4 * ] }
 | |
|         { 8  [ 3 * ] }
 | |
|         { 2  [ ] }
 | |
|     } case ; inline
 | |
| 
 | |
| : base2-point ( float-parse -- point )
 | |
|     [ point>> ] [ radix>> ] bi base2-digits ; inline
 | |
| 
 | |
| : base2-magnitude ( float-parse -- point )
 | |
|     [ magnitude>> ] [ radix>> ] bi base2-digits ; inline
 | |
| 
 | |
| : make-float-bin-exponent ( float-parse n/f -- float/f )
 | |
|     over [ exponent>> ] [ base2-magnitude ] bi +
 | |
|     {
 | |
|         { [ dup max-magnitude-2 > ] [ 3drop 1/0. ] }
 | |
|         { [ dup min-magnitude-2 < ] [ 3drop 0.0 ] }
 | |
|         [ drop
 | |
|             [ [ drop 2 ] [ base2-point ] [ exponent>> ] tri - (pow) ]
 | |
|             [ swap /f ] bi*
 | |
|         ]
 | |
|     } cond ; inline
 | |
| 
 | |
| : ?default-exponent ( float-parse n/f -- float-parse' n/f' )
 | |
|     over exponent>> [
 | |
|         over radix>> 10 = [ 0 store-exponent ] [ drop f ] if
 | |
|     ] unless ; inline
 | |
| 
 | |
| : ?make-float ( float-parse n/f -- float/f )
 | |
|     { float-parse object } declare
 | |
|     ?default-exponent
 | |
|     {
 | |
|         { [ dup not ] [ 2drop f ] }
 | |
|         { [ over radix>> 10 = ] [ make-float-dec-exponent ] }
 | |
|         [ make-float-bin-exponent ]
 | |
|     } cond ;
 | |
| 
 | |
| : ?neg ( n/f -- -n/f )
 | |
|     [
 | |
|         dup bignum? [
 | |
|             dup first-bignum bignum=
 | |
|             [ drop most-negative-fixnum ] [ neg ] if
 | |
|         ] [ neg ] if
 | |
|     ] [ f ] if* ; inline
 | |
| 
 | |
| : ?add-ratio ( m n/f -- m+n/f )
 | |
|     dup ratio? [ + ] [ 2drop f ] if ; inline
 | |
| 
 | |
| : @abort ( i number-parse n x -- f )
 | |
|     4drop f ; inline
 | |
| 
 | |
| : @split ( i number-parse n -- n i number-parse' n' )
 | |
|     -rot 0 >>magnitude 0 ; inline
 | |
| 
 | |
| : @split-exponent ( i number-parse n -- n i number-parse' n' )
 | |
|     -rot 10 >>radix 0 ; inline
 | |
| 
 | |
| : <float-parse> ( i number-parse n -- float-parse i number-parse n )
 | |
|      [ drop nip [ radix>> ] [ magnitude>> ] bi [ 0 f ] dip float-parse boa ] 3keep ; inline
 | |
| 
 | |
| DEFER: @exponent-digit
 | |
| DEFER: @mantissa-digit
 | |
| DEFER: @denom-digit
 | |
| DEFER: @num-digit
 | |
| DEFER: @pos-digit
 | |
| DEFER: @neg-digit
 | |
| 
 | |
| : @exponent-digit-or-punc ( float-parse i number-parse n char -- float-parse n/f )
 | |
|     {
 | |
|         { CHAR: , [ [ @exponent-digit ] require-next-digit ] }
 | |
|         [ @exponent-digit ]
 | |
|     } case ; inline
 | |
| 
 | |
| : @exponent-digit ( float-parse i number-parse n char -- float-parse n/f )
 | |
|     { float-parse fixnum number-parse integer fixnum } declare
 | |
|     digit-in-radix [ [ @exponent-digit-or-punc ] add-exponent-digit ] [ @abort ] if ;
 | |
| 
 | |
| : @exponent-first-char ( float-parse i number-parse n char -- float-parse n/f )
 | |
|     {
 | |
|         { CHAR: - [ [ @exponent-digit ] require-next-digit ?neg ] }
 | |
|         { CHAR: + [ [ @exponent-digit ] require-next-digit ] }
 | |
|         [ @exponent-digit ]
 | |
|     } case ; inline
 | |
| 
 | |
| : ->exponent ( float-parse i number-parse n -- float-parse' n/f )
 | |
|     @split-exponent [ @exponent-first-char ] require-next-digit ?store-exponent ; inline
 | |
| 
 | |
| : exponent-char? ( number-parse n char -- number-parse n char ? )
 | |
|     pick radix>> {
 | |
|         { 10 [ dup "eE" member-eq? ] }
 | |
|         [ drop dup "pP" member-eq? ]
 | |
|     } case ; inline
 | |
| 
 | |
| : or-exponent ( i number-parse n char quot -- n/f )
 | |
|     [ exponent-char? [ drop <float-parse> ->exponent ?make-float ] ] dip if ; inline
 | |
| 
 | |
| : or-mantissa->exponent ( float-parse i number-parse n char quot -- float-parse n/f )
 | |
|     [ exponent-char? [ drop ->exponent ] ] dip if ; inline
 | |
| 
 | |
| : @mantissa-digit-or-punc ( float-parse i number-parse n char -- float-parse n/f )
 | |
|     {
 | |
|         { CHAR: , [ [ @mantissa-digit ] require-next-digit ] }
 | |
|         [ @mantissa-digit ]
 | |
|     } case ; inline
 | |
| 
 | |
| : @mantissa-digit ( float-parse i number-parse n char -- float-parse n/f )
 | |
|     { float-parse fixnum number-parse integer fixnum } declare
 | |
|     [
 | |
|         digit-in-radix
 | |
|         [ [ @mantissa-digit-or-punc ] add-mantissa-digit ]
 | |
|         [ @abort ] if
 | |
|     ] or-mantissa->exponent ;
 | |
| 
 | |
| : ->mantissa ( i number-parse n -- n/f )
 | |
|     <float-parse> [ @mantissa-digit ] next-digit ?make-float ; inline
 | |
| 
 | |
| : ->required-mantissa ( i number-parse n -- n/f )
 | |
|     <float-parse> [ @mantissa-digit ] require-next-digit ?make-float ; inline
 | |
| 
 | |
| : @denom-digit-or-punc ( i number-parse n char -- n/f )
 | |
|     {
 | |
|         { CHAR: , [ [ @denom-digit ] require-next-digit ] }
 | |
|         { CHAR: . [ ->mantissa ] }
 | |
|         [ [ @denom-digit ] or-exponent ]
 | |
|     } case ; inline
 | |
| 
 | |
| : @denom-digit ( i number-parse n char -- n/f )
 | |
|     { fixnum number-parse integer fixnum } declare
 | |
|     digit-in-radix [ [ @denom-digit-or-punc ] add-digit ] [ @abort ] if ;
 | |
| 
 | |
| : @denom-first-digit ( i number-parse n char -- n/f )
 | |
|     {
 | |
|         { CHAR: . [ ->mantissa ] }
 | |
|         [ @denom-digit ]
 | |
|     } case ; inline
 | |
| 
 | |
| : ->denominator ( i number-parse n -- n/f )
 | |
|     { fixnum number-parse integer } declare
 | |
|     @split [ @denom-first-digit ] require-next-digit ?make-ratio ;
 | |
| 
 | |
| : @num-digit-or-punc ( i number-parse n char -- n/f )
 | |
|     {
 | |
|         { CHAR: , [ [ @num-digit ] require-next-digit ] }
 | |
|         { CHAR: / [ ->denominator ] }
 | |
|         [ @num-digit ]
 | |
|     } case ; inline
 | |
| 
 | |
| : @num-digit ( i number-parse n char -- n/f )
 | |
|     { fixnum number-parse integer fixnum } declare
 | |
|     digit-in-radix [ [ @num-digit-or-punc ] add-digit ] [ @abort ] if ;
 | |
| 
 | |
| : ->numerator ( i number-parse n -- n/f )
 | |
|     { fixnum number-parse integer } declare
 | |
|     @split [ @num-digit ] require-next-digit ?add-ratio ;
 | |
| 
 | |
| : @pos-digit-or-punc ( i number-parse n char -- n/f )
 | |
|     {
 | |
|         { CHAR: , [ [ @pos-digit ] require-next-digit ] }
 | |
|         { CHAR: + [ ->numerator ] }
 | |
|         { CHAR: / [ ->denominator ] }
 | |
|         { CHAR: . [ ->mantissa ] }
 | |
|         [ [ @pos-digit ] or-exponent ]
 | |
|     } case ; inline
 | |
| 
 | |
| : @pos-digit ( i number-parse n char -- n/f )
 | |
|     { fixnum number-parse integer fixnum } declare
 | |
|     digit-in-radix [ [ @pos-digit-or-punc ] add-digit ] [ @abort ] if ;
 | |
| 
 | |
| : ->radix ( i number-parse n quot radix -- i number-parse n quot )
 | |
|     [ >>radix ] curry 2dip ; inline
 | |
| 
 | |
| : with-radix-char ( i number-parse n radix-quot nonradix-quot -- n/f )
 | |
|     [
 | |
|         rot {
 | |
|             { CHAR: b [ drop  2 ->radix require-next-digit ] }
 | |
|             { CHAR: o [ drop  8 ->radix require-next-digit ] }
 | |
|             { CHAR: x [ drop 16 ->radix require-next-digit ] }
 | |
|             [ [ drop ] 2dip swap call ]
 | |
|         } case
 | |
|     ] 2curry next-digit ; inline
 | |
| 
 | |
| : @pos-first-digit ( i number-parse n char -- n/f )
 | |
|     {
 | |
|         { CHAR: . [ ->required-mantissa ] }
 | |
|         { CHAR: 0 [ [ @pos-digit ] [ @pos-digit-or-punc ] with-radix-char ] }
 | |
|         [ @pos-digit ]
 | |
|     } case ; inline
 | |
| 
 | |
| : @neg-digit-or-punc ( i number-parse n char -- n/f )
 | |
|     {
 | |
|         { CHAR: , [ [ @neg-digit ] require-next-digit ] }
 | |
|         { CHAR: - [ ->numerator ] }
 | |
|         { CHAR: / [ ->denominator ] }
 | |
|         { CHAR: . [ ->mantissa ] }
 | |
|         [ [ @neg-digit ] or-exponent ]
 | |
|     } case ; inline
 | |
| 
 | |
| : @neg-digit ( i number-parse n char -- n/f )
 | |
|     { fixnum number-parse integer fixnum } declare
 | |
|     digit-in-radix [ [ @neg-digit-or-punc ] add-digit ] [ @abort ] if ;
 | |
| 
 | |
| : @neg-first-digit ( i number-parse n char -- n/f )
 | |
|     {
 | |
|         { CHAR: . [ ->required-mantissa ] }
 | |
|         { CHAR: 0 [ [ @neg-digit ] [ @neg-digit-or-punc ] with-radix-char ] }
 | |
|         [ @neg-digit ]
 | |
|     } case ; inline
 | |
| 
 | |
| : @first-char ( i number-parse n char -- n/f )
 | |
|     {
 | |
|         { CHAR: - [ [ @neg-first-digit ] require-next-digit ?neg ] }
 | |
|         { CHAR: + [ [ @pos-first-digit ] require-next-digit ] }
 | |
|         [ @pos-first-digit ]
 | |
|     } case ; inline
 | |
| 
 | |
| : @neg-first-digit-no-radix ( i number-parse n char -- n/f )
 | |
|     {
 | |
|         { CHAR: . [ ->required-mantissa ] }
 | |
|         [ @neg-digit ]
 | |
|     } case ; inline
 | |
| 
 | |
| : @pos-first-digit-no-radix ( i number-parse n char -- n/f )
 | |
|     {
 | |
|         { CHAR: . [ ->required-mantissa ] }
 | |
|         [ @pos-digit ]
 | |
|     } case ; inline
 | |
| 
 | |
| : @first-char-no-radix ( i number-parse n char -- n/f )
 | |
|     {
 | |
|         { CHAR: - [ [ @neg-first-digit-no-radix ] require-next-digit ?neg ] }
 | |
|         { CHAR: + [ [ @pos-first-digit-no-radix ] require-next-digit ] }
 | |
|         [ @pos-first-digit-no-radix ]
 | |
|     } case ; inline
 | |
| 
 | |
| PRIVATE>
 | |
| 
 | |
| : string>number ( str -- n/f )
 | |
|     10 <number-parse> [ @first-char ] require-next-digit ;
 | |
| 
 | |
| : base> ( str radix -- n/f )
 | |
|     <number-parse> [ @first-char-no-radix ] require-next-digit ;
 | |
| 
 | |
| : bin> ( str -- n/f )  2 base> ; inline
 | |
| : oct> ( str -- n/f )  8 base> ; inline
 | |
| : dec> ( str -- n/f ) 10 base> ; inline
 | |
| : hex> ( str -- n/f ) 16 base> ; inline
 | |
| 
 | |
| <PRIVATE
 | |
| 
 | |
| CONSTANT: TENS B{
 | |
|     48 48 48 48 48 48 48 48 48 48 49 49 49 49 49 49 49 49 49 49
 | |
|     50 50 50 50 50 50 50 50 50 50 51 51 51 51 51 51 51 51 51 51
 | |
|     52 52 52 52 52 52 52 52 52 52 53 53 53 53 53 53 53 53 53 53
 | |
|     54 54 54 54 54 54 54 54 54 54 55 55 55 55 55 55 55 55 55 55
 | |
|     56 56 56 56 56 56 56 56 56 56 57 57 57 57 57 57 57 57 57 57
 | |
| }
 | |
| 
 | |
| CONSTANT: ONES B{
 | |
|     48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57
 | |
|     48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57
 | |
|     48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57
 | |
|     48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57
 | |
|     48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57
 | |
| }
 | |
| 
 | |
| : (two-digit) ( num accum -- num' accum )
 | |
|     [
 | |
|         100 /mod [ TENS nth-unsafe ] [ ONES nth-unsafe ] bi
 | |
|     ] dip [ push ] keep [ push ] keep ; inline
 | |
| 
 | |
| : (one-digit) ( num accum -- num' accum )
 | |
|     [ 10 /mod CHAR: 0 + ] dip [ push ] keep ; inline
 | |
| 
 | |
| : (bignum>dec) ( num accum -- num' accum )
 | |
|     [ over most-positive-fixnum > ]
 | |
|     [ { bignum sbuf } declare (two-digit) ] while
 | |
|     [ >fixnum ] dip ; inline
 | |
| 
 | |
| : (fixnum>dec) ( num accum -- num' accum )
 | |
|     { fixnum sbuf } declare
 | |
|     [ over 10 >= ] [ (two-digit) ] while
 | |
|     [ over zero? ] [ (one-digit) ] until ; inline
 | |
| 
 | |
| GENERIC: (positive>dec) ( num -- str )
 | |
| 
 | |
| M: bignum (positive>dec)
 | |
|     12 <sbuf> (bignum>dec) (fixnum>dec) "" like reverse! nip ; inline
 | |
| 
 | |
| : (count-digits) ( digits n -- digits' )
 | |
|     {
 | |
|         { [ dup 10 < ] [ drop ] }
 | |
|         { [ dup 100 < ] [ drop 1 fixnum+fast ] }
 | |
|         { [ dup 1,000 < ] [ drop 2 fixnum+fast ] }
 | |
|         [
 | |
|             dup 1,000,000,000,000 < [
 | |
|                 dup 100,000,000 < [
 | |
|                     dup 1,000,000 < [
 | |
|                         dup 10,000 < [
 | |
|                             drop 3
 | |
|                         ] [
 | |
|                             100,000 >= 5 4 ?
 | |
|                         ] if
 | |
|                     ] [
 | |
|                         10,000,000 >= 7 6 ?
 | |
|                     ] if
 | |
|                 ] [
 | |
|                     dup 10,000,000,000 < [
 | |
|                         1,000,000,000 >= 9 8 ?
 | |
|                     ] [
 | |
|                         100,000,000,000 >= 11 10 ?
 | |
|                     ] if
 | |
|                 ] if fixnum+fast
 | |
|             ] [
 | |
|                 [ 12 fixnum+fast ] [ 1,000,000,000,000 /i ] bi*
 | |
|                 (count-digits)
 | |
|             ] if
 | |
|         ]
 | |
|     } cond ; inline recursive
 | |
| 
 | |
| M: fixnum (positive>dec)
 | |
|     1 over (count-digits) <sbuf> (fixnum>dec) "" like reverse! nip ; inline
 | |
| 
 | |
| : (positive>base) ( num radix -- str )
 | |
|     dup 1 <= [ invalid-radix ] when
 | |
|     [ dup 0 > ] swap [ /mod >digit ] curry "" produce-as nip
 | |
|     reverse! ; inline
 | |
| 
 | |
| : positive>base ( num radix -- str )
 | |
|     dup 10 = [ drop (positive>dec) ] [ (positive>base) ] if ; inline
 | |
| 
 | |
| PRIVATE>
 | |
| 
 | |
| GENERIC# >base 1 ( n radix -- str )
 | |
| 
 | |
| : number>string ( n -- str ) 10 >base ; inline
 | |
| 
 | |
| : >bin ( n -- str ) 2 >base ; inline
 | |
| : >oct ( n -- str ) 8 >base ; inline
 | |
| : >hex ( n -- str ) 16 >base ; inline
 | |
| 
 | |
| M: integer >base
 | |
|     {
 | |
|         { [ over 0 = ] [ 2drop "0" ] }
 | |
|         { [ over 0 > ] [ positive>base ] }
 | |
|         [ [ neg ] dip positive>base CHAR: - prefix ]
 | |
|     } cond ;
 | |
| 
 | |
| M: ratio >base
 | |
|     [ >fraction [ /mod ] keep ] [ [ >base ] curry tri@ ] bi*
 | |
|     "/" glue over first-unsafe {
 | |
|         { CHAR: 0 [ nip ] }
 | |
|         { CHAR: - [ append ] }
 | |
|         [ drop "+" glue ]
 | |
|     } case ;
 | |
| 
 | |
| <PRIVATE
 | |
| 
 | |
| : fix-float ( str -- newstr )
 | |
|     CHAR: e over index [
 | |
|         cut [ fix-float ] dip append
 | |
|     ] [
 | |
|         CHAR: . over member? [ ".0" append ] unless
 | |
|     ] if* ;
 | |
| 
 | |
| : mantissa-expt-normalize ( mantissa expt -- mantissa' expt' )
 | |
|     [ dup log2 52 swap - [ shift 52 2^ 1 - bitand ] [ 1022 + neg ] bi ]
 | |
|     [ 1023 - ] if-zero ;
 | |
| 
 | |
| : mantissa-expt ( float -- mantissa expt )
 | |
|     [ 52 2^ 1 - bitand ]
 | |
|     [ -0.0 double>bits bitnot bitand -52 shift ] bi
 | |
|     mantissa-expt-normalize ;
 | |
| 
 | |
| : bin-float-sign ( bits -- str )
 | |
|     -0.0 double>bits bitand zero? "" "-" ? ;
 | |
| 
 | |
| : bin-float-value ( str size -- str' )
 | |
|     CHAR: 0 pad-head [ CHAR: 0 = ] trim-tail
 | |
|     [ "0" ] when-empty "1." prepend ;
 | |
| 
 | |
| : float>hex-value ( mantissa -- str )
 | |
|     >hex 13 bin-float-value ;
 | |
| 
 | |
| : float>oct-value ( mantissa -- str )
 | |
|     4 * >oct 18 bin-float-value ;
 | |
| 
 | |
| : float>bin-value ( mantissa -- str )
 | |
|     >bin 52 bin-float-value ;
 | |
| 
 | |
| : bin-float-expt ( mantissa -- str )
 | |
|     10 >base "p" prepend ;
 | |
| 
 | |
| : (bin-float>base) ( value-quot n -- str )
 | |
|     double>bits
 | |
|     [ bin-float-sign swap ] [
 | |
|         mantissa-expt rot [ bin-float-expt ] bi*
 | |
|     ] bi 3append ; inline
 | |
| 
 | |
| : bin-float>base ( n base -- str )
 | |
|     {
 | |
|         { 16 [ [ float>hex-value ] swap (bin-float>base) ] }
 | |
|         { 8  [ [ float>oct-value ] swap (bin-float>base) ] }
 | |
|         { 2  [ [ float>bin-value ] swap (bin-float>base) ] }
 | |
|         [ invalid-radix ]
 | |
|     } case ;
 | |
| 
 | |
| : format-string ( format -- format )
 | |
|     0 suffix >byte-array ; foldable
 | |
| 
 | |
| : format-head ( byte-array n -- string )
 | |
|     swap over 0 <string> [
 | |
|         [
 | |
|             [ [ nth-unsafe ] 2keep drop ]
 | |
|             [ set-string-nth-fast ] bi*
 | |
|         ] 2curry each-integer
 | |
|     ] keep ; inline
 | |
| 
 | |
| : format-float ( n format -- string )
 | |
|     format-string (format-float)
 | |
|     dup [ 0 = ] find drop
 | |
|     format-head fix-float ; inline
 | |
| 
 | |
| : float>base ( n radix -- str )
 | |
|     {
 | |
|         { 10 [ "%.16g" format-float ] }
 | |
|         [ bin-float>base ]
 | |
|     } case ; inline
 | |
| 
 | |
| PRIVATE>
 | |
| 
 | |
| M: float >base
 | |
|     {
 | |
|         { [ over fp-nan? ] [ 2drop "0/0." ] }
 | |
|         { [ over 1/0. =  ] [ 2drop "1/0." ] }
 | |
|         { [ over -1/0. = ] [ 2drop "-1/0." ] }
 | |
|         { [ over  0.0 fp-bitwise= ] [ 2drop  "0.0" ] }
 | |
|         { [ over -0.0 fp-bitwise= ] [ 2drop "-0.0" ] }
 | |
|         [ float>base ]
 | |
|     } cond ;
 | |
| 
 | |
| : # ( n -- ) number>string % ; inline
 |