Powershell - Hex to IEEE754 Single-Precision Floating-Point - powershell

I'm new in Powershell and I'm struggling with a basic conversion from hex to float.
I'd like to convert 0x46FEEBD0 to float number 32629.90625 (IEEE754 standard).
All my attempts gave me 1191111632 (or 1,191112E+09) which is the decimal representation of 0x46FEEBD0.
Is there a simple Powershell method to do so ?
Thanks

Yes, you can use the BitConverter.ToSingle() method:
# Get the byte representation (produces D0, EB, FE, 46)
$bytes = [BitConverter]::GetBytes([single]32629.90625)
$bytes | Foreach-Object { ("{0:X2}" -f $_) }
# Convert the $bytes back to a float (produces 32629.91)
[BitConverter]::ToSingle($bytes, 0)
Edit: Thanks to LotPings for pointing out that I should be using single rather than double.

Related

Count the scale of a given decimal

How can I count the scale of a given decimal in Powershell?
$a = 0.0001
$b = 0.000001
Casting $a to a string and returning $a.Length gives a result of 6...I need 4.
I thought there'd be a decimal or math function but I haven't found it and messing with a string seems inelegant.
There's probably a better mathematic way but I'd find the decimal places like this:
$a = 0.0001
$decimalPlaces = ("$a" -split '\.')[-1].TrimEnd('0').Length
Basically, split the string on the . character and get the length of the last string in the array. Wrapping $a in double-quotes implicitly calls .ToString() with an invariant culture (you could expand this as $a.ToString([CultureInfo]::InvariantCulture)), making this method to determine the number of decimal places culture-invariant.
.TrimEnd('0') is used in case $a were sourced from a string, not a proper number type, it's possible that trailing zeroes could be included that should not count as decimal places. However, if you want the scale and not just the used decimal places, leave .TrimEnd('0') off like so:
$decimalPlaces = ("$a" -split '\.')[-1].Length
mclayton helpfully linked to this answer to a related C# question in a comment, and the solution there can indeed be adapted to PowerShell, if working with or conversion to type [decimal] is acceptable:
# Define $a as a [decimal] literal (suffix 'd')
# This internally records the scale (number of decimal places) as specified.
$a = 0.0001d
# [decimal]::GetBits() allows extraction of the scale from the
# the internal representation:
[decimal]::GetBits($a)[-1] -shr 16 -band 0xFF # -> 4, the number of decimal places
The System.Decimal.GetBits method returns an array of internal bit fields whose last element contains the scale in bits 16 - 23 (8 bits, even though the max. scale allowed is 28), which is what the above extracts.
Note: A PowerShell number literal that is a fractional number without the d suffix - e.g., 0.0001 becomes a [double] instance, i.e. a double-precision binary floating-point number.
PowerShell automatically converts [double] to [decimal] values on demand, but do note that there can be rounding errors due to the differing internal representations, and that [double] can store larger numbers than [decimal] can (although not accurately).
A [decimal] literal - one with suffix d (note that C# uses suffix m) - is parsed with a scale exactly as specified, so that applying the above to 0.000d and 0.010d yields 3 in both cases; that is, the trailing zeros are meaningful.
This does not apply if you (implicitly) convert from [double] instances such as 0.000 and 0.010, for which the above yields 0 and 2, respectively.
A string-based solution:
To offer a more concise (also culture-invariant) alternative to Bender The Greatest's helpful answer:
$a = 0.0001
("$a" -replace '.+\.').Length # -> 4, the number of decimal places
Caveat: This solution relies on the default string representation of a [double] number, which need not match the original input format; for instance, .0100, when stringified later, becomes '0.01'; however, as discussed above, you can preserve trailing zeros if you start with a [decimal] literal: .0100d stringifies to '0.0100' (input number of decimals preserved).
"$a", uses an expandable string (PowerShell's string interpolation) to create a culture-invariant string representation of the number so as to ensure that the string representation uses . as the decimal mark.
In effect, PowerShell calls $a.ToString([cultureinfo]::InvariantCulture) behind the scenes.[1].
By contrast, .ToString() (argument-less) applies the rules of the current culture, and in some cultures it is , - not . - that is used as the decimal mark.
Caveat: If you use just $a as the LHS of -replace, $a is implicitly stringified, in which case you - curiously - get culture-sensitive behavior, as with .ToString() - see this GitHub issue.
-replace '.+\.' effectively removes all characters up to and including the decimal point from the input string, and .Length counts the characters in the resulting string - the number of decimal places.
[1] Note that casts from strings in PowerShell too use the invariant culture (effectively, ::Parse($value, [cultureinfo]::InvariantCulture) is called) so that in order to parse a a culture-local string representation you'll need to use the ::Parse() method explicitly; e.g., [double]::Parse('1,2'), not [double] '1,2'.

Powershell: Convert unique string to unique int

Is there a method for converting unique strings to unique integers in PowerShell?
I'm using a PowerShell function as a service bus between two API's,
the first API produces unique codes e.g. HG44X10999 (varchars)- but the second API which will consume the first as input, will only accept integers. I only care about keeping them unique.
I have looked at $string.gethashcode() but this produces negative integers and also changes between builds. Get-hash | $string -encoding ASCII obviously outputs varchars too.
Other examples on SO are referring to converting a string of numeric characters to integers i.e. $string = 123 - but I can't find a way of quickly computing an int from a string of alphanumeric
The Fowler-Noll-Vo hash function seems well-suited for your purpose, as it can produce a 32-bit hash output.
Here's a simple implementation in PowerShell (the offset basis and initial prime is taken from the wikipedia reference table for 32-bit outputs):
function Get-FNVHash {
param(
[string]$InputString
)
# Initial prime and offset chosen for 32-bit output
# See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
[uint32]$FNVPrime = 16777619
[uint32]$offset = 2166136261
# Convert string to byte array, may want to change based on input collation
$bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString)
# Copy offset as initial hash value
[uint32]$hash = $offset
foreach($octet in $bytes)
{
# Apply XOR, multiply by prime and mod with max output size
$hash = $hash -bxor $octet
$hash = $hash * $FNVPrime % [System.Math]::Pow(2,32)
}
return $hash
}
Now you can repeatably produce distinct integers from the input strings:
PS C:\> Get-FNVHash HG44X10999
1174154724
If the target API only accepts positive signed 32-bit integers you can change the modulus to [System.Math]::Pow(2,31) (doubling the chance of collisions, to
approx. 1 in 4300 for 1000 distinct inputs)
For further insight into this simple approach, see this page on FNV and have a look at this article exploring short string hashing

How can I convert hexadecimal fraction to decimal in PowerShell?

Is there a way to convert hexadecimal fractions (i.e.: numbers with values <1.0, represented in hex) into their decimal equivalents in PowerShell?
Examples:
Hex Dec
0.0858b9da24fb4cac = 0.032603851087498366
0.8c3115559ab0c10b = 0.5476239522928976
Guides I've found for general HEX/DEC conversion say to use [Convert], which is fine for whole numbers, but it seems to completely fail when I throw a decimal point in the string.
Note: Examples were taken from data on another site, where these calculations are routinely done. I haven't fully verified their accuracy - there may be some error in the last few digits. Fortunately, I won't be using that much precision anyway.
I don't know of a builtin .Net way to do it. I wrote this brute-force converter, but your values don't seem to exactly match up with .Net type precision - [double] (64bit) is too low precision, and [decimal] (128bit) is more.
Your examples come out like so:
0.0858b9da24fb4cac = 0.032603851087498366
= 0.0326038510874983682928164128
0.8c3115559ab0c10b = 0.5476239522928976
= 0.5476239522928976344718082711
Code:
<#
.Synopsis
Converts strings containing hexadecimal numbers with fractional components
into base-10 [decimal] numbers.
.EXAMPLE
PS C:\> ConvertFrom-HexFraction '0.0858b9da24fb4cac'
0.0326038510874983682928164128
.Example
PS C:\> '0.0858b9da24fb4cac', '0.8c3115559ab0c10b' | ConvertFrom-HexFraction
0.0326038510874983682928164128
0.5476239522928976344718082711
#>
function ConvertFrom-HexFraction
{
[CmdletBinding()]
[OutputType([decimal])]
Param
(
[Parameter(Mandatory=$true,
ValueFromPipeline=$true)]
[string]$hexValue
)
Process
{
# strip leading hex indicator, setup result placeholder of desired type.
$hexValue = $hexValue -replace '^(0x|&h)'
$result = [decimal]0
# Loop over the digits (hexits?) in the string, skip the (hexa)decimal point '.'
# and calculate (value * base ^ columnNum) for each.
$i = $hexValue.IndexOf('.') - 1
[char[]]$hexValue | Where { $_ -ne '.' } | ForEach {
$result += [convert]::ToInt32($_, 16) * [Math]::Pow(16, $i--)
}
$result #output the result
}
}
ConvertFrom-HexFraction '0.0'
ConvertFrom-HexFraction 'a.a'
ConvertFrom-HexFraction '0.0858b9da24fb4cac'
ConvertFrom-HexFraction '0.8c3115559ab0c10b'
'0.0858b9da24fb4cac', '0.8c3115559ab0c10b' | ConvertFrom-HexFraction
Ok, this one caught my interest. I didn't even know that hexadecimal fractions were a thing, so I went and looked it up. Converting them isn't covered in the [math] or [convert] libraries that I can see, so we'll have to write up a function that takes care of it for us. I wrote this up, and it's relatively accurate, but you have to consider that a fraction as small as you have it there's going to be some rounding... I mean really, for your first example even you rounded. The last digit alone is something like 6.50521303491303E-19 (which is 12*(16^-16)).
So, that function looks like:
Function HexToInt([string]$TextIn){
$Pre,$Post = $TextIn.split('.')
$PostConverted = For($i=1;$i -le $Post.Length;$i++){
[convert]::ToInt32($Post[($i-1)],16) * [math]::pow(16,($i*-1))
}
$PostSum = $PostConverted | Measure-Object -Sum |% Sum
$Int = [convert]::ToInt64($Pre,16)
$Int+$PostSum
}
That should convert your fractions for you.

Write REG_MULTI_SZ with Hex data

I am trying to write to a multi string, but using data gleaned from a REG file, so it's in Hex format. I have managed to convert the string to a byte array using the Convert-HexStringToByteArray here, but that doesn't produce the same result in the registry as loading the REG, so I am thinking that is not actually the right data type to be casting to.
The initial data looks like this
"NavigatorLayoutOrder"=hex(7):31,00,30,00,00,00,31,00,00,00,32,00,00,00,33,00,00,00,30,00,00,00,34,00,00,00,35,00,00,00,36,00,00,00,37,00,00,00,38,00,00,00,39,00,00,00,31,00,31,00,00,00,31,00,32,00,00,00,31,00,33,00,00,00,31,00,34,00,00,00,31,00,35,00,00,00,31,00,36,00,00,00,31,00,37,00,00,00,31,00,38,00,00,00,31,00,39,00,00,00,32,00,30,00,00,00,32,00,31,00,00,00,32,00,32,00,00,00,00,00
and I have removed the hex(7): off the front, then tried it as a pure string and casting to a byte array, and neither seems to work.
I have found reference to REG_MULTI_SZ being UTF-16le, but my understanding is that this is also the default for PowerShell, so I shouldn't need to be changing the encoding, but perhaps I am wrong there?
EDIT: I also tried this, with again a successful write, but the wrong result.
$enc = [system.Text.Encoding]::UTF8
[byte[]]$bytes = $enc.GetBytes($string)
Also tried
$array = $string.Split(',')
$byte = [byte[]]$array
This also puts data into the registry, but the result is not the same as importing the REG. And, everything I am finding keeps pointing at the idea that the REG file is UTF16, so I tried
$enc = [system.Text.Encoding]::Unicode
[byte[]]$bytes = $enc.GetBytes($string)
both with BigEndianUnicode & Unicode. Not only did it not work, the result is the same which I find odd. Seems like changing the endian-ness SHOULD change the result.
EDIT: To clarify, the input string as taken from the REG file is shown above. I simply removed the hex(7): from the front of the data.
The results are seen here, where the second value is what results from PowerShell, while the first is what the REG file produced.
The code used to produce this was
$string = "31,00,30,00,00,00,31,00,00,00,32,00,00,00,33,00,00,00,30,00,00,00,34,00,00,00,35,00,00,00,36,00,00,00,37,00,00,00,38,00,00,00,39,00,00,00,31,00,31,00,00,00,31,00,32,00,00,00,31,00,33,00,00,00,31,00,34,00,00,00,31,00,35,00,00,00,31,00,36,00,00,00,31,00,37,00,00,00,31,00,38,00,00,00,31,00,39,00,00,00,32,00,30,00,00,00,32,00,31,00,00,00,32,00,32,00,00,00,00,00"
$enc = [system.Text.Encoding]::BigEndianUnicode
[byte[]]$bytes = $enc.GetBytes($string)
New-ItemProperty "HKCU:\Software\Synchro\Synchro\ProjectConfig" -name:"NavigatorLayoutOrder2" -value:$bytes -propertyType:MultiString -force
Using Unicode encoding produces a very slightly different, but still wrong, result.
For one thing, the multistring is little endian encoded, so you need [Text.Encoding]::Unicode, not [Text.Encoding]::BigEndianUnicode. Plus, using [Text.Encoding]::Unicode.GetBytes() on the string from the .reg file ("31,00,30,00,...") would give you a byte array of the characters of that string:
'3' → 51, 0
'1' → 49, 0
',' → 44, 0
'0' → 48, 0
…
What you actually want is a byte array of the comma-separated hexadecimal values in that string:
31 → 49 (character '1')
00 → 0 (character NUL)
30 → 48 (character '0')
…
Split the string at commas, convert the hexadecimal number strings to integers, and cast the resulting list of integers to a byte array:
[byte[]]$bytes = $string -split ',' | ForEach-Object { [int]"0x$_" }
Then you can convert that (little endian encoded) byte array to a string:
$ms = [Text.Encoding]::Unicode.GetString($bytes)
and write that to the registry:
$key = 'HKCU:\Software\Synchro\Synchro\ProjectConfig'
$name = 'NavigatorLayoutOrder2'
New-ItemProperty $key -Name $name -Value $ms -PropertyType MultiString -Force

Powershell - Round down to nearest whole number

What's the best way to round down to nearest whole number in PowerShell?
I am trying [math]::truncate but its not giving me predictable results.
Example:
$bla = 17.2/0.1
[math]::truncate($bla)
outputs 171 instead of the expected 172!
$bla = 172
[math]::truncate($bla)
outputs 172
I just need something that works.... and must always round down (i.e round($myNum + 0.5) won't work due to baker's rounding which may round up if the number has a 0.5 component).
Ah, I see. Looks like the datatype needs to be decimal:
[decimal] $de = 17.2/.1
[double] $db = 17.2/.1
[math]::floor($de)
172
[math]::floor($db)
171
http://msdn.microsoft.com/en-us/library/system.math.floor(v=vs.85).aspx
The Math::Floor function combined with [decimal] declaration should give you the results you want.
[Math]::Floor([decimal](17.27975/0.1))
returns = 172
The issue you are encountering with the original 17.2/0.1 division example is due to inaccuracy in the floating-point representation of the given decimal values (as mentioned in Joey's comment on another answer). You can see this in PowerShell by examining the round-trip representation of the final value:
PS> $bla = 17.2/0.1
PS> $bla.GetType().FullName
System.Double
PS> $bla.ToString()
172
PS> $bla.ToString('r')
171.99999999999997
A simple way to get around this is to declare the result as int, as PowerShell will automatically round to the the result to the nearest integer value:
PS> [int]$bli = 17.2/0.1
PS> $bli.GetType().FullName
System.Int32
PS> $bli.ToString()
172
Note that this uses the default .NET method of MidpointRounding.ToEven (also known as banker's rounding). This has nice statistical properties when tabulating large numbers of numeric values, but can also be changed to the simpler away-from-zero method:
function round( $value, [MidpointRounding]$mode = 'AwayFromZero' ) {
[Math]::Round( $value, $mode )
}
PS> [int]3.5
4
PS> [int]4.5
4
PS> round 3.5
4
PS> round 4.5
5
Another option is to use a more accurate representation for the original values, which will avoid the issue entirely:
PS> $bld = [decimal]17.2/0.1
PS> $bld.GetType().FullName
System.Decimal
PS> $bld.ToString()
172
[Math]::floor($x) is the built-in way to do it.
Just be aware of how it will behave with negative numbers. [Math]::floor(5.5) returns 5, but [Math]::floor(-5.5) returns -6.
If you need the function to return the value closest to zero, you'll need:
If ($x -ge 0) {
[Math]::Floor($x)
} Else {
[Math]::Ceiling($x)
}