ТЛ; др
Если вам нужно контролировать определенный числовой тип данных , используемый для суммирования чисел :
Избегайте Measure-Object
, который неизменно использует [double]
вычисления.
Вместо этого используйте метод LINQ Sum
(доступный в PSv3 +) с cast для нужного числового типа
[Linq.Enumerable]::Sum(
[decimal[]] @(
$Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
).Points
)
Полезный ответ Матиаса Р. Джессена показывает вам элегантный способ суммировать ваш столбец Points
, сгруппированный по строкам, которые имеют один и тот же адрес электронной почты, и Полезный ответ Тео улучшает его путем истинно суммируя баллы как [decimal]
значения.
Некоторые общие замечания о Measure-Object
с -Sum
и типами данных с плавающей точкой :
Вы правильно заявляете:
Свойство [тип данных] изменилось на double
[...] я обнаружил, что свойство Powershell GenericMeasureInfo.Sum
может возвращать только Nullable<Double>
в качестве значения свойства.
Действительно: Measure-Object -Sum
:
- неизменно использует
[double]
значения для суммирования входных данных.
- it принудительно вводит значения в
[double]
s, если это возможно - даже если они не числа.
- Если вход не может быть приведен к
[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
[...] Кажется, у меня переполнение "double"
Переполнение будет означать превышение максимального значения, которое может быть сохранено в [double]
, что является (а) маловероятным ([double]::MaxValue
равно 1.79769313486232E+308
, т. Е. Больше 10 в степени 308) и (б) будет производить другой симптом; e.g.:
PS> ([double]::MaxValue, [double]::MaxValue | Measure-Object -Sum).Sum
∞ # represents positive infinity
Что вы делаете получаете, однако, это округление ошибок из-за внутреннего двоичного представления *1118* типа *, которое не не всегда имеет точное десятичное представление, что может привести к сбивающим с толку результатам вычислений; e.g.:
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]
с, вы должны избегать 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()
и сообщить об ошибке "Обнаружено несколько неоднозначных перегрузок" для "суммы" и количества аргументов: 1 ".
Приведенная выше команда неизменно требует, чтобы все соответствующие строки CSV (представленные в виде пользовательских объектов) помещались в память в целом , тогда как Measure-Object
, как и большинство командлетов в конвейере PowerShell, обрабатывает их один за другим , что требует только постоянного объема памяти (но медленнее).
Если загрузка всех совпадающих строк в память не возможна, используйте командлет ForEach-Object
(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 }