4

I am tryng to compare the following data to obtain the largest number:

$UserDeets 

name                 lastLogon
----                 ---------
Frank Ti 132273694413991065
Frank Ti 132279742884182029
Frank Ti 132282196073500496
Frank Ti 132272912975826719
Frank Ti 132282144707771693
Frank Ti 132228790551703041

To do this I am trying to use the built in 'measure' function. This is the code I am executing

($UserDeets| measure -Property lastLogon -Maximum ).Maximum

The results of this are as follows

1.322821960735E+17

As you can see althogh it is returning the correct data it is truncating the last few digits off.

Is there a way to prevent this truncation?

mklement0
  • 382,024
  • 64
  • 607
  • 775
FrankU32
  • 311
  • 1
  • 3
  • 18
  • 2
    Interesting -- it appears that `Measure-Object` will produce a `Double` even if its input is `Int64`. This is more than a mere formatting problem, since a `Double` can't hold every value of an `Int64` precisely. Try `[System.Linq.Enumerable]::Max([long[]]($userDeets.lastLogon))` (relying on LINQ rather than M-O). – Jeroen Mostert Mar 09 '20 at 10:39
  • Thanks for the response. It's good to know the actual reason for this not working. I did find a work around. – FrankU32 Mar 09 '20 at 10:47
  • 2
    You can convert your datetime and then get latest by sorting `@{Name="lastLogon";Expression={[datetime]::FromFileTime($_.'lastLogon')}}` – Vad Mar 09 '20 at 10:58

4 Answers4

1

Jeroen Mostert provided the crucial pointers in a comment on the question:

Unfortunately, as of PowerShell 7.0, Measure-Object invariably converts input numbers[1] for its -Sum, -Maximum, -Minimum, -Average and -StandardDeviation operations to [double] (System.Double) and reports the result as that type, which can result in loss of precision.

Your input numbers are of type [long], and their values exceed the highest integer that can precisely be represented in a [double], which is 9007199254740991 (you can calculate it with [bigint]::pow(2, 53) - 1)

An efficient workaround is to use LINQ (System.Linq.Enumerable.Max):

[Linq.Enumerable]::Max(
  [long[]] $UserDeets.lastLogon
)

Note that the explicit [long[]] cast is needed in order for PowerShell to be able to call the generic .Max() method with a concrete type.

Another, less efficient, but more PowerShell-idiomatic workaround is to use sorting, similar to the OP's own answer:

# Sort the LastLogon property values, then grab the *last* one,
# which is the largest value.
($UserDeets.LastLogon | Sort-Object)[-1]

Sorting just the array of .lastLogon values rather than the full input objects minimizes the conceptually unnecessary overhead of creating a duplicate, sorted array just so the max. value can be determined.


[1] Note that for -Min and -Max non-numeric inputs are accepted too, as long as they implement the System.IComparable interface, in which case the inputs are preserved as-is and no loss of precision occurs; e.g., 'c', 'b', 'a' | Measure-Object -Minimum and [datetime]::now, [datetime]::now.AddDays(1) | Measure-Object -Maximum work fine, because types [string] and [datetime] both implement IComparable.

mklement0
  • 382,024
  • 64
  • 607
  • 775
0

OK I have a solution to this. The answer is to not use 'measure'. This is a work around, but it gets the desired answer.

First i sorted the array:

$UserDeets = ($UserDeets | Sort-Object -Property LastLogon)

the highest object will be at the end of the array and can be obtained like this:

$UserDeets[-1]
FrankU32
  • 311
  • 1
  • 3
  • 18
0

i think you try get latest logon for users by checking multiple DC, i do this by this code(where:$samacc - current user, $controller -lists of all DC hostnames.):

$scriptblock={
    param($samacc,$controller)
    $result=@()
    foreach($cont in $controller){
    $RESULT=$result + (Get-ADUser -Server $cont -Identity $samacc -Properties lastlogon,whenchanged,displayname,title,company  | sort-object lastLogon -descending | select-object enabled,displayname,samaccountname,title,company, @{Name="lastLogon";Expression={[datetime]::FromFileTime($_.'lastLogon')}},whenchanged)
    }
    $result|Sort-Object -Descending -Property LastLogon|select -First 1
    }
Vad
  • 693
  • 3
  • 13
-1

This worked for me. Turn it back from 64-bit floating point to 64-bit integer. But some numbers will be slightly off (.000000000000005 or 5/1 quadrillion % error), or 6 ten millionths of a second (ticks) if it were a datetime.

$jsontext = @'
[ { name: 'Frank Ti', lastLogon: 132273694413991065 },
  { name: 'Frank Ti', lastLogon: 132279742884182029 },
  { name: 'Frank Ti', lastLogon: 132282196073500496 },
  { name: 'Frank Ti', lastLogon: 132272912975826719 },
  { name: 'Frank Ti', lastLogon: 132282144707771693 },
  { name: 'Frank Ti', lastLogon: 132228790551703041 },
  { name: 'Frank Ti', lastLogon: 132282196073500499 },
  { name: 'Frank Ti', lastLogon:   9007199254740991 } ]
'@

$userdeets = $jsontext | convertfrom-json
[long]($userdeets | measure lastlogon -Maximum).maximum

132282196073500496
js2010
  • 23,033
  • 6
  • 64
  • 66
  • 2
    It works for this particular value, but that's getting lucky. Had the maximum value been `132282196073500497`, `Measure-Object` would not be able to produce it since that value can't be represented with sufficient precision in a `double`. – Jeroen Mostert Mar 09 '20 at 13:15
  • @JeroenMostert You're right. I thought if it was long before the conversion powershell might keep the precision. It stays at 132282196073500496. – js2010 Mar 09 '20 at 15:38
  • @JeroenMostert How did you get that number? – js2010 Mar 09 '20 at 16:10
  • 1
    I took 132282196073500496 from the question and added 1. :-) No magic involved; the fact that small differences will fail to be represented correctly is a simple consequence of how floating-point works. That 132282196073500496 is exactly representable I did not know, but is a happy coincidence (132272912975826719 is not, for example). The next number that can be represented exactly is 132282196073500512. The difference between these is 16, which is of course not coincidentally a power of 2. – Jeroen Mostert Mar 09 '20 at 16:18
  • @JeroenMostert Oh I see, I thought it was a max number. – js2010 Mar 09 '20 at 16:31
  • The max. safe integer you can represent in a `[double]` is `9007199254740991`, which you can calculate with `[bigint]::pow(2, 53) - 1`. As @Jeroen points out, your solution will result in a loss of precision. – mklement0 Mar 09 '20 at 22:14