1- import { ComputeEngine } from '../../src/compute-engine' ;
1+ import { ComputeEngine , NumericType } from '../../src/compute-engine' ;
22import { _BoxedExpression } from '../../src/compute-engine/boxed-expression/abstract-boxed-expression' ;
33import { check , exprToString , engine as TEST_ENGINE } from '../utils' ;
44import type {
@@ -504,8 +504,8 @@ describe('CANONICAL FORMS', () => {
504504 ` ) ;
505505
506506/*
507- * Constant-symbol value cases.
508- */
507+ * Constant-symbol value cases.
508+ */
509509// p === +Infinity (& 'holdUntil: never')
510510expect ( checkPower ( '1^{p}' ) ) . toMatchInlineSnapshot ( `
511511 box = ["Power", 1, "p"]
@@ -666,6 +666,212 @@ describe('CANONICAL FORMS', () => {
666666 ` ) ;
667667} ) ;
668668} ) ;
669+
670+ describe ( 'Number' , ( ) => {
671+ const ce = TEST_ENGINE ;
672+ let expr :string | SemiBoxedExpression ;
673+
674+ const nonCanon = ( input :string | SemiBoxedExpression ) =>
675+ typeof input === 'string'
676+ ?ce . parse ( input , { canonical :false } )
677+ :ce . box ( input , { canonical :false } ) ;
678+ const checkNumber = ( input :string | SemiBoxedExpression ) =>
679+ checkForms ( input , [ 'Number' ] , ce ) ;
680+ const canonNumber = ( input :string | SemiBoxedExpression ) =>
681+ typeof input === 'string'
682+ ?ce . parse ( input , { canonical :'Number' } )
683+ :ce . box ( input , { canonical :'Number' } ) ;
684+
685+ //*note*: BoxedNumbers may get JSON serialized as ['Rational',...] ('check' outputs JSON): so
686+ //need to additionally test for the result 'operator' as 'Number' for desired result.
687+ test ( `'Rational' or 'Divide' (expr.) -> number (when valid)` , ( ) => {
688+ expr = '1/3' ;
689+ //(•see above note: both 'canonForms' & 'canonical' are serialized as 'Rational', but may still
690+ //be numbers)
691+ expect ( checkNumber ( expr ) ) . toMatchInlineSnapshot ( `
692+ box = ["Divide", 1, 3]
693+ canonForms = ["Rational", 1, 3]
694+ canonical = ["Rational", 1, 3]
695+ ` ) ;
696+ expect ( canonNumber ( expr ) . operator ) . toBe ( 'Number' ) ;
697+
698+ expr = '3/7' ;
699+ expect ( checkNumber ( expr ) ) . toMatchInlineSnapshot ( `
700+ box = ["Divide", 3, 7]
701+ canonForms = ["Rational", 3, 7]
702+ canonical = ["Rational", 3, 7]
703+ ` ) ;
704+ expect ( canonNumber ( expr ) . operator ) . toBe ( 'Number' ) ;
705+
706+ //(case of direct number serialization (denominator of 1))
707+ expr = '2/1' ;
708+ expect ( checkNumber ( expr ) ) . toMatchInlineSnapshot ( `
709+ box = ["Divide", 2, 1]
710+ canonForms = 2
711+ canonical = 2
712+ ` ) ;
713+ expect ( canonNumber ( expr ) . operator ) . toBe ( 'Number' ) ;
714+
715+ /*
716+ * BigNum (Decimal)
717+ */
718+ //@note : as of time of writing (CE 0.29.1), the test engine which test-cases in this block use
719+ //has a precision set to `100`: so all BigNum/Int digits are output.
720+ const bigRational :SemiBoxedExpression = [
721+ 'Divide' ,
722+ '318982460862894267352492496399' ,
723+ '-358796515092200247647243' ,
724+ ] ;
725+ expect ( checkNumber ( bigRational ) ) . toMatchInlineSnapshot ( `
726+ box = [
727+ "Divide",
728+ {num: "318982460862894267352492496399"},
729+ {num: "-358796515092200247647243"}
730+ ]
731+ canonForms = [
732+ "Rational",
733+ {num: "-318982460862894267352492496399"},
734+ {num: "358796515092200247647243"}
735+ ]
736+ canonical = [
737+ "Rational",
738+ {num: "-318982460862894267352492496399"},
739+ {num: "358796515092200247647243"}
740+ ]
741+ ` ) ;
742+ expect ( canonNumber ( expr ) . operator ) . toBe ( 'Number' ) ; // ✔
743+
744+ /*
745+ * Control
746+ */
747+ expr = '13.19/7.7' ;
748+ expect ( checkNumber ( expr ) ) . toMatchInlineSnapshot ( `
749+ box = ["Divide", 13.19, 7.7]
750+ canonForms = ["Divide", 13.19, 7.7]
751+ ` ) ;
752+ } ) ;
753+
754+ test ( `'Rational' (expr.) -> Divide (when non-rational)` , ( ) => {
755+ expect ( checkNumber ( [ 'Rational' , 1 , 'Pi' ] ) ) . toMatchInlineSnapshot ( `
756+ box = ["Rational", 1, "Pi"]
757+ canonForms = ["Divide", 1, "Pi"]
758+ canonical = ["Divide", 1, "Pi"]
759+ ` ) ;
760+ expect ( checkNumber ( [ 'Rational' , 7.01 , 3 ] ) ) . toMatchInlineSnapshot ( `
761+ box = ["Rational", 7.01, 3]
762+ canonForms = ["Divide", 7.01, 3]
763+ canonical = ["Divide", 7.01, 3]
764+ ` ) ;
765+ //(↓For full-canonical, additional simplifications applied (hence 'Multiply'))
766+ expect ( checkNumber ( [ 'Rational' , 'ExponentialE' , 2 ] ) )
767+ . toMatchInlineSnapshot ( `
768+ box = ["Rational", "ExponentialE", 2]
769+ canonForms = ["Divide", "ExponentialE", 2]
770+ canonical = ["Multiply", ["Rational", 1, 2], "ExponentialE"]
771+ ` ) ;
772+ } ) ;
773+
774+ //@wip ?
775+ // -Presently, 'Complex'-operator exprs. are the only ones cast as BoxedNumber for this form.
776+ // Later potential candidates include those related which take place in Add/Multiply (see
777+ // https://github.com/cortex-js/compute-engine/pull/238#discussion_r2033792056)
778+ describe ( "'Complex' exprs." , ( ) => {
779+ //@note : 'check' is not helpful here, since a `['Complex', ...]` expr. is 'pretty'-printed for
780+ //each variant: whereas we want to test the 'operator'/type
781+ test ( `Convert to eqv. BoxedNumbers` , ( ) => {
782+ const expComplexNum = (
783+ expr :string | SemiBoxedExpression ,
784+ type ?:NumericType
785+ ) => {
786+ expect ( nonCanon ( expr ) . operator ) . toMatchInlineSnapshot ( `Complex` ) ;
787+ expect ( canonNumber ( expr ) . operator ) . toBe ( `Number` ) ;
788+ expect ( canonNumber ( expr ) . type . matches ( type ?? 'complex' ) ) . toBe ( true ) ;
789+ } ;
790+
791+ // Imaginary (only)
792+ expr = [ 'Complex' , 1 ] ;
793+ expComplexNum ( expr , 'imaginary' ) ;
794+
795+ expr = [ 'Complex' , [ 'Rational' , 1 , 43 ] ] ;
796+ expComplexNum ( expr , 'imaginary' ) ;
797+
798+ // finite_complex / complex
799+ expr = [ 'Complex' , 3 , 4 ] ;
800+ expComplexNum ( expr , 'finite_complex' ) ;
801+
802+ //@fixme
803+ //(A present bug: that bignum args. get truncated when canonicalized as a complex-number:
804+ //regardless of set precision)
805+ //@note : precision is '100' for the engine used here...
806+ expr = [ 'Complex' , '22975850700614579948873711' , 4 ] ; // bigIntRe
807+ expComplexNum ( expr , 'finite_complex' ) ;
808+
809+ expr = [ 'Complex' , Infinity , 4 ] ; // bigIntRe
810+ expComplexNum ( expr , 'complex' ) ;
811+ } ) ;
812+
813+ test ( `Convert to 'Add', when imaginary arg. is non-numeric` , ( ) => {
814+ expr = [ 'Complex' , 4 , 'x' ] ;
815+ expect ( checkNumber ( expr ) ) . toMatchInlineSnapshot ( `
816+ box = ["Complex", 4, "x"]
817+ canonForms = ["Add", ["Multiply", ["Complex", 0, 1], "x"], 4]
818+ canonical = ["Add", ["Multiply", ["Complex", 0, 1], "x"], 4]
819+ ` ) ;
820+
821+ expr = [ 'Complex' , 1 , [ 'Multiply' , 'n' , 4 ] ] ;
822+ expect ( checkNumber ( expr ) ) . toMatchInlineSnapshot ( `
823+ box = ["Complex", 1, ["Multiply", "n", 4]]
824+ canonForms = ["Add", ["Multiply", ["Complex", 0, 4], "n"], 1]
825+ canonical = ["Add", ["Multiply", ["Complex", 0, 4], "n"], 1]
826+ ` ) ;
827+ } ) ;
828+ } ) ;
829+
830+ test ( "Cast 'Negate' wrapped numbers as number exprs. " , ( ) => {
831+ expect ( checkNumber ( '-1' ) ) . toMatchInlineSnapshot ( `
832+ box = -1
833+ canonForms = -1
834+ ` ) ;
835+
836+ expect ( checkNumber ( '-3.6' ) ) . toMatchInlineSnapshot ( `
837+ box = ["Negate", 3.6]
838+ canonForms = -3.6
839+ canonical = -3.6
840+ ` ) ;
841+
842+ expr = [ 'Negate' , [ 'Complex' , 1 , 4 ] ] ;
843+ expect ( checkNumber ( expr ) ) . toMatchInlineSnapshot ( `
844+ box = ["Negate", ["Complex", 1, 4]]
845+ canonForms = ["Complex", -1, -4]
846+ canonical = ["Complex", -1, -4]
847+ ` ) ;
848+ expect ( canonNumber ( expr ) . operator ) . toBe ( 'Number' ) ;
849+
850+ expr = [ 'Negate' , [ 'Rational' , 9 , 16 ] ] ;
851+ expect ( checkNumber ( expr ) ) . toMatchInlineSnapshot ( `
852+ box = ["Negate", ["Rational", 9, 16]]
853+ canonForms = ["Rational", -9, 16]
854+ canonical = ["Rational", -9, 16]
855+ ` ) ;
856+ expect ( canonNumber ( expr ) . operator ) . toBe ( 'Number' ) ;
857+ } ) ;
858+
859+ //(↓Only replaces for the *unit*, including negated...)
860+ //@wip ?: maybe 'InvisibleOperator' case of '3i' etc. should be covered
861+ test ( `Replace 'ImaginaryUnit' symbol instances with a number` , ( ) => {
862+ expect ( checkNumber ( 'i' ) ) . toMatchInlineSnapshot ( `
863+ box = i
864+ canonForms = ["Complex", 0, 1]
865+ canonical = ["Complex", 0, 1]
866+ ` ) ;
867+
868+ expect ( checkNumber ( '-i' ) ) . toMatchInlineSnapshot ( `
869+ box = ["Negate", "i"]
870+ canonForms = ["Complex", 0, -1]
871+ canonical = ["Complex", 0, -1]
872+ ` ) ;
873+ } ) ;
874+ } ) ;
669875} ) ;
670876
671877//
@@ -940,6 +1146,12 @@ describe('POLYNOMIAL ORDER', () => {
9401146 *
9411147 * Only prints 'canonical' if this differs from 'boxed'.
9421148 *
1149+ * <!--
1150+ * **NOTE**:
1151+ * -Unlike 'checkJson', does not temporarily set the engine's precision to 'auto' for printing.
1152+ * (Save oneself some headaches...)
1153+ * -->
1154+ *
9431155 *@param inExpr
9441156 *@param forms
9451157 *@param [engine]
@@ -961,9 +1173,6 @@ function checkForms(
9611173engine ??= TEST_ENGINE ;
9621174
9631175try {
964- const precision = engine . precision ;
965- engine . precision = 'auto' ;
966-
9671176//Boxed, *non-canonical*
9681177const boxed =
9691178typeof inExpr === 'string'
@@ -973,7 +1182,6 @@ function checkForms(
9731182const boxedStr = exprToString ( boxed ) ;
9741183
9751184if ( ! boxed . isValid ) {
976- engine . precision = precision ;
9771185return `invalid =${ exprToString ( boxed ) } ` ;
9781186}
9791187
@@ -989,8 +1197,6 @@ function checkForms(
9891197result . push ( 'canonForms = ' + partialCanonStr ) ;
9901198if ( canonicalStr !== boxedStr ) result . push ( 'canonical = ' + canonicalStr ) ;
9911199
992- engine . precision = precision ;
993-
9941200return result . join ( '\n' ) ;
9951201} catch ( e ) {
9961202return e . toString ( ) ;