In this puzzle we tried to recreate the numput/get functions.
was made by lexikos, but before that jeeswg attemped a different approach which ultimately contained some errors. Jeeswg was given one point for a good effort and partial solution, and I left it open for anyone to try to fix his version for one additional point. At the time I wrote a fix for
Code: Select all
;slightly improved PuzPut function which handles rounding
;the MsgBox indicating a slight discrepancy now appears less frequently
;before: e.g. 2381/10000 = 0.2381 [24%]
;after: e.g. 67/10000 = 0.0067 [0.7%]
;AHK v2 script
;'PuzGet'/'PuzPut' - recreate NumGet/NumPut functionality
;where 'Puz' refers to 'Code Puzzle Thread'
;note: when converting from/to Floats,
;the fractions could be *fractionally* different,
;although the tests show the expected accuracy
;note: the maths of handling the raw binary data
;of the Floats is very fiddly, so errors are possible
;AHK v2 is used because you can convert from/to Doubles
;by using the address of a variable
w:: ;test PuzPut and PuzGet functions
VarSetCapacity(vData1, 8, 0)
VarSetCapacity(vData2, 8, 0)
NumPut(-12345678, &vData1, "Int")
PuzPut(-12345678, &vData2, "Int")
MsgBox(NumGet(&vData1, "Int"))
MsgBox(NumGet(&vData2, "Int"))
MsgBox(PuzGet(&vData1, "Int"))
return
double_to_float_to_double(d){
local
static inf := 1.0e400
if (d == 0 || instr(d, "n") || instr(d, "i")) ; no need to do anything in these cases, -0.0, 0.0, NaN or inf
return d
d := d * 1.0 ; Ensure double
; Write the double to an address from where we can get it as an int64
varsetcapacity(dd, 8)
puzput(d, &dd, "double")
i := puzget(&dd, "int64") ; The double as int64
; Split the int64 into sign, exponenent and fraction
sign_bit := i < 0 ; get sign bit
i &= ~(1 << 63) ; remove sign bit
exp := ( i >> 52 ) - 1023 + 127 ; get exponenent and convert it to "float exponent"
fraction := (i & (2**52-1) ) >> (52 - 23) ; get fraction and convert it to "float fraction"
if (exp >= 0xff) ; it is inf, NaN was detected above
return inf * (sign_bit ? -1 : 1)
; When converting a double to float, the result might be either larger or smaller than the original number (or equal but that is covered here too).
; Pick the one closest to the original. If there is a tie, choose the one which doesn't have its most significant bit set.
loop 2 {
uint_%a_index% := ( sign_bit << 31 ) | ( exp << 23 ) | fraction ; assemble the sign, exponenent and fraction as a uint
puzput( ( sign_bit << 31 ) | ( exp << 23 ) | fraction, &dd, "uint" ) ; interpret this uint as a float, reusing dd.
d%a_index% := PuzGet(&dd, "float") ; interpret this float as a double
fraction += 1 ; next float
}
; Now check which float is the closest to the original double, and pick it.
diff1 := abs(d-d1)
diff2 := abs(d-d2)
if (diff1 == diff2) {
; tie! pick the one which doesn't have its most significant bit set.
if !(uint_1 & 1)
return d1
return d2
}
if (diff1 < diff2)
return d1
return d2
}
q:: ;test PuzPut and PuzGet functions
;oType := StrSplit("Char,Short,Int,Int64,Ptr,UChar,UShort,UInt,UInt64,UPtr", ",")
;oType := StrSplit("Char,Short,Int,Int64,Ptr,UChar,UShort,UInt,UInt64,UPtr,Float,Double", ",")
oType := StrSplit("Float,Double", ",")
VarSetCapacity(vData1, 8)
VarSetCapacity(vData2, 8)
vCount := 0
Loop 10000
{
try
{
vNum := Random(-2147483648, 2147483647)
vNum *= 256**3
;vTemp := Random(1, oType.Length())
vType := "float" ;oType[vTemp]
NumPut(vNum, &vData1, vType)
PuzPut(vNum, &vData2, vType)
vNum1 := NumGet(&vData1, vType)
vNum2 := NumGet(&vData2, vType)
vNum1X := PuzGet(&vData1, vType)
vHex1 := Format("0x{:X}", vNum1)
vHex2 := Format("0x{:X}", vNum2)
vHex1X := Format("0x{:X}", vNum1X)
}
catch
continue
;vCount++
vIsErr := 0
;if 0
if !(vNum1 = vNum2)
;if !InStr(vType, "Int64")
MsgBox("put`r`n" "index: " A_Index "`r`n" vType "`r`n" vNum1 "`r`n" vNum2 "`r`n" vHex1 "`r`n" vHex2)
, vIsErr := 1
;if 0
if !(vNum1 = vNum1X)
MsgBox("get`r`n" "index: " A_Index "`r`n" vType "`r`n" vNum1 "`r`n" vNum1X "`r`n" vHex1 "`r`n" vHex1X)
, vIsErr := 1
if vIsErr
vCount++
}
MsgBox("done " vCount)
return
;==================================================
PuzGet(vAddr, vType:="UPtr")
{
local
static oChar, oType := {Char:1, Short:2, Int:4, Int64:8, Ptr:A_PtrSize=8?8:4}
static vIsReady := 0
if !vIsReady
{
oChar := {0:0}
VarSetCapacity(vData, 1, 0)
Loop 255
PuzPut(A_Index, &vData, "UChar")
, vOrd := Ord(StrGet(&vData, 1, "CP0"))
, oChar[vOrd] := A_Index
vIsReady := 1
}
if (vType = "Double")
{
vData := 0.0
memcpy(&vData, vAddr, 8)
return vData
}
if (vType = "Float")
{
vNum := PuzGet(vAddr+0, "UInt")
vSign := !!(vNum & 0x80000000)
vPow := ((vNum & 0x7F800000) >> 23)
vNum := vNum & 0x7FFFFF
if (vPow = 255)
vPow := 2047
else if (vPow > 0)
vPow := (vPow - 127 + 1023)
;else if (vPow = 0)
; vPow := 0
vNum <<= (52-23)
vTemp := (vSign ? -0x8000000000000000 : 0) | (vPow << 52) | vNum
VarSetCapacity(vData, 8)
PuzPut(vTemp, &vData, "Int64")
return PuzGet(&vData, "Double")
}
vIsSigned := !RegExMatch(vType, "i)^U")
vSize := oType[RegExReplace(vType, "i)^U")]
vNum := 0
Loop vSize
vNum |= oChar[Ord(StrGet(vAddr+A_Index-1, 1, "CP0"))] << (A_Index*8-8)
if vIsSigned
if (vSize = 1) && (vNum >= 128)
return vNum - 256
else if (vSize = 2) && (vNum >= 32768)
return vNum - 65536
else if (vSize = 4) && (vNum >= 2147483648)
return vNum - 4294967296
return vNum
}
PuzPut(vNum, vAddr, vType:="UPtr")
{
local
static oChar, oType := {Char:1, Short:2, Int:4, Int64:8, Ptr:A_PtrSize=8?8:4}
static vIsReady := 0, vDataNull := 0
if !vIsReady
{
;oChar := {0:""}
oChar := {}
VarSetCapacity(vDataNull, 1, 0)
Loop 255
oChar[A_Index] := Chr(A_Index)
vIsReady := 1
}
if (vType = "Double")
{
vData := vNum + 0.0
memcpy(vAddr, &vData, 8)
return
}
if (vType = "Float")
{
vData := vNum + 0.0
vData := double_to_float_to_double(vData)
vNum := PuzGet(&vData, "Int64")
;0x8000000000000000 (UInt64) = 0x8000000000000000 - 0x10000000000000000 (Int64) = -0x8000000000000000 (Int64)
vSign := !!(vNum & -0x8000000000000000)
vPow := ((vNum & 0x7FF0000000000000) >> 52)
vNum := vNum & 0xFFFFFFFFFFFFF
if (vPow = 2047)
vPow := 255
else if (vPow > 0)
vPow := (vPow - 1023 + 127) & 0xFF
;else if (vPow = 0)
; vPow := 0
vBit := (vNum >> (52-24)) & 0x1 ;truncate number, but if bit is 1, round up
vNum >>= (52-23)
;MsgBox vBit
;if (vBit)
;throw
;handle rounding:
;if 0
if vBit
{
vTemp := 1
Loop 22
{
if !(vNum & vTemp)
{
vNum |= vTemp
vNum ^= vTemp-1
break
}
vTemp <<= 1
}
if (vTemp = 2**22)
vNum := 0, vPow += 1
}
vTemp := (vSign << 31) | (vPow << 23) | vNum
PuzPut(vTemp, vAddr+0, "UInt")
return
}
vSize := oType[RegExReplace(vType, "i)^U")]
Loop Min(vSize, 7)
vTemp := vNum & (0xFF << (A_Index*8-8))
, vTemp >>= (A_Index*8-8)
, (vTemp=0) ? memcpy(vAddr+A_Index-1, &vDataNull, 1) : memcpy(vAddr+A_Index-1, oChar.GetAddress(vTemp), 1)
;0xFF00000000000000 (UInt64) = (0xFF00000000000000 - 0x10000000000000000) (Int64) = -0x100000000000000 (Int64)
if (vSize = 8)
vTemp := SubStr(Format("0x{:016X}", vNum & -0x100000000000000), 1, 4)
, vTemp := Integer(vTemp)
, (vTemp=0) ? memcpy(vAddr+7, &vDataNull, 1) : memcpy(vAddr+7, oChar.GetAddress(vTemp), 1)
}
memcpy(dest, src, count){
/*
void *memcpy(
void *dest,
const void *src,
size_t count
);
url: https://msdn.microsoft.com/en-us/library/dswaw1wk.aspx (memcpy)
*/
return dllcall("MSVCRT.dll\memcpy", "ptr", dest, "ptr", src, "ptr", count, "cdecl")
}
;prepend Z to MsgBox, and it becomes a NoOp
ZMsgBox(oParams*)
{
}
It requires an older v2 alpha version, it seems ok with a100. I added a function
which correctly converts double to float (and back to double), which is the part which jeeswg didn't get quite right.
to start the script, and it has no exitapp hotkey so you have to manually close it from the tray
Cheers.