Add-Type -TypeDefinition @"
using System;
namespace DemoCode
{
public class DemoClass
{
public static void Test()
{
int i = 5;
System.Numerics.BigInteger bi = 5;
double d = 1.5;
double r = i * d;
Console.WriteLine("bigint c#: " + r.ToString());
}
}
}
"@
[DemoCode.DemoClass]::Test()
prints
bigint c#: 7.5
Why is the multiplication directly in PS truncating the double?
It doesn't matter if I use 1.1, 1.5 or 1.9, the double gets truncated and then multiplied.
This bug happens in PowerShell 74.1 and 5
Expected behavior
PS> [bigint]5 * [double]1.57.5
Actual behavior
PS> [bigint]5 * [double]1.55
Error details
There was no error
Environment data
PS>$PSVersionTable
Name Value
---------
PSVersion 7.4.1
PSEdition Core
GitCommitId 7.4.1
OS Microsoft Windows 10.0.22631
Platform Win32NT
PSCompatibleVersions {1.0,2.0,3.0,4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
While performing arithmetic operations with other integer types in combination with a [double] (or the rarely used [float]) yield a [double]-typed result, with [bigint], specifically, the respective other operand is forced to [bigint] too, and the result is also a [bigint].
In other words:
[bigint] 5* [double] 1.5
is currently - unexpectedly - the same as:
[bigint] 5* [bigint] [double] 1.5
Since [bigint] 1.5 (same as: [bigint] [double] 1.5, given that a number literal with a decimal point is implicitly a [double]) is 1, the result is 5n (aka [bigint] 5)
Fundamentally, C# plays by different rules than PowerShell, so differing behavior isn't in and of itself relevant.
For that reason, my comment focused on how the observed behavior is inconsistent in the realm of PowerShell - and that's what the conversation should focus on.
As an - inconsequential - aside: C# prevents mixing System.Numerics.BigInteger and System.Double in arithmetic expressions, so that, for instance, double r = (System.Numerics.BigInteger) 1 * 1.2; fails.
I haven't looked at any technical challenges, but ideally the - PowerShell-idiomatic - behavior would be:
Automatically convert to [double], because that's what happens with all other integer types ([sbyte], [byte], [int16], [uint16], [int], [uint], [long], [uint64]).
Result ResultType
------ ----------
7.500 System.Double
But currently is:
Result ResultType
------ ----------
5 System.Numerics.BigInteger
However, the open-ended nature of [bigint] theoretically makes it possibly for a given instance not to fit into a [double] - something that cannot happen with the other integer types.
In that case, it would make sense to exhibit the same behavior as when trying too cast a too-large / too-small [bigint] to a [double], which means: [double]::PositiveInfinity / [double]::NegativeInfinity
# Try to cast a [bigint] that is too large to fit into [double] to a [double]
[double] (Invoke-Expression ('9'*1e3+'n'))
# Ditto for a negative number
[double] (iex ('-'+ ('9'*1e3+'n')))
The Engine WG reviewed this and we've decided this is by design. If bigint is specified, it's expected that very large numbers will be involved. If you need decimal places, we'd recommend converting the bigint to double before the operation.
How about a warning when using Set-StrictMode -Version Latest
At least you would know about this. I can accept that it's not working.
But failing without error or warning is not good.
Tragen, speaking in no official capacity whatsoever:
I disagree with the WG decision: coercion to [double] - even if it situationally results in [double]::PositiveInfinity / [double]::NegativeInfinity - amounts to behavior that is consistent with arithmetic with other types; receiving either of the two results allows you to infer what happened. Carving out an exception for [bigint] is a confusing exception that is likely to trip up users.
Set-StrictMode is problematic in general, given PowerShell's dynamic scoping: it affects not just the current scope, but also all descendent scopes, and therefore requires careful managing; note that Set-StrictMode hasn't seen updates since -Version 3.
Given the WG decision:
The behavior should be documented: however, going by past behavior, this likely won't happen until a (non-team) user takes the initiative to ask that the Type conversion to accommodate result part of the docs be amended.
The best you can hope for is implementation of a PSSA rule, so that, for instance you'd get a design-time warning in Visual Studio Code - again, this will likely require user initiative.
I think also the decision is wrong.
You force all users to start searching why it isn't working until you find the documentation or the this bug.
It's unnecessary at makes more work.
I disagree with the WG decision: coercion to [double] - even if it situationally results in [double]::PositiveInfinity / [double]::NegativeInfinity - amounts to behavior that is consistent with arithmetic with other types; receiving either of the two results allows you to infer what happened. Carving out an exception for [bigint] is a confusing exception that is likely to trip up users.
I would be more worried about loss of precision than hitting an actual limit. Neither choice is wrong here, we just think it's more likely to need precision if you've already specified a bigint. Also helps that it's the existing behavior at least as far back as 5.1 so no breaks.
I think also the decision is wrong. You force all users to start searching why it isn't working until you find the documentation or the this bug. It's unnecessary at makes more work.
Keep in mind that this would also apply if we changed it and someone was instead expecting precision. Short of becoming a strict language like C# where it would instead simply fail to compile, that isn't really avoidable.
That's the reason why I wanted a strict mode with more warnings.
For me I like it when it fails fast because it's wrong.
And this is clearly wrong and it's also wrong because it works differently in C#.
I would think that PowerShell is .Net and behaves the same way like C# so different behavior is just wrong for me imho.
And this is clearly wrong and it's also wrong because it works differently in C#. I would think that PowerShell is .Net and behaves the same way like C# so different behavior is just wrong for me imho.
In C# it just fails to compile, the example you had was double * int. See this SharpLab.io example.
Tragen, it was never PowerShell's goal to be C#-compatible, and it intentionally deviates from C# in several ways, notably with respect to PowerShell's very flexible implicit type conversions:
Typically, these conversions are helpful and work as one would intuitively expect - pitfalls notwithstanding - and are a better fit for a "looser" shell scripting language vs. a compiled language such as C#.
In the context of PowerShell's existing behavior with integer type operands - coercion to [double] when an operand of that type is involved - I would have expected [bigint] to be treated the same way: it would make for consistent behavior in a PowerShell context.
As Patrick Meinecke (@SeeminglyScience) points out, the behavior of unexpectedly forcing [double] operands to [bigint] - an inconsistency in the realm of PowerShell - goes back to Windows PowerShell, which would make changing the behavior a more serious breaking change than I originally thought.
Yet, breaking changes are still acceptable if they fall into bucket 3: a breaking change considered to be beneficial while deemed to be unlikely to impact existing code.
However, determining whether something falls into bucket 3 would require extensive analysis of real-world scripts.
more worried about loss of precision than hitting an actual limit.
While I can appreciate that perspective, note that loss of precision has always been and continues to be an ever-present danger in PowerShell, due to the implicit widening to [double] that occurs whenever an [int] / [long] operand is involved in an operation that exceeds the range of these types (smaller integer types are in effect treated like [int]; e.g.:
([int]::MaxValue +1).GetType().Name # !! 'Double'
To give a concrete example of how loss of precision can occur in practice:
# !! -> $false, because [long]::MaxValue + 2 is coerced to [double], resulting in loss of precision# !! The equivalent operation is the following - note the difference in the last digit.# !! 9223372036854775809ul -eq 9223372036854775808ul9223372036854775807ul+2-eq [ulong] ([long]::MaxValue +2)
By contrast, number literals more helpfully widen their types from [int] -> [long] -> [decimal] -> [double].
Suggesting that, for integer literals, [decimal] transition to [bigint] instead of [double], is the subject of:
I don't think that a strict-mode warning will be implemented; the next best thing (though not equivalent) is to get a design-time warning, via implementation of a PSSA rule, which notably would be surfaced via the PowerShell extension in Visual Studio Code.
Activity
mklement0 commented on Mar 13, 2024
The bug boils down to this:
In other words:
is currently - unexpectedly - the same as:
Since [bigint] 1.5 (same as: [bigint] [double] 1.5, given that a number literal with a decimal point is implicitly a [double]) is 1, the result is 5n (aka [bigint] 5)
mklement0 commented on Mar 14, 2024
rhubarb-geek-nz:
Fundamentally, C# plays by different rules than PowerShell, so differing behavior isn't in and of itself relevant.
For that reason, my comment focused on how the observed behavior is inconsistent in the realm of PowerShell - and that's what the conversation should focus on.
As an - inconsequential - aside: C# prevents mixing System.Numerics.BigInteger and System.Double in arithmetic expressions, so that, for instance, double r = (System.Numerics.BigInteger) 1 * 1.2; fails.
Tragen commented on Mar 14, 2024
You are right with C#, I made a mistake there.
But in PS, there should be an error also and not a wrong result.
mklement0 commented on Mar 14, 2024
I haven't looked at any technical challenges, but ideally the - PowerShell-idiomatic - behavior would be:
Automatically convert to [double], because that's what happens with all other integer types ([sbyte], [byte], [int16], [uint16], [int], [uint], [long], [uint64]).
Thus:
SHOULD be:
But currently is:
Result ResultType ------ ---------- 5 System.Numerics.BigIntegerHowever, the open-ended nature of [bigint] theoretically makes it possibly for a given instance not to fit into a [double] - something that cannot happen with the other integer types.
In that case, it would make sense to exhibit the same behavior as when trying too cast a too-large / too-small [bigint] to a [double], which means: [double]::PositiveInfinity / [double]::NegativeInfinity
SeeminglyScience commented on Apr 1, 2024
The Engine WG reviewed this and we've decided this is by design. If bigint is specified, it's expected that very large numbers will be involved. If you need decimal places, we'd recommend converting the bigint to double before the operation.
Tragen commented on Apr 2, 2024
How about a warning when using Set-StrictMode -Version Latest
At least you would know about this. I can accept that it's not working.
But failing without error or warning is not good.
mklement0 commented on Apr 3, 2024
Tragen, speaking in no official capacity whatsoever:
I disagree with the WG decision: coercion to [double] - even if it situationally results in [double]::PositiveInfinity / [double]::NegativeInfinity - amounts to behavior that is consistent with arithmetic with other types; receiving either of the two results allows you to infer what happened. Carving out an exception for [bigint] is a confusing exception that is likely to trip up users.
Set-StrictMode is problematic in general, given PowerShell's dynamic scoping: it affects not just the current scope, but also all descendent scopes, and therefore requires careful managing; note that Set-StrictMode hasn't seen updates since -Version 3.
Given the WG decision:
The behavior should be documented: however, going by past behavior, this likely won't happen until a (non-team) user takes the initiative to ask that the Type conversion to accommodate result part of the docs be amended.
The best you can hope for is implementation of a PSSA rule, so that, for instance you'd get a design-time warning in Visual Studio Code - again, this will likely require user initiative.
Tragen commented on Apr 3, 2024
I think also the decision is wrong.
You force all users to start searching why it isn't working until you find the documentation or the this bug.
It's unnecessary at makes more work.
SeeminglyScience commented on Apr 3, 2024
I would be more worried about loss of precision than hitting an actual limit. Neither choice is wrong here, we just think it's more likely to need precision if you've already specified a bigint. Also helps that it's the existing behavior at least as far back as 5.1 so no breaks.
Keep in mind that this would also apply if we changed it and someone was instead expecting precision. Short of becoming a strict language like C# where it would instead simply fail to compile, that isn't really avoidable.
Tragen commented on Apr 3, 2024
That's the reason why I wanted a strict mode with more warnings.
For me I like it when it fails fast because it's wrong.
And this is clearly wrong and it's also wrong because it works differently in C#.
I would think that PowerShell is .Net and behaves the same way like C# so different behavior is just wrong for me imho.
SeeminglyScience commented on Apr 3, 2024
In C# it just fails to compile, the example you had was double * int. See this SharpLab.io example.
Tragen commented on Apr 3, 2024
I know. And I expect that to happen in PowerShell as well.
I would know much faster to use long or int or double or cast it if necessary.
mklement0 commented on Apr 4, 2024
Tragen, it was never PowerShell's goal to be C#-compatible, and it intentionally deviates from C# in several ways, notably with respect to PowerShell's very flexible implicit type conversions:
Typically, these conversions are helpful and work as one would intuitively expect - pitfalls notwithstanding - and are a better fit for a "looser" shell scripting language vs. a compiled language such as C#.
In the context of PowerShell's existing behavior with integer type operands - coercion to [double] when an operand of that type is involved - I would have expected [bigint] to be treated the same way: it would make for consistent behavior in a PowerShell context.
As Patrick Meinecke (@SeeminglyScience) points out, the behavior of unexpectedly forcing [double] operands to [bigint] - an inconsistency in the realm of PowerShell - goes back to Windows PowerShell, which would make changing the behavior a more serious breaking change than I originally thought.
Yet, breaking changes are still acceptable if they fall into bucket 3: a breaking change considered to be beneficial while deemed to be unlikely to impact existing code.
However, determining whether something falls into bucket 3 would require extensive analysis of real-world scripts.
Patrick Meinecke (@SeeminglyScience)
While I can appreciate that perspective, note that loss of precision has always been and continues to be an ever-present danger in PowerShell, due to the implicit widening to [double] that occurs whenever an [int] / [long] operand is involved in an operation that exceeds the range of these types (smaller integer types are in effect treated like [int]; e.g.:
To give a concrete example of how loss of precision can occur in practice:
By contrast, number literals more helpfully widen their types from [int] -> [long] -> [decimal] -> [double].
Suggesting that, for integer literals, [decimal] transition to [bigint] instead of [double], is the subject of:
mklement0 commented on Apr 4, 2024
Tragen, let me attempt a pragmatic summary (speaking in no official capacity whatsoever):
It isn't PowerShell's goal to be consistent with C# - that won't change.
The WG decision is justifiable, especially from a backward-compatibility perspective.
The documentation should be amended to describe the existing [bigint] behavior; I've created an issue to bring that about:
I don't think that a strict-mode warning will be implemented; the next best thing (though not equivalent) is to get a design-time warning, via implementation of a PSSA rule, which notably would be surfaced via the PowerShell extension in Visual Studio Code.
microsoft-github-policy-service commented on Apr 5, 2024
This issue has been marked as by-design and has not had any activity for 1 day. It has been closed for housekeeping purposes.
microsoft-github-policy-service commented on Apr 5, 2024
📣 Hey Tragen, how did we do? We would love to hear your feedback with the link below! 🗣️
🔗 https://aka.ms/PSRepoFeedback