1

I am building an API and the system that I am using just uses midpoint rounding in PowerShell. I've come to find out that certain numbers still disobey this rule and my research as to why has proven futile. Basically, anything below 5 rounds down, and anything above 5 rounds up, except for certain situations I guess?

My scripts are included in this image if anybody would like to assist.

My Scripts

11.025 = 11.03
12.025 = 12.03
29.025 = 29.03
39.025 = 39.03

This values and everything in the script is expected EXCEPT for:

19.025 = 19.02

If somebody could explain what is going on that would be greatly appreciated.

phuclv
  • 37,963
  • 15
  • 156
  • 475

2 Answers2

1

In PowerShell floating-point literals are of type [double], and the closest to 19.025 in double precision is 19.0249999999999985789... which is closer to 19.02 than 19.03. Therefore the result is as expected.

In fact most decimal values won't be stored exactly in binary floating-point. If you're not aware of that please check out Is floating point math broken?. It's impossible to store your values as finite non-repeating binary fractional values. You can check that easily by printing more digits

PS $> 11.025, 12.025, 19.015, 19.025, 29.025, 39.025 |% { $_.ToString("G60") }
11.0250000000000003552713678800500929355621337890625
12.0250000000000003552713678800500929355621337890625
19.0150000000000005684341886080801486968994140625
19.02499999999999857891452847979962825775146484375
29.02499999999999857891452847979962825775146484375
39.02499999999999857891452847979962825775146484375

There is nothing that prevents midpoint rounding from working, it's just because the digit isn't exactly 5. If you want to deal with exact decimal values you must use the [decimal] type. But you'll have to initialize the value directly with the d suffix (like 19.025d) or convert from string as [decimal]"19.025". Don't cast from [double] to [decimal] because that won't give you the lost precision back, [decimal]double_literal is done as [decimal]([double]double_literal). [decimal]19.025 can result in the value rounded to [double] which is 19.0249999999999985789... Somehow it works in that case, probably because the shell constructs the [decimal] directly from the literal as string, but it won't work in all cases, for example

PS $> [decimal]19.0255555555555555555555555555
19.0255555555556
PS $> [decimal]19.02555555555555e25
190255555555556000000000000
mklement0
  • 382,024
  • 64
  • 607
  • 775
phuclv
  • 37,963
  • 15
  • 156
  • 475
  • I don't think that PowerShell constructs the `[decimal]` instance from the string representation of the string literal. I'm unclear on the exact conversion mechanism, but with `19.25` as a `[double]`, it is regular rounding to the closest _decimal_ representation that presumably takes place, which is _not_ midpoint rounding with this number, as you can see by doing `([decimal] 19.0249999999999985789).ToString('N20')`. However, your advice to _start_ with a `[decimal]` number literal is sound. – mklement0 Jul 20 '23 at 17:21
0

I am using [float] variables, everything I have seen about rounding issues has been about [float] variables. I casted to [decimal] instead, and that cleared everything up. I guess there are bits in a [float] that sometimes prohibit it from rounding with MidpointRounding.

  • As an aside: PowerShell's floating-point number literals become `[double]` instances, not `[float]`. Verify with `(1.0).GetType().FullName` – mklement0 Jul 20 '23 at 15:17