66using System . Collections . Generic ; // for dictionary, list
77using System . Globalization ;
88using System . Linq ;
9+ using System . Numerics ;
910using System . Text ;
1011using System . Text . RegularExpressions ;
1112
@@ -20,7 +21,7 @@ public enum Dtype : ushort
2021 TYPELESS = 0 ,
2122 /// <summary>represented as booleans</summary>
2223 BOOL = 1 ,
23- /// <summary>represented as longs </summary>
24+ /// <summary>represented as <see cref="System.Numerics.BigInteger"/> </summary>
2425 INT = 2 ,
2526 /// <summary>represented as doubles</summary>
2627 FLOAT = 4 ,
@@ -249,7 +250,7 @@ public JNode()
249250 //extras = null;
250251 }
251252
252- public JNode ( long value , int position = 0 )
253+ public JNode ( BigInteger value , int position = 0 )
253254 {
254255 this . position = position ;
255256 this . type = Dtype . INT ;
@@ -403,6 +404,104 @@ public static void StrToSb(StringBuilder sb, string s)
403404 }
404405 }
405406
407+ public static BigInteger ConvertToBigInteger ( object val )
408+ {
409+ if ( val is BigInteger bi )
410+ return bi ;
411+ if ( val is double d )
412+ return ( BigInteger ) d ;
413+ if ( val is bool b )
414+ return b ? BigInteger . One : BigInteger . Zero ;
415+ if ( val is string s )
416+ return BigInteger . Parse ( s ) ;
417+ // hope that it's convertible to a long
418+ return ( BigInteger ) Convert . ToInt64 ( val ) ;
419+ }
420+
421+ public static double ConvertJNodeValueToDouble ( object val )
422+ {
423+ if ( val is BigInteger bi )
424+ return ( double ) bi ;
425+ if ( val is double d )
426+ return d ;
427+ if ( val is bool b )
428+ return b ? 1D : 0D ;
429+ // hope it's convertible to a double
430+ return Convert . ToDouble ( val ) ;
431+ }
432+
433+ /// <summary>
434+ /// return true and set d to (double)this.value if this.value is one of:<br></br>
435+ /// - a BigInteger that could be converted to a double.<br></br>
436+ /// - a double<br></br>
437+ /// - a bool (and set d = 1 if this.value else 0)<br></br>
438+ /// return false and set d to 0 otherwise.
439+ /// </summary>
440+ public bool TryGetValueAsDouble ( out double d )
441+ {
442+ d = 0 ;
443+ if ( value is double dval )
444+ {
445+ d = dval ;
446+ return true ;
447+ }
448+ if ( value is bool b )
449+ {
450+ d = b ? 1D : 0D ;
451+ return true ;
452+ }
453+ if ( value is BigInteger bi && bi >= DoubleMinValueAsBigInt && bi <= DoubleMaxValueAsBigInt )
454+ {
455+ d = ( double ) bi ;
456+ return true ;
457+ }
458+ return false ;
459+ }
460+
461+ /// <summary>
462+ /// return true if this.value is one of:<br></br>
463+ /// - a BigInteger (and set bi to this.value)<br></br>
464+ /// - a bool (and set bi = 1 if this.value else 0)<br></br>
465+ /// return false and set bi to 0 otherwise.
466+ /// </summary>
467+ public bool TryGetValueAsBigInteger ( out BigInteger bi )
468+ {
469+ bi = BigInteger . Zero ;
470+ if ( value is BigInteger biv )
471+ {
472+ bi = biv ;
473+ return true ;
474+ }
475+ if ( value is bool b )
476+ {
477+ if ( b )
478+ bi = BigInteger . One ;
479+ return true ;
480+ }
481+ return false ;
482+ }
483+
484+ /// <summary>
485+ /// return true and set i to (int)this.value if this.value is a BigInteger that could be converted to an int.<br></br>
486+ /// return false and set i to 0 otherwise.
487+ /// </summary>
488+ /// <param name="d"></param>
489+ /// <returns></returns>
490+ public bool TryGetValueAsInt32 ( out int i )
491+ {
492+ i = 0 ;
493+ if ( value is BigInteger bi && bi >= int . MinValue && bi <= int . MaxValue )
494+ {
495+ i = ( int ) bi ;
496+ return true ;
497+ }
498+ return false ;
499+ }
500+
501+ public static readonly BigInteger DoubleMaxValueAsBigInt = ( BigInteger ) double . MaxValue ;
502+
503+ public static readonly BigInteger DoubleMinValueAsBigInt = ( BigInteger ) double . MinValue ;
504+
406505 /// <summary>
407506 /// Format a valid double as a string.<br></br>
408507 /// This method should not be used on NaN or number greater than <see cref="double.MaxValue"/> or less than <see cref="double.MinValue"/>,<br></br>
@@ -450,7 +549,7 @@ public virtual string ToString(bool sortKeys = true, string keyValueSep = ": ",
450549 if ( double . IsNaN ( v ) ) { return "NaN" ; }
451550 return DoubleToString ( v ) ;
452551 }
453- case Dtype . INT : return Convert . ToInt64 ( value ) . ToString ( ) ;
552+ case Dtype . INT : return ( ( BigInteger ) value ) . ToString ( ) ;
454553 case Dtype . NULL : return "null" ;
455554 case Dtype . BOOL : return ( bool ) value ? "true" : "false" ;
456555 case Dtype . REGEX : return StrToString ( ( ( JRegex ) this ) . regex . ToString ( ) , true ) ;
@@ -699,23 +798,33 @@ public int CompareTo(object other)
699798 // we could simply say value.CompareTo(other) after checking if value is null.
700799 // It is more user-friendly to attempt to allow comparison of different numeric types, so we do this instead
701800 case Dtype . STR : return ( ( string ) value ) . CompareTo ( other ) ;
702- case Dtype . INT : // Convert.ToInt64 has some weirdness where it rounds half-integers to the nearest even
801+ case Dtype . INT : // JNode.ConvertToBigInteger has some weirdness where it rounds half-integers to the nearest even
703802 // so it is generally preferable to have ints and floats compared the same way
704803 // this way e.g. "3.5 < 4" will be treated the same as "4 > 3.5",
705804 // which is the same comparison but with different operand order.
706- // The only downside of this approach is that integers between
707- // 4.5036e15 (2 ^ 52) and 9.2234e18 (2 ^ 63)
708- // can be precisely represented by longs but not by doubles,
709- // so very large integers will have a loss of precision.
805+ // Obviously if value and other are both too big to be coerced to doubles we will compare them as BigIntegers
806+ BigInteger bi = ( BigInteger ) value ;
807+ if ( other is BigInteger obi )
808+ return bi . CompareTo ( obi ) ;
809+ if ( other is double od )
810+ {
811+ if ( bi > DoubleMaxValueAsBigInt )
812+ return 1 ;
813+ if ( bi < DoubleMinValueAsBigInt )
814+ return - 1 ;
815+ return ( ( double ) bi ) . CompareTo ( od ) ;
816+ }
817+ if ( other is bool b )
818+ return bi . CompareTo ( b ? BigInteger . One : BigInteger . Zero ) ;
819+ throw new ArgumentException ( "Can't compare numbers to non-numbers" ) ;
710820 case Dtype . FLOAT :
711- if ( ! ( other is long || other is double || other is bool ) )
821+ if ( ! ( other is BigInteger || other is double || other is bool ) )
712822 throw new ArgumentException ( "Can't compare numbers to non-numbers" ) ;
713- return Convert . ToDouble ( value ) . CompareTo ( Convert . ToDouble ( other ) ) ;
823+ return ( ( double ) value ) . CompareTo ( ConvertJNodeValueToDouble ( other ) ) ;
714824 case Dtype . BOOL :
715- if ( ! ( other is long || other is double || other is bool ) )
825+ if ( ! ( other is BigInteger || other is double || other is bool ) )
716826 throw new ArgumentException ( "Can't compare numbers to non-numbers" ) ;
717- if ( ( bool ) value ) return ( 1.0 ) . CompareTo ( Convert . ToDouble ( other ) ) ;
718- return ( 0.0 ) . CompareTo ( Convert . ToDouble ( other ) ) ;
827+ return ( ( bool ) value ? 1.0 : 0.0 ) . CompareTo ( ConvertJNodeValueToDouble ( other ) ) ;
719828 case Dtype . NULL :
720829 if ( other != null )
721830 throw new ArgumentException ( "Cannot compare null to non-null" ) ;
0 commit comments