Jump to content

czardas
 Share

Recommended Posts

I needed this library to continue working on one or two of my projects. Hopefully it will be of use to someone else too. These functions attempt to perform accurate calculations for the largest Int-64 values, testing for integer overflow and resorting to floats when results go out of range. This library is primarily intended to be used with Int-32 or Int-64 values and it offers no advantage whatsoever to pass floats as parameters. It only makes sense if you intend to perform accurate calculations on large Int-64 values. I know this can be done using strings too, but I prefer not to speculate as to the difference in time penalties.

; #INDEX# ======================================================================================================================
; Title .........: operator64
; AutoIt Version : 3.3.14.0
; Language ......: English
; Description ...: Helper functions for basic maths operations to be used with Int-32 and Int-64 numeric data types.
; Notes .........: Tests for integer overflow and attempts to correct for small inaccuracies affecting certain calculations.
;                  Return values are cast as Int-32 or Int-64 when possible, otherwise the result will be returned as a double.
;                  If it is not possible to return an Int-64 data type, the @extended flag is always set to non zero.
;                  Extended return values indicate that 100% accuracy cannot be guaranteed or that the result is ambiguous.
;                  The following values are valid for all functions in this library
;                  |@error = 1 The input or first operand is not an integer.
;                  |@error = 2 The second operand is not an integer.
;                  |@error = 3 Internal function error (ignore).
;                  |@extended = 1 The result is limited to double precision.
;                  |@extended = 2 The result is not an integer.
;                  |@extended = 3 The result is out of range for Int-64.
;                  |@extended = 4 The result is infinity.
;                  |@extended = 5 The result is an imaginary number.
;                  When an error occurs, the @extended flag is always set to non zero and a value returned regardless.
; Author(s) .....: czardas
; ==============================================================================================================================


; #CURRENT# ====================================================================================================================
; _Abs64
; _Add64
; _Divide64
; _Multiply64
; _Power64
; _Root64
; _Subtract64
; ==============================================================================================================================


; #INTERNAL_USE_ONLY#===========================================================================================================
; __DoubleTo64
; __Integer64
; __OverflowDetect
; __Product64
; __WholeNumberDivision
; ==============================================================================================================================


; #FUNCTION# ===================================================================================================================
; Name...........: _Abs64
; Description ...: Returns the absolute value of an integer.
; Syntax.........: _Abs64($iInteger)
; Parameters.....; $iInteger - The integer to operate on.
; Return values .: Returns an Int-32, or Int-64, value.
;                  Failure returns the executed expression and sets the following flags:
;                  |@error = 1 The input parameter is not an integer.
;                  |@extended = 2 The result is not an integer.
;                  |@extended = 3 The absolute value (2^63) is out of range for Int-64.
; Author.........: czardas
; ==============================================================================================================================

Func _Abs64($iInteger)
    $iInteger = __Integer64($iInteger)
    If @error Then Return SetError(1, 2, Abs($iInteger))
    If $iInteger = 0x8000000000000000 Then Return SetExtended(3, Abs($iInteger))
    Return $iInteger < 0 ? $iInteger *-1 : $iInteger
EndFunc ;==> _Abs64


; #FUNCTION# ===================================================================================================================
; Name...........: _Add64
; Description ...: Adds two integers together.
; Syntax.........: _Add64($iOperand_1, $iOperand_2)
; Parameters.....; $iOperand_1 - The first operand.
;                  $iOperand_2 - The second operand.
; Return values .: Returns an Int-32, or Int-64, value.
;                  Failure returns the executed expression and sets the following flags:
;                  |@error = 1 The first parameter is not an integer.
;                  |@error = 2 The second parameter is not an integer.
;                  |@extended = 1 The result is limited to double precision.
;                  |@extended = 3 The result is out of range for Int-64.
; Author.........: czardas
; ==============================================================================================================================

Func _Add64($iOperand_1, $iOperand_2)
    $iOperand_1 = __Integer64($iOperand_1)
    If @error Then Return SetError(1, 1, __DoubleTo64($iOperand_1 + $iOperand_2)) ; may still return an integer
    $iOperand_2 = __Integer64($iOperand_2)
    If @error Then Return SetError(2, 1, __DoubleTo64($iOperand_1 + $iOperand_2)) ; ditto

    Local $bOverflow, $nResult
    $bOverflow = __OverflowDetect('+', $iOperand_1, $iOperand_2, $nResult)
    Return SetExtended($bOverflow *3, $nResult)
EndFunc ;==> _Add64


; #FUNCTION# ===================================================================================================================
; Name...........: _Divide64
; Description ...: Divides two integers.
; Syntax.........: _Divide64($iOperand_1, $iOperand_2)
; Parameters.....; $iOperand_1 - The first operand.
;                  $iOperand_2 - The second operand.
;                  Failure returns the executed expression and sets the following flags:
;                  |@error = 1 The first parameter is not an integer.
;                  |@error = 2 The second parameter is not an integer.
;                  |@extended = 1 The result is limited to double precision.
;                  |@extended = 2 The result is not an integer.
;                  |@extended = 4 The result is infinity.
; Author.........: czardas
; Comments ......; Due to internal rounding, errors may return an integer with the extended flag set to 2. This is not a bug!
; ==============================================================================================================================

Func _Divide64($iOperand_1, $iOperand_2)
    $iOperand_1 = __Integer64($iOperand_1)
    If @error Then Return SetError(1, 1, __DoubleTo64($iOperand_1 / $iOperand_2)) ; may still return an integer
    $iOperand_2 = __Integer64($iOperand_2)
    If @error Then Return SetError(2, 1, __DoubleTo64($iOperand_1 / $iOperand_2)) ; as above

    If $iOperand_1 = 0x8000000000000000 Then ; this is not dealt with by the internal function
        If $iOperand_2 = 0x8000000000000000 Then Return 1 ; $iOperand_1 is divided by itself

        ; divide both values by 2 to remain within the specified range of the function __WholeNumberDivision()
        $iOperand_1 = 0xC000000000000000 ; -4611686018427387904
        $iOperand_2 = __WholeNumberDivision($iOperand_2, 2)
        If @extended Then Return SetExtended(@extended, $iOperand_1 / $iOperand_2)
    EndIf

    Local $nResult = __WholeNumberDivision($iOperand_1, $iOperand_2)
    Return SetExtended(@extended, $nResult)
EndFunc ;==> _Divide64


; #FUNCTION# ===================================================================================================================
; Name...........: _Multiply64
; Description ...: Multiplies two integers together.
; Syntax.........: _Multiply64($iOperand_1, $iOperand_2)
; Parameters.....; $iOperand_1 - The first operand.
;                  $iOperand_2 - The second operand.
; Return values .: Returns an Int-32, or Int-64, value.
;                  Failure returns the executed expression and sets the following flags:
;                  |@error = 1 The first parameter is not an integer.
;                  |@error = 2 The second parameter is not an integer.
;                  |@extended = 2 The result is not an integer.
;                  |@extended = 3 The result is out of range for Int-64.
; Author.........: czardas
; Comments ......; Due to internal rounding, errors may return an integer with the extended flag set to 2. This is not a bug!
; ==============================================================================================================================

Func _Multiply64($iOperand_1, $iOperand_2)
    $iOperand_1 = __Integer64($iOperand_1)
    If @error Then Return SetError(1, 2, $iOperand_1 * $iOperand_2)
    $iOperand_2 = __Integer64($iOperand_2)
    If @error Then Return SetError(2, 2, $iOperand_1 * $iOperand_2)

    Local $iProduct = __Product64($iOperand_1, $iOperand_2)
    Return SetExtended(@extended, $iProduct)
EndFunc ;==> _Multiply64


; #FUNCTION# ===================================================================================================================
; Name...........: _Power64
; Description ...: Raises an integer to the power of a second integer.
; Syntax.........: _Power64($iInteger, $iPower)
; Parameters.....; $iInteger - The integer to operate on.
;                  $iPower - The power to raise the integer to.
; Return values .: Returns an Int-32, or Int-64, value.
;                  Failure returns the executed expression and sets the following flags:
;                  |@error = 1 The first parameter is not an integer.
;                  |@error = 2 The second parameter is not an integer.
;                  |@extended = 1 The result is limited to double precision.
;                  |@extended = 3 The result is out of range for Int-64.
; Author.........: czardas
; ==============================================================================================================================

Func _Power64($iInteger, $iPower)
    $iInteger = __Integer64($iInteger)
    If @error Then Return SetError(1, 1, $iInteger ^ $iPower)
    $iPower = __Integer64($iPower)
    If @error Then Return SetError(2, 1, $iInteger ^ $iPower)

    If $iInteger = 1 Or $iPower = 0 Then Return 1
    If $iPower < 0 Or $iPower > 63 Then Return SetExtended(1, $iInteger ^ $iPower)

    Local $iTriggers = 0, $iCount = 0, $iPow = $iPower

    While $iPow > 1
        If Mod($iPow, 2) Then
            $iPow -= 1
        Else ; to reduce calls to __Product64()
            ; triggers indicate the product should be squared (see next loop)
            $iTriggers += 2 ^ $iCount ; set trigger
            $iPow /= 2
        EndIf
        $iCount += 1
    WEnd

    Local $iOperand, $iResult = $iInteger

    For $i = $iCount -1 To 0 Step -1
        ; multiply the product either by itself or the original value
        $iOperand = BitAND($iTriggers, 2^$i) ? $iResult : $iInteger
        $iResult = __Product64($iResult, $iOperand)
        If @extended Then Return SetExtended(3, $iInteger ^ $iPower)
    Next

    Return $iResult
EndFunc ;==> _Power64


; #FUNCTION# ===================================================================================================================
; Name...........: _Root64
; Description ...: Calculates the nth root of an integer.
; Syntax.........: _Root64($iInteger, $iRoot)
; Parameters.....; $iInteger - The integer to operate on.
;                  $iRoot - The root of the integer to calculate.
; Return values .: Returns an Int-32, or Int-64, value.
;                  Failure returns the executed expression and sets the following flags:
;                  |@error = 1 The first parameter is not an integer.
;                  |@error = 2 The second parameter is not an integer.
;                  |@extended = 1 The result is limited to double precision.
;                  |@extended = 2 The result is not an integer.
;                  |@extended = 5 The result is an imaginary number.
; Author.........: czardas
; Comments ......; _Root64() has limited application beyond forcing an integer return value whenever possible.
;                  Roots of negative integers return a defined value if one exists, otherwise the result is always undefined.
; ==============================================================================================================================

Func _Root64($iInteger, $iRoot)
    $iInteger = __Integer64($iInteger)
    If @error Then Return SetError(1, 1, $iInteger ^ (1 / $iRoot))
    $iRoot = __Integer64($iRoot)
    If @error Then Return SetError(2, 1, $iInteger ^ (1 / $iRoot))

    If $iRoot > 63 Or $iRoot < 1 Then Return SetExtended(1, $iInteger ^ (1 / $iRoot)) ; out of range

    If $iRoot = 1 Then Return $iInteger

    Local $iSign = $iInteger < 0 ? -1 : 1
    If $iSign = -1 And Not Mod($iRoot, 2) Then Return SetExtended(5, $iInteger ^ (1 / $iRoot)) ; undefined

    If $iInteger = 0x8000000000000000 Then ; here the absolute value cannot be represented by Int-64
        ; a small set of defined return values are simply hard coded
        Local $aMaxRoot = [[3,-2097152],[7,-512],[9,-128],[21,-8],[63,-2]] ; there are only five cases
        For $i = 0 To 4
            If $iRoot = $aMaxRoot[$i][0] Then Return $aMaxRoot[$i][1]
        Next
        Return SetExtended(2, $iInteger ^ (1 / $iRoot)) ; the return value is not an integer
    EndIf

    ; positive values are used to calculate the odd roots of negative integers
    Local $iOperand = $iSign = 1 ? $iInteger : $iInteger *-1

    ; here the margin of error with Int() is probably too small to necessitate further processing
    Local $iReturn = Int($iOperand ^ (1 / $iRoot))

    ; check the result
    Local $iCompare = $iReturn
    For $i = 2 To $iRoot
         $iCompare *= $iReturn
    Next

    ; the chances that this comparison will fail seem negligible - see comments above
    If $iCompare <> $iOperand Then Return SetExtended(2, $iInteger ^ (1 / $iRoot)) ; the return value is not an integer

    Return $iReturn * $iSign
EndFunc ;==> _Root64


; #FUNCTION# ===================================================================================================================
; Name...........: _Subtract64
; Description ...: Subtracts one integer from another.
; Syntax.........: _Subtract64($iOperand_1, $iOperand_2)
; Parameters.....; $iOperand_1 - The first operand.
;                  $iOperand_2 - The second operand.
; Return values .: Returns an Int-32, or Int-64, value.
;                  Failure returns the executed expression and sets the following flags:
;                  |@error = 1 The first parameter is not an integer.
;                  |@error = 2 The second parameter is not an integer.
;                  |@extended = 1 The result is limited to double precision.
;                  |@extended = 3 The result is out of range for Int-64.
; Author.........: czardas
; ==============================================================================================================================

Func _Subtract64($iOperand_1, $iOperand_2)
    $iOperand_1 = __Integer64($iOperand_1)
    If @error Then Return SetError(1, 1, __DoubleTo64($iOperand_1 - $iOperand_2)) ; may still return an integer
    $iOperand_2 = __Integer64($iOperand_2)
    If @error Then Return SetError(2, 1, __DoubleTo64($iOperand_1 - $iOperand_2)) ; ditto

    Local $bOverflow, $nResult
    $bOverflow = __OverflowDetect('-', $iOperand_1, $iOperand_2, $nResult)
    Return SetExtended($bOverflow *3, $nResult)
EndFunc ;==> _Subtract64


#Region - Internal Functions

; #INTERNAL_USE_ONLY# ==========================================================================================================
; Name...........: __DoubleTo64
; Description ...: Helper function - converts an integer value double to Int-32 or Int-64.
; Syntax.........: __DoubleTo64($nValue)
; Parameters.....; $nValue - The double to convert
; Return values .: Success returns Int-32 or Int-64 when the interpreter evaluates the converted double equal to the input.
;                  Failure returns the input parameter without any modification and sets the following flags:
;                  |@error = 3 Internal error (x2) ==> see comments in the code.
;                  |@extended = 2 Conversion failed ==> the return value is not an integer.
; Author ........: czardas
; Comments ......; Doubles representing integer values greater than 15 digits cannot be relied on for accuracy.
; ==============================================================================================================================

Func __DoubleTo64($nValue)
    If Not IsNumber($nValue) Then Return SetError(3, 2, $nValue) ; the input is not a number.
    If $nValue > -1.0e+015 And $nValue < 1.0e+015 Then
        Local $iVal64 = Number($nValue, 2) ; convert to Int-64
        If $iVal64 = $nValue Then ; check to see if conversion was a success [expected range +/- 5.62949953421311e+014]
            Return ($iVal64 > 2147483647 Or $iVal64 < -2147483648) ? $iVal64 : Number($iVal64, 1) ; Int-64 or Int-32
        ElseIf $iVal64 -1 = $nValue Then ; attempt to adjust for inaccuracies [subject to possible change]
            Return $iVal64 -1
        ElseIf $iVal64 +1 = $nValue Then ; as above
            Return $iVal64 +1
        EndIf
    EndIf

    Return SetError(3, 2, $nValue) ; conversion failed
EndFunc ;==> __DoubleTo64


; #INTERNAL_USE_ONLY# ==========================================================================================================
; Name...........: __Integer64
; Description ...: Checks if a number is an integer and converts doubles to Int-32 or Int-64 if the result appears unambiguous.
; Syntax.........: __Integer64($nParam)
; Parameters.....; $nParam - The number to test
; Return values .: Success returns Int-32 or Int-64 when the interpreter evaluates the returned integer equal to the input.
;                  Failure returns the input parameter without any modification and sets the following flags:
;                  |@error = 1 The input is not an integer.
;                  |@extended = 2 The return value is not an integer.
; Author ........: czardas
; Comments ......; Doubles representing integer values greater than 15 digits cannot be relied on for accuracy.
; ==============================================================================================================================

Func __Integer64($nParam)
    If Not StringInStr(VarGetType($nParam), 'Int') Then
        Local $iInt64 = __DoubleTo64($nParam) ; attempt conversion
        If @error Then Return SetError(1, 2, $nParam) ; $fParam <> $iInt64
        $nParam = $iInt64 ; float was compared as being equal to an integer
    EndIf
    Return $nParam
EndFunc ;==> __Integer64


; #INTERNAL_USE_ONLY# ==========================================================================================================
; Name...........: __OverflowDetect
; Description ...: Checks for integer overflow on execution of the expression.
; Syntax.........: __OverflowDetect($sOperator, $iOperand_1, $iOperand_2, $nResult)
; Parameters.....; $sOperator - May be +, - or *.
;                  $iOperand_1 - The first operand.
;                  $iOperand_2 - The second operand.
;                  $nResult - [ByRef] Result of executed expression.
; Return values .: Returns True if integer overflow occurs - otherwise returns False. Returns $nResult ByRef.
; Author ........: czardas
; Comments ......; Int-32 or Int-64 only. No error checks!
; ==============================================================================================================================

Func __OverflowDetect($sOperator, $iOperand_1, $iOperand_2, ByRef $nResult)
    Local $iExecute, $fCompare

    Switch $sOperator
        Case '+' ; execute the expression
            $iExecute = $iOperand_1 + $iOperand_2
            ; execute the expression with the operands converted to doubles
            $fCompare = Number($iOperand_1, 3) + Number($iOperand_2, 3)
        Case '-' ; as above
            $iExecute = $iOperand_1 - $iOperand_2
            $fCompare = Number($iOperand_1, 3) - Number($iOperand_2, 3)
        Case '*' ; as above
            $iExecute = $iOperand_1 * $iOperand_2
            $fCompare = Number($iOperand_1, 3) * Number($iOperand_2, 3)
    EndSwitch

    ; the results should be approximately equal
    Local $bReturn = StringFormat('%.14e', $iExecute) <> StringFormat('%.14e', $fCompare) ; %.15e is too sensitive

    $nResult = $bReturn ? $fCompare : $iExecute
    Return $bReturn
EndFunc ;==> __OverflowDetect


; #INTERNAL_USE_ONLY# ==========================================================================================================
; Name...........: __Product64($iOperand_1, $iOperand_2)
; Description ...: Helper function - multiplies two Int-32 or Int-64 values together.
; Syntax.........: __Product64($iOperand_1, $iOperand_2)
; Parameters.....; $iOperand_1 - The first integer.
;                  $iOperand_2 - The second integer.
; Return values .: Returns an Int-32, or Int-64, value.
;                  Failure returns the executed expression and sets the following flags:
;                  |@extended = 3 The result is out of range for Int-64.
; Author.........: czardas
; Comments ......; Int-32 or Int-64 only. No error checks!
; ==============================================================================================================================

Func __Product64($iOperand_1, $iOperand_2)
    Local $bOverflow, $nResult
    $bOverflow = __OverflowDetect('*', $iOperand_1, $iOperand_2, $nResult)
    Return SetExtended($bOverflow *3, $nResult)
EndFunc ;==> __Product64


; #INTERNAL_USE_ONLY# ==========================================================================================================
; Name...........: __WholeNumberDivision
; Description ...: Divides two integers.
; Syntax.........: __WholeNumberDivision($iDividend, $iDivisor)
; Parameters.....; $iDividend - The first operand.
;                  $iDivisor - The second operand.
; Return values .: Returns an Int-32, or Int-64, value.
;                  Failure returns the executed expression and sets the following flags:
;                  |@extended = 1 The result is limited to double precision.
;                  |@extended = 2 The result is not an integer.
;                  |@extended = 4 The result is infinity.
; Author ........: czardas
; Comments ......; Input is limited to +/- 9223372036854775807 for both consitency and compatibility with future libraries.
;                  May return an integer with the extended flag set to 2 due to internal rounding. This is not a bug!
; ==============================================================================================================================

Func __WholeNumberDivision($iDividend, $iDivisor) ; Input ranges -9223372036854775807 To 9223372036854775807
    If $iDivisor = 0 Then Return SetExtended(4, $iDividend / $iDivisor) ; division by zero

    Local $aDiv = [$iDividend, $iDivisor], _
    $iSign = 1

    For $i = 0 To 1
        If $aDiv[$i] > 0x7FFFFFFFFFFFFFFF Or $aDiv[$i] < 0x8000000000000001 Then Return SetExtended(1, $iDividend / $iDivisor) ; input range exceeded

        If $aDiv[$i] < 0 Then ; force positive integers
            $aDiv[$i] *= -1
            $iSign *= -1 ; to add back later
        EndIf
    Next

    If Mod($aDiv[0], $aDiv[1]) Then Return SetExtended(2, $iDividend / $iDivisor) ; not divisible
    If $aDiv[0] = 0 Then Return 0
    If $aDiv[1] = 1 Then Return $aDiv[0] * $iSign

    Local $iDivision = Floor($aDiv[0] / $aDiv[1]), $iDifference, $iIntegral

    While $iDivision * $aDiv[1] > $aDiv[0] ; division is overstated
        $iDifference = ($aDiv[1] * $iDivision) - $aDiv[0]
        $iIntegral = Floor($iDifference / $aDiv[1]) ; avoid shooting beyond the target
        If $iIntegral = 0 Then $iIntegral = 1 ; prevents hanging in an infinite loop
        $iDivision -= $iIntegral
    WEnd

    While $iDivision * $aDiv[1] < $aDiv[0] ; division is understated
        $iDifference = $aDiv[0] - ($aDiv[1] * $iDivision)
        $iIntegral = Floor($iDifference / $aDiv[1]) ; as above
        If $iIntegral = 0 Then $iIntegral = 1 ; prevents hanging
        $iDivision += $iIntegral
    WEnd

    Return $iDivision * $iSign
EndFunc ;==> __WholeNumberDivision

#EndRegion


The following tests need updating because values for @error and @extended have changed.

#include 'operator64.au3'

ConsoleWrite("Testing _Abs64()" & @LF)                       ; ==> Err  Ext Result
$test = _Abs64(0xC000000000000000)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   4611686018427387904
$test = _Abs64(0x8000000000000000)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 2    1   9.22337203685478e+018
$test = _Abs64(0x8000000000000001)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   9223372036854775807
$test = _Abs64(-0.123)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 1    1   0.123
;===========================================================

ConsoleWrite(@LF & "Testing _Add64()" & @LF)
$test = _Add64(0x7FFFFFFFFFFFFFFF, 1)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    1   9.22337203685478e+018
$test = _Add64(0x7FFFFFFFFFFFFFFF, 0)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   9223372036854775807
$test = _Add64(0x7FFFFFFFFFFFFFFF, -1)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   9223372036854775806
$test = _Add64(33, 15)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   48
;===========================================================

ConsoleWrite(@LF & "Testing _Divide64()" & @LF)
$test = _Divide64(0x7FFFFFFFFFFFFFFF, -1)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   -9223372036854775807
$test = _Divide64(0x8000000000000000, -2)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   4611686018427387904
$test = _Divide64(10.0, 2)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   5
$test = _Divide64(9223372036854775552, -2)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   -4611686018427387776
$test = _Divide64(9223372036854775552, 0)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 2    1   1.#INF
$test = _Divide64(675432.0097, -987)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 1    1   -684.328277304965
$test = _Divide64(6922337, 15)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 4    1   461489.133333333
;===========================================================

ConsoleWrite(@LF & "Testing _Multiply64()" & @LF)
$test = _Multiply64(9223372036854775552, -2)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    1   -1.84467440737096e+019
$test = _Multiply64(223372036854775552, 9)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   2010348331692979968
$test = _Multiply64(1.01, -1)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 1    1   -1.01
;===========================================================

ConsoleWrite(@LF & "Testing _Power64()" & @LF)
$test = _Power64(-2.0, 63)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   -9223372036854775808
$test = _Power64(37, 12.0)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   6582952005840035281
$test = _Power64(17, 19)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    1   2.39072435685151e+023
;===========================================================

ConsoleWrite(@LF & "Testing _Subtract64()" & @LF)
$test = _Subtract64(1.0, 1)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   0
$test = _Subtract64(0x8000000000000000, -1)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   -9223372036854775807
$test = _Subtract64(0x8000000000000000, 0)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    0   -9223372036854775808
$test = _Subtract64(0x8000000000000000, 1)
ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0    1   -9.22337203685478e+018

It's nice to try and figure out something like this for yourself, but it would be quite impossible without the hints and advice I receive from others when I take a wrong turn sometimes. So thanks to those people. :)

Edited by czardas
Link to comment
Share on other sites

I have optimized the code for _Power64() as best as I know how. There is still quite a time penalty, but I see this as a trade off for performing accurate calculations with large integers. I'm sure if there was no time penalty involved then the world would look slightly different. I have also changed the behaviour of the UDF: now all functions accept integer value doubles of up to 15 digits.

Edited by czardas
Link to comment
Share on other sites

I said I dared not to speculate about speed, but I lied. :P Speed test comparisons with BigNum revealed that operator64 is about 2 to 3 times faster for all maths functions. Of course with operator64, accuracy is limited to the set of whole numbers which can be represented as Int-64 data types. I'm reasonably happy with these results. Perhaps, when I look around, I will discover better solutions to the ones I came up with. Maybe in the future there will be changes to AutoIt which will render some of these functions obsolete. I'm sure there must be plenty of non-native libraries which do a similar job much faster - so be it! :mellow:

#include 'operator64.au3'
#include 'BigNum.au3'

Local $iOperand1, $iOperand2, $iTimer, $iIterations = 10000

;=================================================================

; Add 2 numbers
ConsoleWrite('Addition' & @LF)

$iOperand1 = 70009256455583555
$iOperand2 = 198116002927730039

;ConsoleWrite(_Add64($iOperand1, $iOperand2) & @LF)
;ConsoleWrite(_BigNum_Add($iOperand1, $iOperand2) & @LF)

$iTimer = TimerInit()
For $i = 1 To $iIterations
    _Add64($iOperand1, $iOperand2)
Next
ConsoleWrite('operator64.au3 ... ' & TimerDiff($iTimer) & @LF)

$iTimer = TimerInit()
For $i = 1 To $iIterations
    _BigNum_Add($iOperand1, $iOperand2)
Next
ConsoleWrite('BigNum.au3 ....... ' & TimerDiff($iTimer) & @LF & @LF)

;=================================================================

; Subtract 2 numbers
ConsoleWrite('Subtraction' & @LF)

$iOperand1 = 198116002927730039
$iOperand2 = 70009256455583555

;ConsoleWrite(_Subtract64($iOperand1, $iOperand2) & @LF)
;ConsoleWrite(_BigNum_Sub($iOperand1, $iOperand2) & @LF)

$iTimer = TimerInit()
For $i = 1 To $iIterations
    _Subtract64($iOperand1, $iOperand2)
Next
ConsoleWrite('operator64.au3 ... ' & TimerDiff($iTimer) & @LF)

$iTimer = TimerInit()
For $i = 1 To $iIterations
    _BigNum_Sub($iOperand1, $iOperand2)
Next
ConsoleWrite('BigNum.au3 ....... ' & TimerDiff($iTimer) & @LF & @LF)

;=================================================================

; Divide 2 numbers
ConsoleWrite('Division' & @LF)

$iOperand1 = 0x8000000000000000
$iOperand2 = -8

;ConsoleWrite(_Divide64($iOperand1, $iOperand2) & @LF)
;ConsoleWrite(_BigNum_Div($iOperand1, $iOperand2) & @LF)

$iTimer = TimerInit()
For $i = 1 To $iIterations
    _Divide64($iOperand1, $iOperand2)
Next
ConsoleWrite('operator64.au3 ... ' & TimerDiff($iTimer) & @LF)

$iTimer = TimerInit()
For $i = 1 To $iIterations
    _BigNum_Div($iOperand1, $iOperand2)
Next
ConsoleWrite('BigNum.au3 ....... ' & TimerDiff($iTimer) & @LF & @LF)

;=================================================================

; Multiply 2 numbers
ConsoleWrite('Multiplication' & @LF)

$iOperand1 = 999999996
$iOperand2 = 234567873

;ConsoleWrite(_Multiply64($iOperand1, $iOperand2) & @LF)
;ConsoleWrite(_BigNum_Mul($iOperand1, $iOperand2) & @LF)

$iTimer = TimerInit()
For $i = 1 To $iIterations
    _Multiply64($iOperand1, $iOperand2)
Next
ConsoleWrite('operator64.au3 ... ' & TimerDiff($iTimer) & @LF)

$iTimer = TimerInit()
For $i = 1 To $iIterations
    _BigNum_Mul($iOperand1, $iOperand2)
Next
ConsoleWrite('BigNum.au3 ....... ' & TimerDiff($iTimer) & @LF & @LF)

;=================================================================

; raise a number to a power
ConsoleWrite('Power Operator' & @LF)

$iOperand1 = -2
$iOperand2 = 63
$iIterations = 2000

;ConsoleWrite(_Power64($iOperand1, $iOperand2) & @LF)
;ConsoleWrite(_BigNum_Pow($iOperand1, $iOperand2) & @LF)

$iTimer = TimerInit()
For $i = 1 To $iIterations
    _Power64($iOperand1, $iOperand2)
Next
ConsoleWrite('operator64.au3 ... ' & TimerDiff($iTimer) & @LF)

$iTimer = TimerInit()
For $i = 1 To $iIterations
    _BigNum_Pow($iOperand1, $iOperand2)
Next
ConsoleWrite('BigNum.au3 ....... ' & TimerDiff($iTimer) & @LF & @LF)

Results

Addition
operator64.au3 ... 611.45171195762
BigNum.au3 ....... 1001.19733099668

Subtraction
operator64.au3 ... 593.831397302383
BigNum.au3 ....... 1295.27405555922

Division
operator64.au3 ... 753.401508513282
BigNum.au3 ....... 1744.577881667

Multiplication
operator64.au3 ... 670.554139638136
BigNum.au3 ....... 1547.65752329044

Power Operator
operator64.au3 ... 1174.47467967118
BigNum.au3 ....... 3246.95088321818

One further point worth mentioning - this library is intended for calculations which are likely to result in overflow on Int-64, or are that are handled internally by floating point operations: which cannot produce accurate results for the whole Int-64 numeric range.

Edited by czardas
Link to comment
Share on other sites

Update:

Some values were registering as having overflowed because an internal string comparison was too sensitive. Sensitivity can be reduced down to just a few digits: so this constitutes fine tuning rather than an actual bug.

Error values for the function _Divide64() have changed.

Edited by czardas
Link to comment
Share on other sites

As I was writing the following, function I kept thinking about Route 66 but that's something else. ;) I don't really know what use _Root64() might have: it just seemed to be missing from the current library. Perhaps the function is superfluous because roots of integers do not suffer from integer overflow - just tiny FP inaccuracies. It does have some added functionality in that it calculates odd roots of negative integers, but its usefulness is limited to calculating roots when you require an integer to be returned and that's probably not such a common occurrence - hmm! You might want to get back to the original value after using _Power64() and check for errors. :think:
 

#include 'operator64.au3'

ConsoleWrite(_Root64(-6071163615208263051, 11) & @LF)
ConsoleWrite(_Root64(0x8000000000000000, 7) & @LF)
ConsoleWrite(_Root64(4052555153018976267, 13) & @LF)


; #FUNCTION# ===================================================================================================================
; Name...........: _Root64
; Description ...: Calculates the nth root of an integer.
; Syntax.........: _Root64($iInteger, $iRoot)
; Parameters.....; $iInteger - The integer to operate on.
;                  $iRoot - The root of the integer to calculate.
; Return values .: Returns an Int-32, or Int-64, value.
;                  Failure returns the executed expression and sets @error as follows:
;                  |@error = 1 The first parameter is not an integer.
;                  |@error = 2 The second parameter is not an integer.
;                  |@error = 3 The result cannot be represented as an integer.
;                  |@error = 4 Undefined - the result cannot be represented - period.
; Author.........: czardas
; Comments ......; Experimental because:
;                  1. Code to accomodate unexpected exceptions was removed due to lack of evidence that a problem exists.
;                  2. _Root64() has limited application beyond forcing an integer return value whenever possible.
;                  Roots of negative integers return a defined value if one exists, otherwise the result is always undefined.
; ==============================================================================================================================

Func _Root64($iInteger, $iRoot)
    $iInteger = __Integer64($iInteger)
    If @error Then Return SetError(1, 1, $iInteger ^ (1 / $iRoot))
    $iRoot = __Integer64($iRoot)
    If @error Then Return SetError(2, 1, $iInteger ^ (1 / $iRoot))

    If $iRoot > 63 Or $iRoot < 1 Then Return SetError(3, 1, $iInteger ^ (1 / $iRoot)) ; out of range

    If $iRoot = 1 Then Return $iInteger

    Local $iSign = $iInteger < 0 ? -1 : 1
    If $iSign = -1 And Not Mod($iRoot, 2) Then Return SetError(4, 1, $iInteger ^ (1 / $iRoot)) ; undefined

    If $iInteger = 0x8000000000000000 Then ; here the absolute value cannot be represented by Int-64
        ; a small set of defined return values are simply hard coded
        Local $aMaxRoot = [[3,-2097152],[7,-512],[9,-128],[21,-8],[63,-2]] ; there are only five cases
        For $i = 0 To 4
            If $iRoot = $aMaxRoot[$i][0] Then Return $aMaxRoot[$i][1]
        Next
        Return SetError(3, 1, $iInteger ^ (1 / $iRoot)) ; the return value is not an integer
    EndIf

    ; positive values are used to calculate the odd roots of negative integers
    Local $iOperand = $iSign = 1 ? $iInteger : $iInteger *-1

    ; here the margin of error with Int() is probably too small to necessitate further processing
    Local $iReturn = Int($iOperand ^ (1 / $iRoot))

    ; check the result
    Local $iCompare = $iReturn
    For $i = 2 To $iRoot
         $iCompare *= $iReturn
    Next

    ; the chances that this comparison will fail seem negligible - see comments above
    If $iCompare <> $iOperand Then Return SetError(3, 1, $iInteger ^ (1 / $iRoot)) ; the return value is not an integer

    Return $iReturn * $iSign
EndFunc ;==> _Root64

 

Edited by czardas
Link to comment
Share on other sites

  • 2 weeks later...

Changes to @error and @extended values. Note division by zero no longer returns an error. The values below are consistent throughout the UDF, applying to all functions. New version in the first post.

;                  |@error = 1 The input or first operand is not an integer.
;                  |@error = 2 The second operand is not an integer.
;                  |@error = 3 Internal function error (ignore).
;                  |@extended = 1 The result is limited to double precision.
;                  |@extended = 2 The result is not an integer.
;                  |@extended = 3 The result is out of range for Int-64.
;                  |@extended = 4 The result is infinity.
;                  |@extended = 5 The result is an imaginary number.

Also added _Root64() to the UDF.

I believe this is finished, unless future changes to the AutoIt core render some of these functions outmoded or superfluous.

Edited by czardas
Link to comment
Share on other sites

  • 1 year later...

A question about base conversion came up recently. This presents a good opportunity to write a practical example using some of these functions. It makes good sense to post examples here. I have made a couple of modifications to my original code after running a few tests.

#include 'operator64.au3'

Func DecToBase($iInt, $iBase)
    $iInt = __Integer64($iInt)
    If @error Or $iInt < 0 Then Return SetError(1, 0, '') ; positive integers only

    $iBase = __Integer64($iBase)
    If @error Or $iBase < 2 Or $iBase > 9 Then Return SetError(2, 0, '') ; bases 2 to 9 only

    Local $iRem, $sRet = ''
    While $iInt >= $iBase
        $iRem = Mod($iInt, $iBase)
        $sRet = $iRem & $sRet
        $iInt = _Divide64(($iInt - $iRem), $iBase)
    WEnd
    Return $iInt & $sRet
EndFunc ;==> DecToBase

Func BaseToDec($sInt, $iBase)
    $iBase = __Integer64($iBase)
    If @error Or $iBase < 2 Or $iBase > 9 Then Return SetError(2, 0, '') ; bases 2 to 9 only

    $sInt = StringRegExpReplace($sInt, '(\A0+)(0\z|.+)', '$2') ; strip leading zeros
    If $sInt == '' Or Not __BaseIsValid($sInt, $iBase) Then Return SetError(1, 0, '') ; out of range or invalid digits

    Local $iRet = 0, $iLen = StringLen($sInt)
    For $i = 1 To $iLen
        $iRet += StringMid($sInt, $i, 1) * _Power64($iBase, $iLen - $i)
    Next
    Return $iRet
EndFunc ;==> BaseToDec

Func __BaseIsValid($sInt, $iBase) ; bases 2 to 9 only
    If StringRegExp($sInt, '[^0-' & $iBase -1 & ']') Then Return False ; non-valid digits detected
    Local $aMaxVal = ['','', _
    '111111111111111111111111111111111111111111111111111111111111111', _ ; binary
    '2021110011022210012102010021220101220221', _ ; ternary
    '13333333333333333333333333333333', _ ; quaternary
    '1104332401304422434310311212', _ ; quinary
    '1540241003031030222122211', _ ; senary
    '22341010611245052052300', _ ; septenary
    '777777777777777777777', _ ; octal
    '67404283172107811827'] ; nonary

    Return StringLen($sInt) < StringLen($aMaxVal[$iBase]) Or StringCompare($sInt, $aMaxVal[$iBase]) <= 0
EndFunc ;==> __BaseIsValid

; =========== Example ===========
Local $iInt = 9012345678901234567
Local $sBase6 = DecToBase($iInt, 6)

ConsoleWrite("base 6 ... " & $sBase6 & @LF)
ConsoleWrite("Int 64 ... " & BaseToDec($sBase6, 6) & @LF)


If you want negative numbers, you can remove the sign and put it back after conversion. Bear in mind that the value 0xFFFFFFFFFFFFFFFF is a special case because the positive equivalent results in integer overflow. These functions are intended to be used with decimal integers beyond 15 digits. For smaller numbers, using operator64 may be overkill. Beyond Int64 you will need to use BigNum UDF functions.

NOTE : I have only used operator64 functions as and when needed. Overflow does not occur with addition or multiplication because validation ensures that input values remain within range.

Original Thread: https://www.autoitscript.com/forum/topic/185951-convert-dec-to-binary-but-ones-and-zeros/?do=findComment&comment=1335687

Edited by czardas
Added: strip leading zeros in the function BaseToDec() - to correct a problem with base validation.
Link to comment
Share on other sites

  • 4 weeks later...

!170 :P

#include 'operator64.au3'

; slightly crazy
For $i = 21 To 171
    ConsoleWrite($i & '! ... ' & _Factorial64($i) & @LF)
Next

; slightly more crazy
Local $j
For $i = 21 To 171
    $j = Random(1, $i, 1)
    ConsoleWrite('_Combinations64('& $i & ', ' & $j & ') ... ' & _Combinations64($i, $j) & @LF)
Next

Func _Factorial64($iInt)
    $iInt = __Integer64($iInt)
    If @error Or $iInt < 0 Then Return SetError(1, 0, '') ; positive integers only

    Local $iResult = 1
    For $i = 1 to $iInt <= 20 ? $iInt : 20
        $iResult *= $i
    Next

    For $i = 21 to $iInt
        $iResult = _Multiply64($iResult, $i)
    Next
    Return SetExtended(@extended, $iResult)
EndFunc ; _Factorial64

Func _Combinations64($n, $k)
    $n = __Integer64($n)
    If @error Or $n < 1 Then Return SetError(1, 0, '') ; positive integers > 0 only

    $k = __Integer64($k)
    If @error Or $k < 1 Then Return SetError(2, 0, '') ; positive integers > 0 only

    If $k > $n Then Return 0

    $k = _Multiply64(_Factorial64($n -$k), _Factorial64($k))
    $n = _Factorial64($n)

    Return _Divide64($n, $k)
EndFunc ;==> _Combinations64

There will be small floating point inaccuracies affecting _Combination64() with input values > 20. This is to be expected when internal calculations resort to using large floats. The return values in this case should be considered as being potentially, although not necessarily, understated. The larger the input, the more fuzzy the result. BigNum functions return precise results at the cost of speed.

NOTE Operator64 functions attempt to return an integer whenever possible. With large return values, there is a seamless transition from integers to floats: which is one of the main points of this UDF.

Edited by czardas
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...