【问题标题】:PowerShell: Converting String Representations of Numbers to IntegersPowerShell:将数字的字符串表示形式转换为整数
【发布时间】:2022-01-23 02:55:46
【问题描述】:

我已经非常努力地不问这个问题,但我一直在回过头来,因为我不确定我是否尽我所能高效地做所有事情,或者是否可能存在问题。基本上,我有一个 CSV 文件,其中包含一个数字字段,但它包含一个小数和十万分之一的值,例如15.0000。我需要做的就是将其转换为不带小数位的整数。

我遇到了related question here,但所选答案似乎让人怀疑将字符串表示直接转换为整数数据类型 - 没有解释原因。

简单地将字符串转换为int 不会可靠地工作。您需要将其转换为int32

我没有太多运气让[System.Convert] 方法工作,或者做类似$StringNumber.ToInt32() 的事情。我意识到,一旦我将数据保存回PSCustomObject,它们就会被存储为字符串,所以归根结底,我可能会让这比我的用例更复杂,我只需要重新格式化$StringNumber...但即使这样也给我带来了一些问题。

关于为什么在我的情况下强制转换不是可靠或更好的方法来处理这个问题有什么想法吗?

我尝试过的示例:

PS > $StringNumber = '15.0000'

PS > [Convert]::ToInt32($StringNumber)
#MethodInvocationException: Exception calling "ToInt32" with "1" argument(s): "Input string was not in a correct format."

PS > [Convert]::ToInt32($StringNumber, [CultureInfo]::InvariantCulture)
#MethodInvocationException: Exception calling "ToInt32" with "2" argument(s): "Input string was not in a correct format."

PS > $StringNumber.ToInt32()
#MethodException: Cannot find an overload for "ToInt32" and the argument count: "0".

PS > $StringNumber.ToInt32([CultureInfo]::InvariantCulture)
#MethodInvocationException: Exception calling "ToInt32" with "1" argument(s): "Input string was not in a correct format."

PS > $StringNumber.ToString("F0")
#MethodException: Cannot find an overload for "ToString" and the argument count: "1".

PS > $StringNumber.ToString("F0", [CultureInfo]::CurrentCulture)
#MethodException: Cannot find an overload for "ToString" and the argument count: "2".

PS > "New format: {0:F0}" -f $StringNumber
#New format: 15.0000

所以基本上我想出的是:

总结:有没有办法安全地将我的$StringNumber 转换成一个整数,如果是这样,在大型数据集上最有效的方法是什么?

奖励挑战: 如果有人可以使用ForEach magic method 完成这项工作,那么我会给你买啤酒。这是一些不起作用的伪代码,但如果它起作用会很棒。据我所知,在设置 string property 的值时无法引用集合中的当前项目@

#This code DOES NOT work as written
PS > $CSVData = Import-Csv .\somedata.csv
PS > $CSVData.ForEach('StringNumberField', [int]$_.StringNumberField)

【问题讨论】:

  • 铸造[int]应该不是问题...至少在你遇到[decimal]::MaxValue[long]::MaxValue之前
  • @SantiagoSquarzon 是对的。但是你也可以通过艰难的方式做到这一点。 $d = [Convert]::ToDecimal($StringNumber, [CultureInfo]::InvariantCulture) [System.Decimal]::ToInt32($d)

标签: powershell


【解决方案1】:
  • 如果您的字符串表示可以解释为数字,您可以将其转换为整数,只要使用的特定整数类型足够大 容纳(整数部分)表示的值(例如[int] '15.0000'

    • 不能被解释为数字或表示太大(或小,对于负数)的数字的字符串对于目标类型,导致语句终止错误;例如[int] 'foo'[int] '444444444444444'

    • 请注意,PowerShell 的 casts隐式字符串到数字的转换 使用 invariant culture,这意味着 只有无论当前有效的文化是什么(如反映在$PSCulture)。

    • 至于整数类型,您可以使用(除了开放式[bigint] 类型外,所有类型都支持::MinValue::MaxValue 来确定它们可以容纳的整数范围;例如[int]::MaxValue)

      • 有符号整数类型:[sbyte][int16][int] ([int32])、[long] ([int64])、[bigint]
      • 无符号 整数类型:[byte][uint16][uint] ([uint32])、[ulong] ([uint64]) - 但请注意 PowerShell 本身仅使用 有符号 em> 在其计算中使用本机类型。
  • 转换为整数类型会执行half-to-even中点四舍五入,这意味着字符串表示的值的小数部分为.5 被四舍五入到最接近的 even 整数;例如[int] '1.5'[int] '2.5' 舍入到 2

    • 要选择不同的中点舍入策略,请使用[Math]::Round()System.MidpointRounding 参数;例如:

      [Math]::Round('2.5', [MidPointRounding]::AwayFromZero) # -> 3
      
    • 无条件向上或向下舍入到最接近的整数,请使用[Math]::Ceiling()[Math]::Floor()[Math]::Truncate();例如:

      [Math]::Ceiling('2.5')    # -> 3
      [Math]::Floor('2.5')      # -> 2
      [Math]::Truncate('2.5')   # -> 2
      #
      [Math]::Ceiling('-2.5')   # -> -2
      [Math]::Floor('-2.5')     # -> -3
      [Math]::Truncate('-2.5')  # -> -2
      
    • 注意:虽然得到的数字概念上是一个整数,技术上它是一个 [double] 或 - 带有显式 [decimal] 或 integer-number-literal 输入 - [decimal]


关于奖励挑战

  • 使用整数类型转换:
[int[]] (Import-Csv .\somedata.csv).StringNumberField

注意:(Import-Csv .\somedata.csv).StringNumberField.ForEach([int]) 也可以,但在这里没有优势。

(Import-Csv .\somedata.csv).StringNumberField.ForEach(
  { [Math]::Round($_, [MidPointRounding]::AwayFromZero) }
)

【讨论】:

    【解决方案2】:

    正如您所解释的那样,铸造[int] 是可行的在大多数情况下,但它也容易出错。如果数字高于 [int]::MaxValue 怎么办?您可以用来避免异常的替代方法是使用 -as [int] 运算符,但是这样做还有另一个问题,如果值无法转换为整数,您将得到 $null 结果。

    为了安全起见,字符串将被转换并且您首先不会得到 null,您需要 100% 确保您提供的数据是正确的或假设最坏的数据并结合使用 [math]::Round(..)-as [decimal]-as [long]-as [double] () 来四舍五入:

    [math]::Round('123.123' -as [decimal]) # => 123
    [math]::Round('123.asd' -as [decimal]) # => 0
    

    注意:我使用的是圆形,但 [math]::Ceiling(..)[math]::Floor(..)[math]::Truncate(..) 也是有效的替代方案,具体取决于您的预期输出。

    另一种选择是使用[decimal]::TryParse(..),但是如果有不是数字的东西,这会抛出:

    $StringNumber = '15.0000'
    $ref = 0
    [decimal]::TryParse( $StringNumber, ([ref]$ref) )
    [math]::Round($ref) # => 15
    

    使用Hazrelle's 建议也可以,但同样会为无效输入或“值对于 Int32 而言太大或太小”引发异常。

    [System.Decimal]::ToInt32('123123123.123') # => 123123123
    

    至于奖励挑战,我认为不可能使用ForEach(type convertToType) 一次性将四舍五入的值转换为您的 CSV,即使是这样,它也可能会带来问题,因为之前提到过:

    $csv = @'
    "Col1","Col2"
    "val1","15.0000"
    "val2","20.123"
    "val3","922337203685477.5807"
    '@ | ConvertFrom-Csv
    
    $csv.Col2.ForEach([int])
    

    无法将参数“item”,值为“922337203685477.5807”,用于“Add”以键入“System.Int32”:“无法将值“922337203685477.5807”转换为“System.Int32”。

    .foreach(..) 数组方法与脚本块结合使用会起作用:

    $csv.ForEach({
        $_.Col2 = [math]::Round($_.Col2 -as [decimal])
    })
    

    如果您想知道为什么不在字符串上使用 [math]::Round(..) 并忘记它:

    [math]::Round('123.123') # => 123 Works!
    

    但是呢:

    PS /> [math]::Round([decimal]::MaxValue -as [string])
    7.92281625142643E+28
    
    PS /> [math]::Round([decimal]([decimal]::MaxValue -as [string]))
    79228162514264337593543950335
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-04-16
      • 2020-04-15
      • 2021-10-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-20
      相关资源
      最近更新 更多