tl;博士:
如果您需要控制用于汇总数字的特定数字数据类型:
[Linq.Enumerable]::Sum(
[decimal[]] @(
$Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
).Points
)
Mathias R. Jessen 的有用答案向您展示了一种优雅的方式来对Points
按共享相同电子邮件地址的行分组的列进行求和,而Theo 的有用答案通过真正将点求和作为[decimal]
值来改进它。
关于with和浮点数据类型的一些一般要点Measure-Object
-Sum
:
您正确地说:
属性 [数据类型] 更改为double
[...] 我发现 Powershell 的GenericMeasureInfo.Sum
属性只能返回一个Nullable<Double>
as 属性值。
确实Measure-Object -Sum
:
- 总是使用
[double]
值来总结输入。
- 如果可能的话,它会强制s的输入
[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
[...]似乎我正在产生“双”溢出
溢出意味着超过可以存储在 a 中的最大值[double]
,这是(a)不太可能([double]::MaxValue
即1.79769313486232E+308
,大于 10 的 308 次方)和(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 * 10 28 )。
如果您确实需要[decimal]
s 的精度,则必须避免Measure-Object
并自己进行 summing。
在原始命令的上下文中,您可以使用Sum
LINQ 方法:
[Linq.Enumerable]::Sum(
[decimal[]] @(
$Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
).Points
)
使用@(...)
(数组子表达式运算符)而不是仅仅围绕管道命令可确保在管道碰巧不返回任何行(...)
的情况下整个命令不会失败。 将非输出转换为空数组,并正确返回。@(...)
.Sum()
0
- 没有它,强制转换
[decimal[]]
将导致$null
,并且 PowerShell 将无法找到该方法的[decimal[]]
-typed 重载.Sum()
并报告错误“为“Sum”和参数计数找到多个不明确的重载:1”。
上述命令总是需要将所有匹配的 CSV 行(表示为自定义对象)作为一个整体放入内存中,而Measure-Object
- 就像 PowerShell 管道中的大多数 cmdlet - 将一个一个地处理它们,这仅需要恒定数量的内存(但速度较慢)。
如果不能一次将所有匹配的行加载到内存中,请使用ForEach-Object
( ) cmdlet,但请注意,这只有在您将实际调用替换为已在内存中的数组foreach
时才有意义: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 }