简体   繁体   中英

Same input into [math]::round returns different results

I'm parsing a file in PowerShell. I have this section of code (with debug writes)

$max_file_size = 0
$max_file_size = $value
Write-Host $max_file_size # DEBUG
$max_file_size = Convert-to-Bytes $max_file_size
Write-Host $max_file_size # DEBUG
[string]$max_file_size = [math]::round($max_file_size, 0)
Write-Host $max_file_size # DEBUG

I'm seeing two different lines with the same data. The first pass returns this result:

8192.00000P
9223372036854775808.00000
9223372036854775808

The second pass returns this result:

8192.00000P
9223372036854775808.00000
9.22337203685478E+18

Wat?

Function Convert-to-Bytes
{
    #Pass in a value and convert to bytes.
    param ($value)

    If ($value -eq $Null)
    {
        $value = 0
    }

    #Convert case to upper.
    [string]$value = $value.ToString().ToUpper()

    #Strip off 'B' if in string.
    [string]$value = $value.TrimEnd("B")

    [decimal]$output = 0

    If ($value.Contains("K"))
    {
        $value = $value.TrimEnd("K")
        [decimal]$output = $value
        [decimal]$output = $output * 1KB
    }
    If ($value.Contains("M"))
    {
        $value = $value.TrimEnd("M")
        [decimal]$output = $value
        [decimal]$output = $output * 1MB
    }
    If ($value.Contains("G"))
    {
        $value = $value.TrimEnd("G")
        [decimal]$output = $value
        [decimal]$output = $output * 1GB
    }
    If ($value.Contains("T"))
    {
        $value = $value.TrimEnd("T")
        [decimal]$output = $value
        [decimal]$output = $output * 1TB
    }
    If ($value.Contains("P"))
    {
        $value = $value.TrimEnd("P")
        [decimal]$output = $value
        [decimal]$output = $output * 1PB
    }
    If ($value.Contains("yte"))
    {
        $value = $value.TrimEnd("yte")
        [decimal]$output = $value
        [decimal]$output = $output
    }
    Return $output
}

When you declare typed variable, then PowerShell add ArgumentTypeConverterAttribute to it, so all subsequent assignment to that variable would be converted to this type.

PS> $Var1=$null
PS> [string]$Var2=$null
PS> Get-Variable Var?|ft Name,Attributes -AutoSize

Name Attributes
---- ----------
Var1 {}
Var2 {System.Management.Automation.ArgumentTypeConverterAttribute}


PS> $Var1=1
PS> $Var2=1
PS> $Var1.GetType().FullName
System.Int32
PS> $Var2.GetType().FullName
System.String

At the end of first pass you declare max_file_size typed as string :

[string]$max_file_size = [math]::round($max_file_size, 0)

So on the second pass anything you assign to it got converted to string .

I placed lines in each Write-Host step like the following to add some type information:

$max_file_size.GetType().FullName

So that I would know the type each time. On the first pass

0 : System.Int32
8192.00000P : System.String
9223372036854775808.00000 : System.Decimal
9223372036854775808 : System.String

On the second pass in the same session

0 : System.String  
#^^^ String here makes sense since last it was cast to string in the previous run.
8192.00000P : System.String
9223372036854775808.00000 : System.String 
#^^^ Here is does not make sense since it should be a decimal returned from the function like before
9.22337203685478E+18 : System.String

Still working on it but [math]::round($max_file_size, 0) is given different data on the second pass, a String instead of a decimal. Working on exactly why. You can see the difference this way as well

PS C:\users\Cameron\Downloads> [math]::round("9223372036854775808.00000", 0)
9.22337203685478E+18

PS C:\users\Cameron\Downloads> [math]::round([decimal]"9223372036854775808.00000", 0)
9223372036854775808

Workaround

I know that adding this line to the beginning of the code addresses the issue. This way it executes the same every time.

Remove-Variable max_file_size

Could also force a decimal to be returned from the function.

[decimal]$max_file_size = Convert-to-Bytes $max_file_size

Forward

This is more question than answers but if no one else comes to help maybe I can figure this out as best I can.

Meat and Potatoes

Just want to keep this answer separate for now since this might just be a workspace for this issue unless someone else has the answer without thinking about it. So I am trying to use Trace-Command to see if I can figure out what is going on. There is a lot of information to potentially parse through but to keep it simple to start I did this.

Created a ps1 script with your function and the following code placed twice

Trace-Command -name TypeConversion -Expression {
$value = "8192.00000P"
$max_file_size = 0
$max_file_size = $value
$max_file_size = Convert-to-Bytes $max_file_size
[string]$max_file_size = [math]::round($max_file_size, 0)
} -PSHost

Put in a line so I would know where the first pass finished and the second on started. Thought to start with TypeConversion as that seemed on par with the issue. I have the following findings (I will show differences only.) Note that all the code lines were prefixed with DEBUG: TypeConversion Information but it was removed here for brevity and readabilty

The first pass has some information in regards to the Math assembly which only occurs once which makes sense

: 0 :         Conversion to System.Type
: 0 :             Found "System.Math" in the loaded assemblies.

The shortly after that where the code for $max_file_size = 0 and $max_file_size = $value occurs I see this in only the second pass

: 0 : Converting "0" to "System.String".
: 0 :     Converting numeric to string.
: 0 : Converting "8192.00000P" to "System.String".
: 0 :     Result type is assignable from value to convert's type

The towards the end where the real question come is what looks like the return from the function and the associated string assignment [string]$max_file_size = [math]::round($max_file_size, 0) .

1.st Pass

: 0 : Converting "9223372036854775808.00000" to "System.Decimal".
: 0 :     Result type is assignable from value to convert's type
: 0 : Converting "9223372036854775808" to "System.String".
: 0 :     Converting numeric to string.

2.nd Pass

: 0 : Converting "9223372036854775808.00000" to "System.Decimal".
: 0 :     Result type is assignable from value to convert's type
: 0 : Converting "9223372036854775808.00000" to "System.String".
: 0 :     Converting numeric to string.
: 0 : Converting to double or single.
: 0 : Converting "9.22337203685478E+18" to "System.String".
: 0 :     Converting numeric to string.

Look like the data leaves your custom function the same as "9223372036854775808.00000" but the assignment back to $max_file_size in the second pass is converting the value to a double as you can replicate with this code.

[double]"9223372036854775808.00000"
9.22337203685478E+18

I don't mean to waste anyones time with this but this is the best place for this issues scratch space.

Feel like Trace-Command might not get the information I need possibly because it cannot get debug information from the method round

Plot thickens

If I change the return variable to something like $another_max_file_size and then check the type of both $another_max_file_size and $max_file_size after the respective multiple passes I see that $max_file_size converts to [System.String] like we already determined and that $another_max_file_size stays as a [System.Decimal]

So we know now the source of the issue is assigning the output of your function from this line. Again, don't think the the return but the assignment operation itself.

$max_file_size = Convert-to-Bytes $max_file_size
#              ^ something is happening here. 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM