! 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 : 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 ; ( 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 + ] 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* ; : 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 } { exponent } { magnitude } ; : inc-point-?dec-magnitude ( float-parse n -- float-parse' ) zero? [ [ 1 - ] change-magnitude ] when [ 1 + ] 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 : ( 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 ->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 ) [ @mantissa-digit ] next-digit ?make-float ; inline : ->required-mantissa ( i number-parse n -- n/f ) [ @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 [ @first-char ] require-next-digit ; : base> ( str radix -- n/f ) [ @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 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 (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) (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 ; 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 [ [ [ [ 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