Currency data type: wrong calculations and comparison!

Kem recently reported that there was a serious bug in Currency data type for 64-bit applications. Another member of the forum pointed out to me another bug, even more serious, on Currency (even in 32 bits).

- Calculations between Currency variables can be wrong!
feedback://showreport?report_id=41711

- Currency comparison operations may be wrong if it is a 64-bit application.
feedback://showreport?report_id=47822

Since this data type is often used in accounting and business management software, it is particularly serious. False data that can be recorded in the accounts.

Are there other problems reported with numbers (double, integer, currency…)?

Please consider adding your points to these Feedback!

I made my own …
https://forum.xojo.com/20531-double-currency/?quote=360880#reply

Thank you Jean-Yves! I hadn’t seen all those threads.
I’m going to study your workaround, but it’s really disappointing that Xojo inc. doesn’t fix bugs that touch the fundamentals of language. They could at least have added a warning in the documentation if the problem is too complicated/long to resolve.

Like everyone else, I store all my financial values in integers (cents), I also do as many calculations as possible with integers. But sometimes there are intermediate calculations made with currency. I never imagined that a data type would be unreliable!

I’ll study your workaround. And there’s a huge amount of verification work on all my existing codes.

Please, Xojo inc! Problems on the Currency have been reported since at least 2014, many customers have already reported their disappointment to you, please take this into account!

• September 2014
https://forum.xojo.com/15425-is-currency-data-type-really-usable
https://forum.xojo.com/18055-llvm-fp128-type

• March 2015

• December 2015

[quote=233360:@Joost Rongen]

Please Xojo Inc. take the message in this thread highly serious and prioritize these matters above adding new stuff.
We dealt with it far too long.[/quote]

• November 2016

• January 2018
This thread and others

I highly recommend to look into the BigNumberMBS class to use as currency replacement.

http://www.monkeybreadsoftware.net/class-bignumbermbs.shtml

A 320bit number with 100 digits of precision, so you can use 1000000 as factor ad of 10000 as currently data type.

There remains issues with the Text type on Linux as well. I discussed this at length in my 2017 review series. Suffice to say they make minimal effort to test and verify that core data types work as intended. This really hurts the language and tool.

// 2 Years ago. 64 bit mode.

Dim a As Currency = 1.32
Dim b As Currency = 1.33
If a=b then MsgBox “This is unacdeptable to exist for more than 48h after discovered”

It is difficult to speculate why such fundamental issues persist. Perhaps the signal to noise ratio in the forums is too high. With Norman stepping out I feel it will be even harder to bring attention to critical issues.

Feedback outside of us voting does not seem to have any real prioritization capabilities. I am sure they have some internally but surely from blogs and numerous posts someone over there is aware…

For a bug this serious I would bring it to the attention of @Geoff Perlman . He’s the one ultimately responsible for priorities.

This can be useful for some of you, a class with some “Currency” functionality I wrote few minutes ago. Expand it as necessary.
Barely tested. Please do more tests. The AsInt returns an Int64 with the contents of the “money” for storage purposes, the AsDbl can be used for calculations.

[code]Class Money
Function Add(d As Double) As Money
Dim m As New Money(d)
intVal=intVal+m.AsInt
Return me
End Function

	Function Add(m As Money) As Money
	  intVal=intVal+m.AsInt
	  Return me
	End Function

	Sub Constructor(d As Double)
	  intVal = d * 10000
	End Sub

	Sub Constructor(m As Money)
	  intVal = m.AsInt
	End Sub

	Function equals(d As Double) As Boolean
	  Dim m As  New Money(d)
	  Return intVal=m.AsInt
	End Function

	Function equals(m As Money) As Boolean
	  Return intVal=m.AsInt
	End Function

	Sub Go()
	  // Does nothing to end one liners like m.Add(x).Subt(y).Round.Go
	End Sub

	Function Round() As Money
	  // 1.3651 -> 1.3700
	  If intVal Mod 100 > 49 Then intVal  = intVal + 100
	  intVal = (intVal \\ 100) * 100
	  Return me		  
	End Function

	Function Save() As Money
	  saveVal = intVal
              Return me
	End Function
    
	Function Restore() As Money
	  intVal = saveVal
              Return me
	End Function        

	Function Subt(d As Double) As Money
	  Dim m As  New Money(d)
	  intVal=intVal-m.AsInt
	  Return me
	End Function

	Function Subt(m As Money) As Money
	  intVal=intVal-m.AsInt
	  Return me
	End Function

	Function Times(d As Double) As Money
	  intVal=((intVal/10000)*d)*10000
	  Return me
	End Function

	Function Times(m As Money) As Money
	  intVal=((intVal/10000)*m.AsDbl)*10000
	  Return me
	End Function

	Function ToString(f As String = "-###,###,###,###,##0.00##") As String
	  Return Format(intVal / 10000, f)
	End Function

	Function ToText(f As String = "-###,###,###,###,##0.00##") As Text
	  Return Format(intVal / 10000, f).ToText
	End Function

	Function Trunc() As Money
	  // 1.3699 -> 1.3600
	  intVal = (intVal \\ 100) * 100
	  Return me
	End Function

	Getter AsCurr As Currency
		  Dim m As New MemoryBlock(8)
		  m.Int64Value(0)=intVal
		  Return m.CurrencyValue(0)
           End Getter

           Setter AsCurr As Currency
		  Dim m As New MemoryBlock(8)
		  m.CurrencyValue(0)=Value
		  intVal = m.Int64Value(0)
           End Setter
    
	Getter AsDbl As Double
		  return intVal / 10000
            End Getter

	Setter AsDbl As Double
		  intVal = value * 10000
            End Setter
    

           Getter AsInt As Int64
		  return intVal
           End Getter

          Setter AsInt As Int64
		  intVal = value
          End Getter


          Property Private intVal As Int64 = 0

          Property Private saveVal As Int64 = 0

End Class
[/code]

Okay, the reason is technically a problem with code generation:

0x1000c041e <+238>: callq 0x1000c6ea4 ; symbol stub for: RuntimeCvtCurrencyToUInt64 0x1000c0423 <+243>: movq -0x50(%rbp), %rdi 0x1000c0427 <+247>: movq %rax, -0x58(%rbp) 0x1000c042b <+251>: callq 0x1000c6ea4 ; symbol stub for: RuntimeCvtCurrencyToUInt64 0x1000c0430 <+256>: movq -0x58(%rbp), %rcx 0x1000c0434 <+260>: cmpq %rax, %rcx
The function RuntimeCvtCurrencyToUInt64 are called to convert each currency value to UInt64.

Looking on registers:

General Purpose Registers:
rax = 0x0000000000000001
rbx = 0x0000000100b28a10
rcx = 0x0000000000000001
rdx = 0x0000000000000001

you see that RuntimeCvtCurrencyToUInt64 returns only the part before dot, so we compare 1 to 1, which is equal.

with

Dim a As Currency = 3.32 Dim b As Currency = 5.33
I do get
General Purpose Registers:
rax = 0x0000000000000005
rbx = 0x0000000100b49fe0
rcx = 0x0000000000000003

so I think this is right. RuntimeCvtCurrencyToUInt64 should not be called at all in this case and compared directly.

So the bug is that they do UInt64 comparison instead of currency comparison.

@Joe Ranieri, if you find time, please change the code generator here.

32 bit compares currency directly:

0xd91dc <+314>: movl   -0x38(%ebp), %edi
0xd91df <+317>: movl   -0x34(%ebp), %eax
0xd91e2 <+320>: cmpl   %edi, %ebx

[quote=371407:@Rick Araujo]This can be useful for some of you, a class with some “Currency” functionality I wrote few minutes ago. Expand it as necessary.
Barely tested. Please do more tests. The AsInt returns an Int64 with the contents of the “money” for storage purposes, the AsDbl can be used for calculations.[/quote]

how do i paste the whole class into the IDE??

Thank you all!

Christian, as usual you are indispensable to the Xojo community!

In your feedback://showreport?report_id=41711 , you also discovered that the calculations between Currency were wrong:

https://www.dropbox.com/preview/Public/xojo-currency-wrong.png?share=1

Alberto De Poo added clarification:

dim Value3 as Currency = 46.78 dim Value4 as Currency = 1.509 dim Result2 as Currency = Value3/Value4 = 31.1866 dim Result3 as Currency = 46.78/Value4 = 31.0007
And

[quote]

  • the calculation is Currency1 / Currency2, then only 2 decimals are used for Currency2
  • but when the calculation is 46.78 / Currency2, then the 4 decimals in Currency2 are used.[/quote]

Maybe that can help to fix this other bug.

I also made two wishes:
feedback://showreport?report_id=51272 and feedback://showreport?report_id=51273

For the divine, the first one calls RuntimeDivCurrency, while second one is performed with double numbers and divsd command, so a double division. Could be RuntimeDivCurrency is not working as expected.

Well, it looks like RuntimeDivCurrency does divide the second parameter by 100 and multiplies the first parameter by 100, so your division gets 4678.0 / 1.50 internally.

I would suggest to make a check in the RuntimeDivCurrency:

if dividend is <= 922337203685477.5807 (and > -922337203685477.5807), please multiply dividend by 10000, than do UInt64 division with divisor and be happy.

e.g.
46.78 is internally 467800.
1.509 is internally 15090.

467800 * 10000 / 15090 gives 310006, which is shown as 31.0006

even better may be using *100000 and do rounding.

467800 * 100000 / 15090 gives 3100066, which could be rounded to 31.0007
(if last digit of x is >=5, result is x/10+1, else x/10)

For use with 100000, please use only with numbers < 92233720368547.7580 and > -92233720368547.7580

Here, I evolved it a bit and packed an example.

https://drive.google.com/file/d/1gQP7vVvJyIAunBRSkJdJyI2PJJErpPFi/view?usp=sharing

[quote=371481:@Rick Araujo]Here, I evolved it a bit and packed an example.

https://drive.google.com/file/d/1gQP7vVvJyIAunBRSkJdJyI2PJJErpPFi/view?usp=sharing[/quote]

rick, thanks for packing this up.

still curious how to cut and paste the whole class for future…
for the other nativecurrency class, i have to cut and paste section by section.