Money, My 2 Cents

xuanbao · · 567 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Dealing with money seems to be a confusing issue. Here&#39;s an example of what seems like a reasonable approach for many applications. I&#39;m curious why more complex solutions are needed. </p> <pre><code>var price float64 = 9845.33 var qty float64 = 3 var discPct float64 = .0725 total := money(price * qty) discount := money(total * discPct) net := money(total - discount) // money returns a &#34;clean&#34; float64 that has been rounded to nearest cent func money(amt float64) float64 { var intAmt int64 = int64((amt + .005) * 100) return float64(intAmt) / 100 } </code></pre> <p>On my 3+ yr old notebook (with AMD chip), money func executes at rate of over 100,000 iterations per millisecond. </p> <pre><code>// exact works like money except you specify how many decimal places func exact(x float64, dec int) float64 { var multiplier float64 = math.Pow(10, float64(dec)) // 1, 10, 100, etc. var rounder float64 = math.Pow(10, float64((dec+1)*-1)) * 5 // .5, .05, .005, etc. var intX int64 = int64((x + rounder) * multiplier) return float64(intX) / multiplier } </code></pre> <hr/>**评论:**<br/><br/>CaptaincCodeman: <pre><p>I think you are better storing currency values as integers instead of floats which are not always precise. There are also very specific rounding rules for accounting purposes.</p></pre>jayposs: <pre><p>Storing &#34;clean&#34; floats seems ok to me. The main problem is computations produce &#34;unclean&#34; results. The money func zeros the extraneous bits (45.296200000001 becomes 45.300000000000).</p></pre>PetalJiggy: <pre><p>Clamping does not solve the fundamental issue. </p> <p><a href="http://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency" rel="nofollow">http://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency</a></p></pre>jayposs: <pre><h2>From the linked post: Representing money as a double or float will probably look good at first as the software rounds off the tiny errors, but as you perform more additions, subtractions, multiplications and divisions on inexact numbers, you&#39;ll lose more and more precision as the errors add up. This makes floats and doubles inadequate for dealing with money, where perfect accuracy for multiples of base 10 powers is required.</h2> <p>Don&#39;t know what you mean by &#34;clamping&#34;.<br/> The solution I have demonstrated removes the tiny error after each computation, so there is no cumulative affect. A decimal type in the language, that removes the need to fix errors, is clearly a better solution. Using only integers would probably drive me crazy, especially with percentages.</p></pre>tcrypt: <pre><p>By clamping, he means your rounding logic that you&#39;re doing.</p> <p>This approach is really bad. We&#39;re talking about programming with money. There is no room for error there. All it takes for your system to fail is forgetting to wrap <code>money()</code> around calls. It&#39;s extremely unsafe.</p> <p>You said this issue was confusing. That indicates that you&#39;re not well aware of the issues with floating point. Please read this: <a href="http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html" rel="nofollow">http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html</a></p></pre>jayposs: <pre><p>Agree that wrapping money() around every computation may not be a good thing, but I still believe this approach works fine. If you want to keep as much precision as possible for each computation (not rounding to specific # of decimal places) a different approach may be needed.</p> <p>Without a decimal number type, you still have to perform some type of extra work to deal with the problem. Didn&#39;t say I was confused, just that there seems to be confusion about what is the best solution to the problem. As far as I can tell, this problem is confusing for many programmers. Of course the Goldberg article really clears things up.</p> <p>Also float64 values hold a ton of precision. If you were to add up millions of float64 decimal values and round just the total, it would probably be correct to the penny.</p></pre>TheMerovius: <pre><blockquote> <p>Didn&#39;t say I was confused, just that there seems to be confusion about what is the best solution to the problem.</p> </blockquote> <p>You disagreeing with the widely held opinion of experts is not the same as there existing confusion around the topic.</p></pre>tty5: <pre><p>a proper decimal type is in the works, see <a href="https://github.com/golang/go/issues/12127" rel="nofollow">https://github.com/golang/go/issues/12127</a></p> <p>also if you only care about printing you can just use format string <a href="https://play.golang.org/p/GaL-ZryNMP" rel="nofollow">https://play.golang.org/p/GaL-ZryNMP</a></p></pre>ackondro: <pre><p>I&#39;d do something like time.Duration: <a href="https://golang.org/pkg/time/#Duration" rel="nofollow">https://golang.org/pkg/time/#Duration</a>. </p></pre>kl0nos: <pre><p>Float for money handling? rounder? Please tell me that you are not working in bank in which i am storing my money. Read how Mt. Gox went down doing things like you try to do. You should ALWAYS operate on full numbers, there is no exceptions to this rule. You only show floats to your customers.</p></pre>jayposs: <pre><p>Do you write code that deals with money ? If so, how do you handle it ? What do you mean by full numbers ? Dollars &amp; cents means there is a fractional part of the value. I suppose all calcs can be done with integers, but that seems overly complex without a better result. If there is a solution that works better, I&#39;m all for it.</p></pre>kl0nos: <pre><p>That&#39;s why you do not operate on 0.5 of dolar but on 50 cents, not on 5 dolars but 500 cents etc. There is a lot of material about this on the internet. So if you store 34 dollars and 30 cents you store it in db as 3430 cents and then you only convert to dollars and cents when you are showing those numbers to client.</p> <p>And no, there is no better solution, this is the only one that is fully secure. There is no better solution because of nature of computers, you don&#39;t have representation of 1/3. </p> <p>how you will make 1/3 + 1/3 + 1/3 = 1 ? You can&#39;t, because you will get 0.33 + 0.33 + 0.33 = 0.99</p></pre>TheMerovius: <pre><p>You are essentially calculating with integers, you just use 53 bit integers. So yes, if you round <em>after every operation</em> -- and as long as you stay below 2<sup>53</sup> cents, which is a reasonable assumption for most applications, but not all -- you can actually get away with using doubles (in my opinion. After thinking about it for 5 minutes. Don&#39;t blame me if you do it, it breaks and you get sued). However, you don&#39;t really gain anything. You are doing <em>exactly</em> the same dance as if you just used int64 from the beginning, except now the compiler won&#39;t tell you if you fuck up.</p> <p>Oh, also your rounding is wrong and might get you sued. But that should be easily fixable.</p></pre>TheMerovius: <pre><p>Arghx, that logic doesn&#39;t even really apply, because you are not calculating in base 10. So you will get screwed up results much sooner. I don&#39;t know exactly how (and have no time to find out), but probably for relevant amounts.</p></pre>jayposs: <pre><p>Ok, maybe the government can&#39;t use it. I&#39;ll rethink with integers, but I still think it&#39;s a good solution. Thanks for the comments.</p></pre>TheMerovius: <pre><blockquote> <p>but I still think it&#39;s a good solution</p> </blockquote> <p>What is that informed by? Have you actually done a calculation of the order of magnitude you need (including intermediate results) and the fact that the arithmetic happens in base 2 (so doing things like dividing by 10 and such will give you more loss of precision than expected)? Did you also understand that what you are doing has zero advantages over using integers and only downsides (i.e. less precision and less compiler checks, combined with the same syntactic and cognitive overhead of doing conversions and rounding)?</p> <p>But, in the end, wha&#39;eva, I guess.</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

567 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传