我陷入以下情况:
我必须从CSV文件中获取信息。我使用Import-Csv
导入了CSV。
我的原始数据如下所示:
45227;01.10.2018 03:24:00;Xxxx Xxxx Xxxxx x XX xxxxxxxxxxxxxx Xxxxx xxx Xxxxxxxxxxxxxxxxxxx;;3;XXXX;XXXX;XXX@XX.com;;;3.7;;
其中包含
3.7
的列是关注值(“点”)。这是我的第一个问题->使用
Import-Csv
,powershell会将这些信息保存在[string]
属性中。为了避免这种情况,我使用了以下行:| Select @{Name="Points";Expression={[decimal]$_.Points}}
现在,我得到一个
Selected.System.Management.Automation.PSCustomObject
类型的对象,其中包含该属性作为[decimal]
。现在,我想总结由同一电子邮件地址使用的所有要点:$Data[$Index].Points += (
$Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender} |
measure Points -sum
).Sum
这似乎工作得很好,但是如果我打开
$Data[$Index] | gm
,我会得到:Points NoteProperty double Points=71301.6000000006
该属性已更改为
[double]
。我挖了一点,发现Powershell的GenericMeasureInfo.Sum
属性只能返回Nullable<Double>
实例作为属性值。似乎我正在产生
[double]
溢出,因为显示的数字是完全错误的。我想坚持十进制或整数,所以我有像71123.4
或类似的输出。还有其他方法,所以我不必使用
(Measure-Object -sum).Sum
?提前致谢!
最佳答案
tl;博士:
如果需要控制特定的数字数据类型用于对数字求和:
Measure-Object
,后者始终使用[double]
计算。 Sum
method (可在PSv3 +中访问),并将强制转换为所需的数字类型:[Linq.Enumerable]::Sum(
[decimal[]] @(
$Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
).Points
)
Mathias R. Jessen's有用的答案向您展示了一种优雅的方式,可以将
Points
列按共享相同电子邮件地址的行进行分组,而Theo's helpful answer通过将这些点真正地求和为[decimal]
值来进行改进。有关
Measure-Object
和-Sum
以及浮点数据类型的一些一般要点:您正确声明:
The property [data type] changed to
double
[...] i found out that Powershell'sGenericMeasureInfo.Sum
property can only give back aNullable<Double>
as property value.
确实:
Measure-Object -Sum
:[double]
值来汇总输入。 [double]
,即使可能不是数字,也可以。[double]
(例如'foo'
),则会发出非终止错误,但将对所有剩余输入继续求和。 上面的内容意味着甚至是字符串都是
Measure-Object -Sum
的可接受输入,因为它们在求和期间会按需转换为[double]
。这意味着您可以直接使用
Import-Csv
命令,如以下示例所示(该示例使用两个[pscustomobject]
实例来模拟Import-Csv
的输出):PS> ([pscustomobject] @{ Points = '3.7' }, [pscustomobject] @{ Points = '1.2' } |
Measure-Object Points -Sum).Sum
4.9 # .Points property values were summed correctly.
71301.6000000006
[...] It seems like i'm producing an overflow of "double"
溢出将意味着超过可以存储在
[double]
中的最大值,这是(a)不太可能的([double]::MaxValue
是1.79769313486232E+308
,即大于308的幂的10),并且(b)会产生不同的症状;例如。:PS> ([double]::MaxValue, [double]::MaxValue | Measure-Object -Sum).Sum
∞ # represents positive infinity
但是,您得到的是四舍五入 错误,这是因为
[double]
类型的内部二进制表示形式,而该二进制表示形式并不总是具有精确的十进制表示形式,这会导致计算结果令人困惑。例如。:PS> 1.3 - 1.1 -eq 0.2
False # !! With [double]s, 1.3 - 1.1 is NOT exactly equal to 0.2
有关更多信息,请参见https://floating-point-gui.de/。
使用
[decimal]
值确实解决了这个问题,但是请注意,这是以较小范围为代价的(实际上,您获得28个十进制数字的精度-最大数字的绝对值取决于小数点的位置) ;作为整数,它是79,228,162,514,264,337,593,543,950,335
,即接近8 * 1028)。如果确实需要
[decimal]
的精度,则必须避免Measure-Object
并自己对求和。在原始命令的上下文中,可以使用
Sum
LINQ方法:[Linq.Enumerable]::Sum(
[decimal[]] @(
$Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
).Points
)
@(...)
(数组子表达式运算符),而不只是(...)
,可确保在管道不返回任何行的情况下,总体命令不会失败。 @(...)
将非输出转换为一个空数组,为此.Sum()
正确返回0
。[decimal[]]
强制转换将导致$null
,而PowerShell将无法找到[decimal[]]
方法的.Sum()
类型的重载并报告错误,“为“Sum”找到多个模棱两可的重载,并且参数计数为:1 ”。 Measure-Object
(作为PowerShell管道中的大多数cmdlet)将一个接一个地处理它们,这仅需要恒定数量的内存(但较慢)。 如果无法将所有匹配的行一次加载到内存中,请使用
ForEach-Object
(foreach
)cmdlet,但是请注意,只有将实际的Import-Csv
调用替换为内存数组$Imported_Csv
时,这才有意义:# Replace $Imported_Csv with the original Import-Csv call to
# get memory-friendly one-by-one processing.
$Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender} |
foreach -Begin { [decimal] $sum = 0 } -Process { $sum += $_.Points } -End { $sum }
关于powershell - (Measure-Object -sum).Sum的替代,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55205714/