BigInt inconsistencies in PowerShell and C#
TLDR: Use [BigInt]::Parse
or 'literal'
syntax prior to Powershell Core 7.0; otherwise use the n
suffix.
The Problem - double
literals
When it comes to un-suffixed literals, Powershell will use the first type the value fits in. The order for integral literals is int
, long
, decimal
and then double
. From the documentation for Powershell 5.1 (bolding mine; this paragraph is the same for Powershell Core):
For an integer literal with no type suffix:
- If the value can be represented by type
[int]
, that is its type.- Otherwise, if the value can be represented by type
[long]
, that is its type.- Otherwise, if the value can be represented by type
[decimal]
, that is its type.- Otherwise, it is represented by type
[double]
.
In your case the value exceeds that of decimal.MaxValue
so your literal is by default a double
literal. That double
value is not exactly representable and is "converted" to the closest representable double.
$h = [double]99999999999999999999999999999
"{0:G29}" -f $h
Outputs
99999999999999991000000000000
Obviously that's not the exact number, just a representation in string form. But it gives you an idea what's going on. Now we take this inexact double
value and we cast it to BigInt
. The original loss in precision is transferred over and compounded upon by the conversion operator. This is what is actually happening in Powershell (note the cast to BigInt
):
$h = [BigInt][double]99999999999999999999999999999
"{0:G}" -f $h
Outputs
99999999999999991433150857216
This is in fact the closest representable double
value. If you could print the exact value of the double
from the first example, this is what it would print. When you add the additional extra digits, you exceed the largest value of a numeric literal, thus the other exception you received.
C# Inconsistencies
Unlike Powershell, C# uses integral literals by default which is why you get the exception for a lot fewer digits. Adding the D
suffix in C# will give you a larger range. The following works fine and will be a double
.
var h = 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999D;
Adding one more digit will raise the following error:
error CS0594: Floating-point constant is outside the range of type 'double'
Note that in Powershell the D
suffix is used for decimal
literals and not double
. There is not an explicit suffix for double
--it is assumed to be the default.
Solutions
Back to your original problem, depending on your Powershell version the solution may vary:
[BigInt]::Parse
If you are using Windows Powershell or Powershell Core <= v6.2, one option is to use BigInteger.Parse
:
[bigint]::Parse("99999999999999999999999999999")
Outputs:
99999999999999999999999999999
Large Value Literals
As pointed out in the comments, another option that works is to enclose the literal in quotes.
[bigint]'99999999999999999999999999999'
Outputs
99999999999999999999999999999
Despite how it looks, this is not shorthand for [bigint]::new([string])
(see below). This is instead a way to ensure that the literal is not treated as a double
but rather as an integral literal with many digits, a so-called "large value literal". See this section of the docs.
N
Integral Suffix (v7.0+)
Powershell Core 6.2 introduced many new literal suffixes for integral types such as unsigned, short
, and byte
but did not introduce one for bigint
. That came along in Powershell Core 7.0 via the n
suffix. This means you can now do the following:
99999999999999999999999999999n
Outputs:
99999999999999999999999999999
See the documentation for more information on the suffixes available in Powershell Core.
[BigInt]::new
If you were to try [bigint]::new('literal')
Powershell recognizes that you intend to use the value as a literal. There is in fact no constructor for BigInt
that accepts a string
(we use Parse
for that) nor is there a constructor which accepts another BigInt
. There is however a constructor that takes a double
. Our large-value literal will start as a BigInt
, Powershell will then implicitly convert that to a double
(losing precision) and then pass it to [bigint]::new([double])
as the best match, once again giving an incorrect result:
[bigint]::new('99999999999999999999999999999')
Outputs:
99999999999999991433150857216
Unfortunately C# have not literal for BigInteger. There are two ways to instantiate BigInteger:
- Convert from primitive type like int, long (cast or using constructor)
- Parse from string using BigInteger.Parse
BigInteger test = BigInteger.Parse("32439845934875938475398457938457389475983475893475389457839475");
Console.WriteLine(test.ToString());
// output: 32439845934875938475398457938457389475983475893475389457839475
See How PowerShell parses numeric literals
To complement the existing, helpful answers - notably pinkfloydx33's - with a succinct summary:
By default, all PowerShell versions up to at least v7.0 use [double]
as the data type for number literals that are larger than [decimal]::MaxValue
, which invariably leads to a loss of accuracy.
- Up to v6.x (which includes Windows PowerShell), the only way to avoid this is to use
[bigint]::Parse()
with the number represented as a string:
[bigint]::Parse('99999999999999999999999999999')
# A *cast* works too, as also shown in pinkfloydx33's answer:
[bigint] '99999999999999999999999999999'
- In v7+, you can use the
n
suffix to designate a number literal as a[bigint]
:
99999999999999999999999999999n # v7+; parses as a [bigint], due to suffix 'n'
Note: Arguably, given that PowerShell usually automatically picks an appropriate number type, it should assume n
in this case, i.e. it should parse unsuffixed 99999999999999999999999999999
as a [bigint]
, not as a [double]
- see this GitHub proposal.
Further reading:
See about_Numeric_Literals, which shows all number-type suffixes, including what PowerShell version they were introduced in.
This answer summarizes how number literals are interpreted in PowerShell.
- In short: For integer literals,
[int]
is the smallest type ever chosen, with[long]
or[decimal]
chosen as needed to accommodate larger values, with[double]
used for values beyond[decimal]::MaxValue
.
- In short: For integer literals,