如何将REAL48浮点数转换为双精度double

3

我正在连接到一个Pervasive SQL数据库,该数据库将某些数据分成两个字段。DOUBLE字段实际上被分成fieldName_1和fieldName_2,其中_1是一个2字节的int,而_2是一个4字节的int。

我想使用PHP将这些值转换为可用的值。我有一些示例代码可以进行转换,但它是用我不理解的Delphi编写的:

{ Reconstitutes a SmallInt and LongInt that form }
{ a Real into a double.                          }
Function EntConvertInts (Const Int2 : SmallInt;
                         Const Int4 : LongInt) : Double; StdCall;
Var
  TheRealArray : Array [1..6] Of Char;
  TheReal      : Real;
Begin
  Move (Int2, TheRealArray[1], 2);
  Move (Int4, TheRealArray[3], 4);
  Move (TheRealArray[1], TheReal, 6);

  Result := TheReal;
End;

一些数据[fieldName_1,fieldName_2]

[132,805306368] -> 这应该是11

[132,1073741824] -> 这应该是12

我不理解逻辑,无法将其移植到PHP中。非常感谢任何帮助。谢谢。

编辑。 这是他们提供的C代码,显示符号/指数:

double real_to_double (real r)
/* takes Pascal real, return C double */
{
    union doublearray da;
    unsigned x;

    x = r[0] & 0x00FF;  /* Real biased exponent in x */
    /* when exponent is 0, value is 0.0 */
    if (x == 0)
        da.d = 0.0;
    else {
        da.a[3] = ((x + 894) << 4) |  /* adjust exponent bias */
                  (r[2] & 0x8000) |  /* sign bit */
                  ((r[2] & 0x7800) >> 11);  /* begin significand */
        da.a[2] = (r[2] << 5) |  /* continue shifting significand */
                  (r[1] >> 11);
        da.a[1] = (r[1] << 5) |
                  (r[0] >> 11);
        da.a[0] = (r[0] & 0xFF00) << 5; /* mask real's exponent */
    }
    return da.d;
}

我认为这不是Delphi代码,而是旧的Turbo Pascal代码。好吧,也许是16位的Delphi 1,它真的是类似于Turbo Pascal的强化版。我会在伪答案中加入更多细节,包括链接、语法高亮等。 - Arioch 'The
5个回答

4

我另外提供一个答案,因为我终于搞明白了。这里是PHP代码,可以将值转换。由于PHP不知道如何解包Real48(非标准),所以必须手动计算。请参见下面的注释说明。

function BiIntToReal48($f1, $f2){
  $x = str_pad(decbin($f1), 16, "0", STR_PAD_LEFT);
  $y = str_pad(decbin($f2), 32, "0", STR_PAD_LEFT);
  //full Real48 binary string
  $real48 = $y . $x;

  //Real48 format is V = (-1)^s * 1.f * 2^(exp-129)
  // rightmost eight bits are the exponent  (bits 40-->47)
  // subtract 129 to get the final value
  $exp = (bindec(substr($real48, -8)) - 129);

  //Sign bit is leftmost bit (bit[0])
  $sign =$real48[0];

  //Now work through the significand - bits are fractional binary 
  //(1/2s place, 1/4s place, 1/8ths place, etc)
  // bits 1-->39 
  // significand is always 1.fffffffff... etc so start with 1.0
  $sgf = "1.0";

  for ($i = 1; $i <= 39; $i++){
      if ($real48[$i] == 1){
        $sgf = $sgf + pow(2,-$i); 
      }       
  } 
  //final calculation
  $final = pow(-1, $sign) * $sgf * pow(2,$exp);
  return($final);
}
$field_1 = 132;
$field_2 = 805306368;      
$ConvVal = BiIntToReal48($field_1, $field_2);
// ^ gives $ConvVal = 11, qed

谢谢。实际上我已经很多年没有碰 PHP 了 - 我花了一会儿时间才记起它的所有工作原理。我已经忘记了它是一个多么庞大的隐式类型转换器! - J...
我很高兴不被迫使用PHP编程。 - LU RD
J - 这太棒了。非常感谢您坚持下来,我真的很感激您的努力。我将在周一进行全面测试,并查看数据库的完整运行情况。再次非常感谢您。 - Alexander Holsgrove

4

我已经花了一个星期的时间处理这个问题,为我们的组织找出解决方案。

我们的财务部门使用IRIS Exchequer,我们需要把成本拿出来。使用上述PHP代码,我成功地将其与以下代码(包括相关函数)一起在Excel VBA中运行。如果下面没有正确注明,我从www.sulprobil.com获得了所有长十进制转二进制函数。如果您将以下代码块复制并粘贴到一个模块中,您可以从单元格中引用我的ExchequerDouble函数。

在我继续之前,我必须指出C / PHP代码中的一个错误。如果您查看Significand循环:

C/PHP: Significand = Significand + 2 ^ (-i)
VBA:   Significand = Significand + 2 ^ (1 - i)

在测试过程中,我注意到答案非常接近,但经常是不正确的。进一步深入研究后,我将问题缩小到了有效数字上。这可能是由于从一种语言/方法转换代码造成的问题,或者可能只是一个打字错误,但添加(1-i)就有了很大的改善。

Function ExchequerDouble(Val1 As Integer, Val2 As Long) As Double
    Dim Int2 As String
    Dim Int4 As String
    Dim Real48 As String
    Dim Exponent As String
    Dim Sign As String
    Dim Significand As String

    'Convert each value to binary
    Int2 = LongDec2Bin(Val1, 16, True)
    Int4 = LongDec2Bin(Val2, 32, True)

    'Concatenate the binary strings to produce a 48 bit "Real"
    Real48 = Int4 & Int2

    'Calculate the exponent
    Exponent = LongBin2Dec(Right(Real48, 8)) - 129

    'Calculate the sign
    Sign = Left(Real48, 1)

    'Begin calculation of Significand
    Significand = "1.0"

    For i = 2 To 40
        If Mid(Real48, i, 1) = "1" Then
           Significand = Significand + 2 ^ (1 - i)
        End If
    Next i

    ExchequerDouble = CDbl(((-1) ^ Sign) * Significand * (2 ^ Exponent))
End Function

Function LongDec2Bin(ByVal sDecimal As String, Optional lBits As Long = 32, Optional blZeroize As Boolean = False) As String
    'Transforms decimal number into binary number.
    'Reverse("moc.LiborPlus.www") V0.3 P3 16-Jan-2011

    Dim sDec As String
    Dim sFrac As String
    Dim sD As String 'Internal temp variable to represent decimal
    Dim sB As String
    Dim blNeg As Boolean
    Dim i As Long
    Dim lPosDec As Long
    Dim lLenBinInt As Long

    lPosDec = InStr(sDecimal, Application.DecimalSeparator)

    If lPosDec > 0 Then
        If Left(sDecimal, 1) = "-" Then 'negative fractions later..
            LongDec2Bin = CVErr(xlErrValue)
            Exit Function
        End If

        sDec = Left(sDecimal, lPosDec - 1)
        sFrac = Right(sDecimal, Len(sDecimal) - lPosDec)
        lPosDec = Len(sFrac)
    Else
        sDec = sDecimal
        sFrac = ""
    End If

    sB = ""

    If Left(sDec, 1) = "-" Then
        blNeg = True
        sD = Right(sDec, Len(sDec) - 1)
    Else
        blNeg = False
        sD = sDec
    End If

    Do While Len(sD) > 0
        Select Case Right(sD, 1)
            Case "0", "2", "4", "6", "8"
                sB = "0" & sB
            Case "1", "3", "5", "7", "9"
                sB = "1" & sB
            Case Else
                LongDec2Bin = CVErr(xlErrValue)
            Exit Function
        End Select

        sD = sbDivBy2(sD, True)

        If sD = "0" Then
            Exit Do
        End If
    Loop

    If blNeg And sB <> "1" & String(lBits - 1, "0") Then
        sB = sbBinNeg(sB, lBits)
    End If

    'Test whether string representation is in range and correct
    'If not, the user has to increase lbits

    lLenBinInt = Len(sB)

    If lLenBinInt > lBits Then
        LongDec2Bin = CVErr(x1ErrNum)
        Exit Function
    Else
        If (Len(sB) = lBits) And (Left(sB, 1) <> -blNeg & "") Then
            LongDec2Bin = CVErr(xlErrNum)
            Exit Function
        End If
    End If

    If blZeroize Then sB = Right(String(lBits, "0") & sB, lBits)

    If lPosDec > 0 And lLenBinInt + 1 < lBits Then
        sB = sB & Application.DecimalSeparator
        i = 1

        Do While i + lLenBinInt < lBits
            sFrac = sbDecAdd(sFrac, sFrac) 'Double fractional part

            If Len(sFrac) > lPosDec Then
                sB = sB & "1"
                sFrac = Right(sFrac, lPosDec)

                If sFrac = String(lPosDec, "0") Then
                    Exit Do
                End If
            Else
                sB = sB & "0"
            End If

            i = i + 1
        Loop

        LongDec2Bin = sB
    Else
        LongDec2Bin = sB
    End If
End Function

Function LongBin2Dec(sBinary As String, Optional lBits As Long = 32) As String
    'Transforms binary number into decimal number.
    'Reverse("moc.LiborPlus.www") V0.3 PB 16-Jan-2011

    Dim sBin As String
    Dim sB As String
    Dim sFrac As String
    Dim sD As String
    Dim sR As String
    Dim blNeg As Boolean
    Dim i As Long
    Dim lPosDec As Long

    lPosDec = InStr(sBinary, Application.DecimalSeparator)

    If lPosDec > 0 Then
        If (Left(sBinary, 1) = "1") And Len(sBin) >= lBits Then 'negative fractions later..
            LongBin2Dec = CVErr(xlErrVa1ue)
            Exit Function
        End If

        sBin = Left(sBinary, lPosDec - 1)
        sFrac = Right(sBinary, Len(sBinary) - lPosDec)
        lPosDec = Len(sFrac)
    Else
        sBin = sBinary
        sFrac = ""
    End If

    Select Case Sgn(Len(sBin) - lBits)
        Case 1
            LongBin2Dec = CVErr(x1ErrNum)
            Exit Function
        Case 0
            If Left(sBin, 1) = "1" Then
                sB = sbBinNeg(sBin, lBits)
                blNeg = True
            Else
                sB = sBin
                blNeg = False
            End If
        Case -1
            sB = sBin
            blNeg = False
    End Select

    sD = "1"
    sR = "0"

    For i = Len(sB) To 1 Step -1
        Select Case Mid(sB, i, 1)
            Case "1"
                sR = sbDecAdd(sR, sD)
            Case "0"
                'Do Nothing
            Case Else
                LongBin2Dec = CVErr(xlErrNum)
                Exit Function
        End Select

        sD = sbDecAdd(sD, sD) 'Double sd
    Next i

    If lPosDec > 0 Then 'now the fraction
        sD = "0.5"

        For i = 1 To lPosDec
            If Mid(sFrac, i, 1) = "1" Then
                sR = sbDecAdd(sR, sD)
            End If

            sD = sbDivBy2(sD, False)
        Next i
    End If

    If blNeg Then
        LongBin2Dec = "-" & sR
    Else
        LongBin2Dec = sR
    End If
End Function

Function sbDivBy2(sDecimal As String, blInt As Boolean) As String
    'Divide sDecimal by two, blInt = TRUE returns integer only
    'Reverse("moc.LiborPlus.www") V0.3 PB 16-Jan-2011

    Dim i As Long
    Dim lPosDec As Long
    Dim sDec As String
    Dim sD As String
    Dim lCarry As Long

    If Not blInt Then
        lPosDec = InStr(sDecimal, Application.DecimalSeparator)

        If lPosDec > 0 Then
            'Without decimal point lPosDec already defines location of decimal point
            sDec = Left(sDecimal, lPosDec - 1) & Right(sDecimal, Len(sDecimal) - lPosDec)
        Else
            sDec = sDecimal
            lPosDec = Len(sDec) + 1 'Location of decimal point
        End If

        If ((1 * Right(sDec, 1)) Mod 2) = 1 Then
            sDec = sDec & "0" 'Append zero so that integer algorithm calculates division exactly
        End If
    Else
        sDec = sDecimal
    End If

    lCarry = 0

    For i = 1 To Len(sDec)
        sD = sD & Int((lCarry * 10 + Mid(sDec, i, 1)) / 2)
        lCarry = (lCarry * 10 + Mid(sDec, i, 1)) Mod 2
    Next i

    If Not blInt Then
        If Right(sD, Len(sD) - lPosDec + 1) <> String(Len(sD) - lPosDec + 1, "0") Then
        'frac part Is non - zero
            i = Len(sD)

            Do While Mid(sD, i, 1) = "0"
                i = i - 1 'Skip trailing zeros
            Loop

            'Insert decimal point again
            sD = Left(sD, lPosDec - 1) _
                & Application.DecimalSeparator & Mid(sD, lPosDec, i - lPosDec + 1)
        End If
    End If

    i = 1

    Do While i < Len(sD)
        If Mid(sD, i, 1) = "0" Then
            i = i + 1
        Else
            Exit Do
        End If
    Loop

    If Mid(sD, i, 1) = Application.DecimalSeparator Then
        i = i - 1
    End If

    sbDivBy2 = Right(sD, Len(sD) - i + 1)
End Function

Function sbBinNeg(sBin As String, Optional lBits As Long = 32) As String
    'Negate sBin: take the 2's-complement, then add one
    'Reverse("moc.LiborPlus.www") V0.3 PB 16-Jan-2011

    Dim i As Long
    Dim sB As String

    If Len(sBin) > lBits Or sBin = "1" & String(lBits - 1, "0") Then
        sbBinNeg = CVErr(xlErrValue)
        Exit Function
    End If

    'Calculate 2 's-complement
    For i = Len(sBin) To 1 Step -1
        Select Case Mid(sBin, i, 1)
            Case "1"
                sB = "0" & sB
            Case "0"
                sB = "1" & sB
            Case Else
                sbBinNeg = CVErr(xlErrValue)
            Exit Function
        End Select
    Next i

    sB = String(lBits - Len(sBin), "1") & sB

    'Now add 1
    i = lBits

    Do While i > 0
        If Mid(sB, i, 1) = "1" Then
            Mid(sB, i, 1) = "0"
            i = i - 1
        Else
            Mid(sB, i, 1) = "1"
            i = 0
        End If
    Loop

    'Finally strip leading zeros
    i = InStr(sB, "1")

    If i = 0 Then
        sbBinNeg = "0"
    Else
        sbBinNeg = Right(sB, Len(sB) - i + 1)
    End If
End Function

Function sbDecAdd(sOne As String, sTwo As String) As String
    'Sum up two string decimals.
    'Reverse("moc.LiborPlus.www") V0.3 PB 16-Jan-2011
    Dim lStrLen As Long
    Dim s1 As String
    Dim s2 As String
    Dim sA As String
    Dim sB As String
    Dim sR As String
    Dim d As Long
    Dim lCarry As Long
    Dim lPosDec1 As Long
    Dim lPosDec2 As Long
    Dim sF1 As String
    Dim sF2 As String

    lPosDec1 = InStr(sOne, Application.DecimalSeparator)

    If lPosDec1 > 0 Then
        s1 = Left(sOne, lPosDec1 - 1)
        sF1 = Right(sOne, Len(sOne) - lPosDec1)
        lPosDec1 = Len(sF1)
    Else
        s1 = sOne
        sF1 = ""
    End If

    lPosDec2 = InStr(sTwo, Application.DecimalSeparator)

    If lPosDec2 > 0 Then
        s2 = Left(sTwo, lPosDec2 - 1)
        sF2 = Right(sTwo, Len(sTwo) - lPosDec2)
        lPosDec2 = Len(sF2)
    Else
        s2 = sTwo
        sF2 = ""
    End If

    If lPosDec1 + lPosDec2 > 0 Then
        If lPosDecl > lPosDec2 Then
            sF2 = sF2 & String(lPosDec1 - lPosDec2, "0")
        Else
            sF1 = sFl & String(lPosDec2 - lPosDec1, "0")
            lPosDec1 = lPosDec2
        End If

        sF1 = sbDecAdd(sF1, sF2) 'Add fractions as integer numbers

        If Len(sF1) > lPosDecl Then
            lCarry = 1
            sF1 = Right(sF1, lPosDec1)
        Else
            lCarry = 0
        End If

        Do While lPosDec1 > 0
            If Mid(sF1, lPosDec1, 1) <> "0" Then
                Exit Do
            End If

            lPosDec1 = lPosDec1 - 1
        Loop

        sF1 = Left(sF1, lPosDec1)
    Else
        lCarry = 0
    End If

    lStrLen = Len(sl)

    If lStrLen < Len(s2) Then
        lStrLen = Len(s2)
        sA = String(lStrLen - Len(s1), "0") & s1
        sB = s2
    Else
        sA = s1
        sB = String(lStrLen - Len(s2), "0") & s2
    End If

    Do While lStrLen > 0
        d = 0 + Mid(sA, lStrLen, 1) + Mid(sB, lStrLen, 1) + lCarry

        If d > 9 Then
            sR = (d - 10) & sR
            lCarry = 1
        Else
            sR = d & sR
            lCarry = 0
        End If

        lStrLen = lStrLen - 1
    Loop

    If lCarry > 0 Then
        sR = lCarry & sR
    End If

    If lPosDec1 > 0 Then
        sbDecAdd = sR & Application.DecimalSeparator & sF1
    Else
        sbDecAdd = sR
    End If
End Function

这段代码是有用的,但有时(测试数据中约1%的情况下),与Excel插件中的Iris' EntDouble函数相比会少几个便士。我认为这是精度问题,除非有人能找到问题所在。
最终在VBA中使它工作是我检查一切是否正常的概念证明。这个功能的预期平台是SQL Server。如果您的Exchequer数据库链接到SQL Server,则应该可以直接对Pervasive数据库中的数据运行此函数。在我的情况下,我们将把过去2.5年的交易数据导出到SQL Server上的静态表中,但我们每年只处理这些数据一次,因此这不是问题。以下两个函数应该可以解决您的问题。就精度而言,它们与上面的VBA代码等效,但有时候可能会差几个便士,但似乎99%的时间是完全一样的。我们使用SQL Server 2000,因此可以通过一些优化(例如Varchar(MAX))来优化一些新版本,但据我所知,这应该可以正常工作。
CREATE FUNCTION dbo.FUNCTION_Exchequer_Double
(
    @Val1 AS SmallInt,
    @Val2 AS BigInt
)
RETURNS Decimal(38, 10)
AS
BEGIN
    -- Declare and set decoy variables
    DECLARE @Val1_Decoy AS SmallInt
    DECLARE @Val2_Decoy AS BigInt

    SELECT  @Val1_Decoy = @Val1,
            @Val2_Decoy = @Val2

    -- Declare other variables
    DECLARE @Val1_Binary AS Varchar(16)
    DECLARE @Val2_Binary AS Varchar(32)
    DECLARE @Real48_Binary AS Varchar(48)
    DECLARE @Real48_Decimal AS BigInt
    DECLARE @Exponent AS Int
    DECLARE @Sign AS Bit
    DECLARE @Significand AS Decimal(19, 10)
    DECLARE @BitCounter AS Int
    DECLARE @Two As Decimal(38, 10) -- Saves us casting inline in the code
    DECLARE @Output AS Decimal(38, 10)

    -- Convert values into two binary strings of the correct length (Val1 = 16 bits, Val2 = 32 bits)
    SELECT  @Val1_Binary = Replicate(0, 16 - Len(dbo.FUNCTION_Convert_To_Base(Cast(@Val1_Decoy AS Binary(2)), 2)))
                + dbo.FUNCTION_Convert_To_Base(Cast(@Val1_Decoy AS Binary(2)), 2),
            @Val2_Binary = Replicate(0, 32 - Len(dbo.FUNCTION_Convert_To_Base(Cast(@Val2_Decoy AS Binary(4)), 2)))
                + dbo.FUNCTION_Convert_To_Base(Cast(@Val2_Decoy AS Binary(4)), 2)

    -- Find the decimal value of the new 48 bit number and its binary value
    SELECT  @Real48_Decimal = @Val2_Decoy * Power(2, 16) + @Val1_Decoy
    SELECT  @Real48_Binary = @Val2_Binary + @Val1_Binary

    -- Determine the Exponent (takes the first 8 bits and subtracts 129)
    SELECT  @Exponent = Cast(@Real48_Decimal AS Binary(1)) - 129

    -- Determine the Sign
    SELECT  @Sign = Left(@Real48_Binary, 1)

    -- A bit of setup for determining the Significand
    SELECT  @Significand = 1,
            @Two = 2,
            @BitCounter = 2

    -- Determine the Significand
    WHILE   @BitCounter <= 40
            BEGIN
                IF Substring(@Real48_Binary, @BitCounter, 1) Like '1'
                    BEGIN
                        SELECT @Significand = @Significand + Power(@Two, 1 - @BitCounter)
                    END

                SELECT @BitCounter = @BitCounter + 1
            END

    SELECT  @Output = Power(-1, @Sign) * @Significand * Power(@Two, @Exponent)

    -- Return the output
    RETURN  @Output
END


CREATE FUNCTION dbo.FUNCTION_Convert_To_Base
(
    @value AS BigInt,
    @base AS Int
)
RETURNS Varchar(8000)
AS
BEGIN
    -- Code from http://dpatrickcaldwell.blogspot.co.uk/2009/05/converting-decimal-to-hexadecimal-with.html

    -- some variables
    DECLARE @characters Char(36)
    DECLARE @result Varchar(8000)

    -- the encoding string and the default result
    SELECT  @characters = '0123456789abcdefghijklmnopqrstuvwxyz',
            @result = ''

    -- make sure it's something we can encode.  you can't have
    -- base 1, but if we extended the length of our @character
    -- string, we could have greater than base 36
    IF      @value < 0 Or @base < 2 Or @base > 36
            RETURN Null

    -- until the value is completely converted, get the modulus
    -- of the value and prepend it to the result string.  then
    -- devide the value by the base and truncate the remainder
    WHILE   @value > 0
            SELECT  @result = Substring(@characters, @value % @base + 1, 1) + @result,
                    @value = @value / @base

    -- return our results
    RETURN  @result

END

请随意使用我的VBA或SQL代码。真正的辛苦工作是由上面将其转换为PHP的人完成的。如果有人发现任何改进的方法,请务必告诉我,以便我们可以使这段代码尽可能完美。

谢谢!


1
你需要的 1-i 是因为你从2->40计数,而我是从1->39计数。这是由于VBA数组将第一个元素计为 1,而许多其他语言(如PHP)将第一个数组值计为 0(因此,在谈论位索引时,你的i值会向上移动一位)。 - J...
是的,我第二天早上意识到了这一点(睡觉可以解决问题!),但我忘记再次发布了。不过,谢谢您为其他人澄清这一点! - ubercam

2
Delphi的Move命令用于将内存块从一个位置移动到另一个位置。这段代码看起来像是旧版Delphi代码 - Real类型已经过时,被Double取代(编辑Real48替换了6字节的Real),而Byte类型可能比Char更好用。两者都是字节,但Char更适用于单字节字符(ASCII)。这段代码的作用是:

1)声明一个长度为6个字节的Char数组(此处可以使用Byte),同时声明一个Real编辑现在是Real48类型)以存储转换后的值。

TheRealArray : Array [1..6] Of Char;
TheReal      : Real;

2) 将两个字节的Int值移动到TheRealArray - 从索引1开始移动2个字节的数据(即:所有的Int2,一个SmallInt(16位))。对于Int4也是同样的操作,将其从索引[3]开始,长度为4个字节。

Move (Int2, TheRealArray[1], 2);
Move (Int4, TheRealArray[3], 4);

如果你是从图片(而不是代码)开始的
Int2 = [2_byte0][2_byte1]
Int4 = [4_byte0][4_byte1][4_byte2][4_byte3]

你将会拥有:

TheRealArray = [2_byte0][2_byte1][4_byte0][4_byte1][4_byte2][4_byte3]

最后的移动命令将该数组复制到TheReal的内存位置,TheReal是一个实数(6字节浮点数)类型。它从数组的索引1开始,将其复制到TheReal中,并复制了总共六个字节(即整个数组)。
 Move (TheRealArray[1], TheReal, 6);

假设Int2和Int4中存储的数据被连接起来可以产生格式正确的Real48数据,那么将得到的数据存放在TheReal变量中。

在PHP中,字符串本质上是字节数组(类似于Delphi中的Char数组[1..6]),因此您可以使用unpack()函数将其转换为浮点数。


1
由于涉及到六个字节,浮点类型应该使用六字节类型Real48(以前称为Real),而不是8字节类型Double - Uli Gerhardt
有道理,这对于格式化来说是有意义的。我已经编辑了我的回答。 - J...
我不知道,我一直在尝试这段代码的变化,但我不知道它应该是什么样子的。了解更多关于存储格式的信息会很有帮助 - 我在这里猜测转换规则。上面的例程是由设计两个整数SQL存储编码的同一人提供的吗?field_1只使用了它16位中的一半(即:存储Real48的8位尾数)。即使知道字段对应的实际数字也会有所帮助。这将有助于弄清楚值的存储方式。 - J...
使用上述代码将141和1163395072作为整数使用会得到非常巨大的数字,例如(^-300数量级)。将“Real”类型更改为“Real48”会得到“4096”,这也没有太多意义。那是一个指数为141且单位尾数的实48...似乎有些不对劲。 - J...
1
使用“Byte”而不是“Char”,可以得到6315。使用“Byte”和“Real”会产生一个exp(263)幅度的数字……似乎在使用“Move”时也有字节序交换->即:[f1b1] [f1b2] [f2b3] [f2b4] [f2b1] [f2b2]是数组中的顺序。实际上需要一份说明存储在数据库中格式的文档,否则很难建立一个强大的转换器。使用Move可能不是一个好主意,因为字节序不能总是得到保证。 - J...
显示剩余4条评论

1

只是在等待J...的答案。 利用变体记录,代码有些简化:

Function EntConvertInts (Const Int2 : SmallInt;
                         Const Int4 : LongInt) : Double; StdCall;
Type
  TReal48PlaceHolder = record
    case boolean of
    true : (theRealArray : array [1..6] of byte);
    false : (r48 : Real48);
  end;

Var
  R48Rec : TReal48PlaceHolder;
Begin
  Move (Int2, R48Rec.theRealArray[1], 2);
  Move (Int4, R48Rec.theRealArray[3], 4);

  Result := R48Rec.r48;
End;

var
  r : Double;
begin
  r:= EntConvertInts(132,805306368);
  WriteLn(r); // Should be 11
  r:= EntConvertInts(141,1163395072);
  WriteLn(r); // Should be 6315
  ReadLn;

end.

谢谢。我正在尝试将其转换为PHP,因为它将处理从股票数据库中获取的数据。 - Alexander Holsgrove
我也遇到了这个问题——使用Real48和Byte类型似乎是“正确的”。但我仍然不太明白它是如何工作的。Real48被存储为(符号位)(39位尾数)(8位指数)。方程的运算方式是(-1)^s*(1.m)*2^(exp-129)。对于值为6315,指数应该是12(+129 = 141),以便将4096倍的1.541748046875与零符号位一起使用,但实际情况并非如此。对于11也是一样,它应该具有132指数(以给出2^(3)=8)乘以1.375,因此尾数应该是375->101110111,但实际结果并非如此——805306368是30000000 hex,只有两个(1)可以移动。可能漏掉了一些东西... - J...
我是个蠢货 - 我一直在使用.NET小数,它们将尾数存储为明确的二进制而不是分数...现在这一切都有意义了。下面是新答案。 - J...

0

这并不是“PHP代码”意义上的答案。我只是想警告任何可能通过Delphi标签找到此代码的人。

那不是Delphi!

这是旧版Turbo Pascal代码。好吧,也许是16位的Delphi 1,它真的是强化版的TP。

在32位的Delphi上不要尝试运行此代码,至少在替换已更改的Char和Real类型之前不要运行。这两种类型从Turbo Pascal时代开始就发生了变化,特别是6字节的Real类型,它从未与硬件FPU兼容!

可能FreePascal可以承载原始的TurboPascal代码,如果设置正确的模式,但最好还是使用Delphi模式和更新的代码。

应该确保 SmallInt 类型是 16 位整数 (int16),而 LongInt 是 32 位 (int32)。这似乎适用于 16 位、32 位和 64 位 Delphi 编译器,但在其他 Pascal 实现中可能会发生变化。

以下是我尝试修改的与现代Delphi兼容的代码。虽然我无法测试它。

希望这能帮助某些人将类似旧式TurboPascal代码转换为更新的版本。

这段代码直接跟随原始代码,但更加兼容、简洁和快速。

{ Reconstitutes a SmallInt and LongInt that form }
{ a Real into a double.                          }
Function EntConvertInts (Const Int2 : SmallInt;
                         Const Int4 : LongInt) : Double; 
(* StdCall; - only needed for non-Pascal DLLs  *)
Var
  TheRealArray : Packed Array [1..6] Of Byte; //AnsiChar  may suffice too

  TheReal      : Real48   absolute TheRealArray;
  TheInt2      : SmallInt absolute TheRealArray[1];
  TheInt4      : LongInt  absolute TheRealArray[3];
Begin
  Assert(SizeOf(TheInt2) = 2);
  Assert(SizeOf(TheInt4) = 2);
  Assert(SizeOf(TheReal) = 6);

  TheInt2 := Int2; (* Move (Int2, TheRealArray[1], 2); *)
  TheInt4 := Int4; (* Move (Int4, TheRealArray[3], 4); *)
                   (* Move (TheRealArray[1], TheReal, 6); *)

  Result := TheReal;
End;

这段代码直接使用了本地的Turbo Pascal功能无标签变体记录

{ Reconstitutes a SmallInt and LongInt that form }
{ a Real into a double.                          }
Function EntConvertInts (Const Int2 : SmallInt;
                         Const Int4 : LongInt) : Double; 
(* StdCall; - only needed for non-Pascal DLLs  *)
Var
  Value : Packed Record
            Case Byte of
              0: (TheReal: Real48);
              1: (Packed Record TheInt2: SmallInt;
                                TheInt4: LongInt; end; );
          end; 
Begin
  Assert(SizeOf(Value.TheInt2) = 2);
  Assert(SizeOf(Value.TheInt4) = 2);
  Assert(SizeOf(Value.TheReal) = 6);

  Value.TheInt2 := Int2; (* Move (Int2, TheRealArray[1], 2); *)
  Value.TheInt4 := Int4; (* Move (Int4, TheRealArray[3], 4); *)
                         (* Move (TheRealArray[1], TheReal, 6); *)

  Result := Value.TheReal;
End;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接