bmx-ng / bcc

A next-generation bcc parser for BlitzMax
zlib License
33 stars 13 forks source link

Optionally warn of data loss #576

Open GWRon opened 2 years ago

GWRon commented 2 years ago

Assume you have a function accepting "long" and you want to manually pass a value.

SuperStrict
Framework Brl.StandardIO

Function MyFunc:Long(p:Long)
  Return p
End Function

Print MyFunc($12345600000654321:Long)
Print MyFunc($12345600000654321)

If MyFunc($12345600000654321:Long) = MyFunc($12345600000654321)
    Print "equal"
Else
    Print "not equal"
EndIf

Output is:

2541543117843415841
2541543113548448545
not equal

BCC should possibly detect the "size/detail" of a passed value - and then decide if to really default to int/float, or if a long/double would fit better. Or at least print out a warning, that the second call (the one without ":long") leads to potential data loss (as it is understood as an "int" here)

davecamp commented 2 years ago

This code could be useful to classify a string literal integer to initially classify if it can fit within an Int, UInt, Long or ULong. There are some prerequisites and assumptions to the string data in that it expects 1 minus 'sign' digit at most at the beginning of the string, and expects the rest of the string to contain digits.

Credit where it is due... I ported the algorithm from the MS c++ stl library. The original algorithm works with multiple bases and I've limited this port to just base 10 for BlitzMax.

SuperStrict

Type IntegerLiteralTypeClassification
    Method New()
        UMaxInt = UInt(-1)
        MaxInt = UMaxInt Shr 1
        ABSMinInt = MaxInt + 1

        UMaxLong = ULong(-1)
        MaxLong = UMaxLong Shr 1
        ABSMinLong = MaxLong + 1
    EndMethod

    Method ClassifyInt:Int(In:String)
        Local StartIndex:Int = 0
        If In[0] = Asc("-")
            RiskyInt = AbsMinInt / 10
            MaxDigitInt = AbsMinInt Mod 10
            StartIndex = 1
        Else
            RiskyInt = MaxInt / 10
            MaxDigitInt = MaxInt Mod 10
        EndIf

        Return ClassifyInteger(In, StartIndex)
    EndMethod

    Method ClassifyUInt:Int(In:String)
        RiskyInt = UMaxInt / 10
        MaxDigitInt = UMaxInt Mod 10

        Return ClassifyInteger(In, 0)
    EndMethod

    Method ClassifyLong:Int(In:String)
        Local StartIndex:Int = 0
        If In[0] = Asc("-")
            RiskyLong = AbsMinLong / 10
            MaxDigitLong = AbsMinLong Mod 10
            StartIndex = 1
        Else
            RiskyLong = MaxLong / 10
            MaxDigitLong = MaxLong Mod 10
        EndIf

        Return ClassifyLongInteger(In, StartIndex)
    EndMethod

    Method ClassifyULong:Int(In:String)
        RiskyLong = UMaxLong / 10
        MaxDigitLong = UMaxLong Mod 10

        Return ClassifyLongInteger(In, 0)
    EndMethod

Private
    Method ClassifyInteger:Int(In:String, StartIndex:Int)
        Local Value:UInt = 0
        Local Overflowed:Int = False

        For Local i:Int = StartIndex Until In.Length
            Local Digit:Int = In[i] - 48
            If Value < RiskyInt Or (Value = RiskyInt And Digit <= MaxDigitInt)
                Value = Value * 10 + Digit
            Else
                Overflowed = True
            EndIf
        Next

        If Overflowed = True
            Return False
        EndIf

        Return True
    EndMethod

    Method ClassifyLongInteger:Int(In:String, StartIndex:Int)
        Local Value:ULong = 0
        Local Overflowed:Int = False

        For Local i:Int = StartIndex Until In.Length
            Local Digit:Int = In[i] - 48
            If Value < RiskyLong Or (Value = RiskyLong And Digit <= MaxDigitLong)
                Value = Value * 10 + Digit
            Else
                Overflowed = True
            EndIf
        Next

        If Overflowed = True
            Return False
        EndIf

        Return True
    EndMethod

    Field UMaxInt:UInt
    Field MaxInt:UInt
    Field AbsMinInt:UInt
    Field RiskyInt:UInt
    Field MaxDigitInt:UInt

    Field UMaxLong:ULong
    Field MaxLong:ULong
    Field AbsMinLong:ULong
    Field RiskyLong:ULong
    Field MaxDigitLong:ULong
EndType

Local Classifier:IntegerLiteralTypeClassification = New IntegerLiteralTypeClassification
Print Classifier.ClassifyInt("-2147483648")             ' fits into Int
Print Classifier.ClassifyInt("-2147483649")             ' does not fit into Int
Print Classifier.ClassifyInt("2147483648")              ' does not fit into Int
Print Classifier.ClassifyUInt("2147483648")             ' fits into UInt
Print Classifier.ClassifyLong("-9223372036854775808")   ' fits into Long
Print Classifier.ClassifyLong("9223372036854775807")    ' fits into Long
Print Classifier.ClassifyLong("9223372036854775808")    ' does not fit into Long
Print Classifier.ClassifyULong("9223372036854775808")   ' fits into ULong 
Print Classifier.ClassifyULong("18446744073709551615")  ' fits into ULong
Print Classifier.ClassifyULong("18446744073709551616")  ' does not fit into ULong