0

I'm trying to create a method converting a number to normalized scientific notation, here is the code that I'm using to calculate mantissa and exponent:

ConvNSN 1000, M, P
MsgBox M & "e" & P

Sub ConvNSN(N, M, P)
    If N = 0 Then
        P = 0
        M = 0
    Else
        P = Int(Log(Abs(N)) / Log(10))
        M = N / 10 ^ P
    End If
End Sub

The problem I am facing is that this code gives wrong exponent value for some numbers, eg 1000, 10E+6, 10E+9, 10E+12, 10E+13, etc... Exactly for 1000 converted should be 1e3, but not 10e2. It's obvious that the same problem with numbers, whose logarithms are close to an integer value, like Log(1 - 5.55111512312578E-17) / Log(10), which result is 0, however 1 - 5.55111512312578E-17 less then 1, and result has to be negative.

How can I get rid of Double type imprecision, and get this code to work properly?

UPDATE

I assume the fastest and quite accurate method to calculate mantissa and exponent of number in normalized scientific notation may be as follows:

Sub ConvNSN(N, M, P)
    Dim A
    If N = 0 Then
        P = 0
        M = 0
        Exit Sub
    End If
    A = Abs(N)
    If A < 1 Then
        P = Int(Log(A) / Log(10))
    Else
        P = Int(Log(A) / Log(10) * (2 + Log(.1) / Log(10)))
    End If
    M = N / 10 ^ P
End Sub

Or another one, based on @Bob's solution:

Sub ConvNSN(N, M, P)
    If N = 0 Then
        P = 0
        M = 0
    Else
        P = Int(Log(Abs(N)) / Log(10))
        M = N / 10 ^ P
    End If
    If Abs(M) = "10" Then
        M = M / 10
        P = P + 1
    End If
End Sub

First one slightly faster. Both of them process exponent from -322 to 308, but return not normalized mantissa with powers of 10 less then -310. I have not tested them yet with numbers, whose logarithms are a marginally less but very close to an integer values.

UPDATE 2

I decided to attach here an extra Sub ConvEN(), allowing to represent a number in engineering notation with SI prefixes from "p" to "T":

N = .0000456789
ConvNSN N, M, P
M = Round(M, 2)
ConvEN M, P, R, S
MsgBox R & " " & S & "Units"

Sub ConvNSN(N, M, P)
    Dim A
    If N = 0 Then
        P = 0
        M = 0
        Exit Sub
    End If
    A = Abs(N)
    If A < 1 Then
        P = Int(Log(A) / Log(10))
    Else
        P = Int(Log(A) / Log(10) * (2 + Log(.1) / Log(10)))
    End If
    M = N / 10 ^ P
End Sub

Sub ConvEN(M, P, R, S)
    DIM Q, P3
    Q = int(P / 3)
    P3 = Q * 3
    If Q >= -4 And Q <= 4 Then
        S = Array("p", "n", ChrW(&H03BC), "m", "", "k", "M", "G", "T")(Q + 4)
    Else
        S = "e" & P3 & " "
    End If
    R = M * 10 ^ (P - P3)
End Sub
omegastripes
  • 12,351
  • 4
  • 45
  • 96

2 Answers2

0

Try this:

ConvNSN 1000, M, P
MsgBox M & "E" & P

ConvNSN 0.00000000000000001234, M, P
MsgBox M & "E" & P

ConvNSN -0.00000000000000001234, M, P
MsgBox M & "E" & P

Sub ConvNSN(N, M, P)
  P = 0
  If N < 0 Then
    S = -1
  ElseIf N > 0 Then
    S = 1
  Else
    M = 0
    Exit Sub
  End If
  M = Abs(N)
  If M >= 10 Then
    While M >= 10
      M = M / 10
      P = P + 1
    Wend
    M = M * S
    Exit Sub
  End If
  If M < 1 Then
    While M < 1
      M = M * 10
      P = P - 1
    Wend
    M = M * S
    Exit Sub
  End If
End Sub

Based on the comments, I re-wrote this my way, ignoring the structure from the OP.

MsgBox NSN(-0.0000000000000000000123456789,4)
MsgBox NSN(1234567890000000000000000000,4)

Function NSN(Number, Accuracy)
  Exponent = 0
  If Number > 0 Then
    Sign = 1
  ElseIf Number < 0 Then
    Sign = -1
  Else
    NSN = 0
    Exit Function
  End If
  Number = Number * Sign
  If Number >= 10 Then
    While Number >= 10
      Number = Number / 10
      Exponent = Exponent + 1
    Wend
  ElseIf Number < 1 Then
    While Number < 1
      Number = Number * 10
      Exponent = Exponent - 1
    Wend
  End If
  Number = Round(Number, Accuracy)
  If Number = "10" Then
    Number = 1
    Exponent = Exponent + 1
  End If
  Number = Number * Sign
  If Exponent = 0 Then
    NSN = Number
  Else
    NSN = Number & "E" & Exponent
  End If
End Function
Bob
  • 1,045
  • 8
  • 10
  • What if `M >= 1 and M <= 10` ? Try `ConvNSN 5, M, P` – JosefZ Apr 05 '15 at 03:06
  • Numbers between 1 and 10 are not usually written using scientific notation. Scientific notation is the way that scientists easily handle very large numbers or very small numbers. – Bob Apr 05 '15 at 03:11
  • Seems `Round()` might be helpful. I've noticed `NSN(1e-14, 15)` returns `10E-13`. The only way to get proper result is to cut off last few digits, so `NSN(1e-14, 14)` returns `1E-14`. It fits if high precision is not need (like for displaying the values), since machine epsilon for `Double` is `1.11e-16`. Also I've tried `ConvNSN 1000, M, P: MsgBox M & "E" & P`, and it still gives `10E2` for me. – omegastripes Apr 05 '15 at 13:31
  • I found and corrected the issue with NSN(1e-14,15), it now returns 1E-14 as it should. ConvNSN 1000, M, P: MsgBox M & "E" & P works fine for me, returning 1E3 using the code from my first example. It's actually the first thing it tests. – Bob Apr 05 '15 at 13:49
  • I don't think you can get anything more than an accuracy of 14 using maths in VBS like this. You might be able to write something using string manipulation as @Trigger suggests if you need more than this. In my opinion, the NSN function is better than the ConvNSN sub in my example. – Bob Apr 05 '15 at 14:04
  • Сoercion of a number to a string while comparing with `"10"` IMO is native and the most accurate rounding. – omegastripes Apr 05 '15 at 15:06
0

Using strings rather than maths can help. Add your own error checking.

Num = "1000000.0005"
NumOfDigits = 4

Mag = Instr(Num, ".")
Num = Replace(Num, ".", "")
MSD = Left(Num, 1)
Rest = Mid(num, 2, NumOfDigits)


msgbox MSD & "." & Rest & " x 10^" & (Mag -2)
Trigger
  • 11
  • 2