- Feature Articles
- CodeSOD
- Error'd
- Forums
-
Other Articles
- Random Article
- Other Series
- Alex's Soapbox
- Announcements
- Best of…
- Best of Email
- Best of the Sidebar
- Bring Your Own Code
- Coded Smorgasbord
- Mandatory Fun Day
- Off Topic
- Representative Line
- News Roundup
- Editor's Soapbox
- Software on the Rocks
- Souvenir Potpourri
- Sponsor Post
- Tales from the Interview
- The Daily WTF: Live
- Virtudyne
Admin
AppleScript (the Mac scripting language) uses banker's rounding (round-to-even). However, users complained that it wasn't getting the results they expected from their high school math classes, so in an update Apple added a new parameter: "rounding as taught in school."
round 2.5 -> 2
round 2.5 rounding as taught in school -> 3
Admin
basix. Loved the post.
Admin
Default VB Functionality:
Ctrl-G (Immediate Window)
?Round(39.995,2) 39.99
?Round(11.995,2) 12
Google:
vb round 30 function
URL:
http://ewbi.blogs.com/develops/2003/11/vba_round_yet_a.html
Quote:
It's interesting to see how this happens. When 37.535 is passed through the expression CDbl(Number * (10 ^ NumDigitsAfterDecimal)), where NumDigitsAfterDecimal is 2, it does not return 3753.5. It returns 3753.4999999999994543 (that's the "inexactness" part). This value is increased by .5, becoming 3753.9999999999994543. This is then run through Fix, leaving 3753. Finally, it is divided by 100 to become 37.53. Which is definitely not what we're looking for.
Admin
Admin
Wow this is fascinating. Probably the first time I've actually learned something interesting (and not just hilarious) from tdwtf.
But I'm not following all your explanations. Let me see if I can recap. Based on my understanding, I think the following statement is true:
There is no binary floating point number (of any finite length) that is not exactly representable as a decimal number of finite length.
Right?
Admin
Correct, but not for the explanation Derrick has given. If you use a number system with base three, you have still infinite many numbers in both systems, but there exists infinitely many finite binary floating point numbers which cannot be expressed by finite trinary floating point numbers. I don't know if I can explain it clearer than in my former post...?-|
Admin
I saw many people saying that Banker's rounding has nothing to do with the code presented.
Well almost. Fact that function Round()in VB6 performs Banker's rounding MAY be the explanation of existence of that code. And that's probably what posters writing about Banker's rounding meant.
And to someone asking about decimal type in VB6. Well it's a bit hidden but it exists.
There is no Decimal declarator.
Dim a as Decimal ' It's an error
One has to use Varaint data type
Dim a as Variant ' Or simply just Dim a Dim b as Variant Dim c as Variant
a = CDec(2.3) b = CDec(4.6)
c = a + b ' operation performed on decimal types
Admin
All real numbers are either rational or irrational.
Irrational numbers can never be expressed as a finite series of digits in any (integer) base, because that would make them rational. So don't worry about those.
Every rational number can (by definition) be expressed as a fraction. A fraction can be represented exactly as a sequence of digits in a certain base if and only if some power of that base is divisible by the denominator.* In other words: If all the prime factors of the denominator are also factors of the base.
Since all the prime factors of 2 are also prime factors of 10, all numbers that can be represented precisely in binary can also be represented precisely in decimal. Since not all the prime factors of 10 are present in 2 (specifically, 5), the reverse is not true.
Because neither 2 nor 10 is divisible by 3 (no matter what power you take them to), you can't express 1/3 in either binary or decimal. Similarly, there are lots of binary and decimal numbers you cannot write in base 3.
*: Because a string of n digits a_1, a_2, ..., a_n after the . in some base b is the same as (a_1^n + a_2^(n-1) + ... + a_n)/b^n, and you can only have A/b^n = x/y if b^n is divisible by y (assuming x/y cannot be simplified).
Admin
The answer is in "It's Visual Basic --- an important fact --- so jot that down.". Is there a standard like ANSI/ISO for VB that lays down what each of the functions in this code are REQUIRED to return? Compare this situation to C/C++. ANSI/ISO C/C++ goes into gory details on what pow(), printf(), scanf() etc are required to do).
VB has no documentation that confirms/denies what kind of arithmetic it uses (which version of VB?, is it IEEE754 etc). VB has no detailed documentation on INT() or any arithmetic operations involved here.
In essence, the code is neither buggy nor bad. There is nothing to verify it against.
I did not want to change the original VB code by more than necessary, so C version is also so long.
Thanks.
=== here is C version and its output on Intel Box/WinXP/Cygwin, gcc-3.4.4 ===
#include <stdio.h> #include <math.h>
/* Public Function roundTo( number as Double, digits as Integer ) As Double Dim temp As Double Dim factor As Double
End Function */
double vb_roundto(double number, int digits) {
}
int main() { double d;
}
$ gcc -O3 vbroundc.c $ ./a.exe 3.9 3.900000 3.99 3.990000 3.999 4.000000 39.999 40.000000 39.999999 40.000000 3999.99999 4000.000000 4000.000000
Admin
You're correct: my last sentence didn't follow from the previous sentence, which was about the behavior of nextafter(...). There are infinitely more non-representable numbers than representable numbers in any base because between any two representable numbers of a given length there are infinitely many non-representable numbers.
The last statement skips over the following sentence. Since two shares a non-trivial prime factor (namely itself) with ten, it is possible to represent all finite binary numbers in decimal. The talk about carnality is because I was comparing finite IEEE floating-point to the infinite real line, which means that finding non-representable numbers is very easy.
Admin
Worth noting that Ada has decimal numbers as a built in native type.
Admin
In some piece of code I once found the following for converting counted bytes to megabytes.
my $MB = ($BYTES / 1024 / 1024) + 1;
This does not matter if it is run once a day, which sums up to 30 MBytes in, 30 MBytes out per month.
If someone realizes that a 32bit counter can easily wrap around several times a day and changes the schedule from once a day to once an hour, you get a lot more:
24h * 30d = 720 MB
So, a totally unused line accumulates 1.44 GBytes of traffic per month of fictional usage. Good luck explaining this to the customer... Why not just record the bytes and do all the conversion-foo at the end of the month?
Admin
Just two days ago I had a VB program adding two 2 decimal place numbers together and then failing a comparison that should have passed. Since these were amounts of money, I tried the Currency type and hey presto problem solved. I wish I had kept a note of the numbers, they were not very big at all.
Captcha: ninjas -yeah!
Admin
Hooray for Simon .. super.
From my own experience I know about the problems with floating point math. In one of the first projects I worked as a programmer our team had the benefit of a very sharp project manager who anticipated these kind of issues straightaway. One of the first libraries we wrote for us was on top of the floating point language type in ANSI-C (yes - that was waaaaay back but the issues have not changed - at all). The very first version of that library started out like this:
<snip from lib header - start> #define EPS 0.001 #define GMEPS 0.00001 #define PI 3.14159265358979323846264 #define PI2 6.28318530717958647692528 <snip from lib header - end>
<snip from lib source - start> int comp(a, b, epsilon) double a, b, epsilon; /* Compares two doubles by using the epsilon condition, whereas two doubles are considered equal if their difference is smaller than a given epsilon
Returns 1 on "equality" of r1 and r2, -1 on a < b, 1 on a > b. */ { double r;
} <snip from lib source - end >
it would have been called like this: if (comp(39.95, 39.9500000006, EPS) == 0)) { <yadayada> }
You will notice that we had two different epsilons defined for different application modules in our project the subject of was the control for flame-cutting machines used for cutting steel plates for ship's hulls. The absolute number for the constants were spec'd by the project manager manager mentioned above who was a mechanical engineer and as such our subject matter expert.
Admin
Nope.
"==" works perfectly well for floating point numbers.
The amount of misconceptions and misinformation in this thread is astounding.
Admin
Captcha: pirates. Arrr!
Admin
You should never ever use == with floating point numbers, every serious coder knows that. It because they are represented with a binary exponential. The conversion between binary and decimal very often lead to slight inaccuracies.
Admin
That was just an example to ilustrate use of Decimals.
To perform computations with monetary values one can use currency data type and there is a currency literal in VB6 or can use Deciomal data type converting from currency literal CDec('currency literal here') and obtain exact results.
Admin
This is simple, it's because floating point numbers aren't 100% accurate and int() actually floors the value.
39.995+.005 = 39.9999999999999999 or something as a floating point. Floor this and you get 39.
11.995+.005 = 12.000000000001 or something, which floors to 12.
captch: waffles
Admin
Should that not be 1/10 of a cent?
Admin
Wrong.
float x = 1.23; float y = 1.23;
if (x == y) { // This works perfectly... }
Completely irrelevant.
Maybe the problem is that you're doing conversions where you shouldn't be.
Admin
In a number system with arbitrary base, the '.' is called the "radix point".
Admin
"i only know this because he was on futurama, which is not at all nerdy"
If this comment isn't sarcastic, it is the biggest wtf of all!
Admin
They all sound a bit lame/awkward pronounciation for me. I vote we go with the name "sexual" derived from "sexal".
Then we can all start using the sexual number system
Admin
Banker's Rounding is completely irrelevant for 2 reasons.
a) Int doesn't use it (CInt does). b) Banker's Rounding rounds to the even (not the odd), so if that was the problem it would round to 40.00.
Admin
The issue is floating point, not VB.
Admin
You obviously didn't test that code.
Admin
VB 6 supports Decimal as a variant subtype. Its slow though.
Check out CDec.
Admin
This has nothing to do with the issue at hand.
Admin
Wrap the number in quotes, or stick the currency identifier at the end - I think its @.
CDec("39.995") CDec(39.995@)
Admin
No one noticed the itoa/iota "joke"? Or no one thought it was worth commenting on?
Admin
I'm suprised that no one has mentioned an article that every programmer or computer-type should know by heart: "What every Computer Scientist Should Know About Floating Point Arithmetic" originally by David Goldberg. If you google the title it will find many copies all over the 'net.
All of these things (and more) are explained and EVERYONE should be aware of this possibilities when they are designing or implementing code.
Susan
Admin
VB uses bankers rounding.
You can use the VB format function to round properly ie Format(dValue, "0.00")
There is actually many other different ways to round (about 20 the last time I looked).
Admin
THIS HAS NOTHING TO DO WITH IT.
Admin
Admin
Citation?
Admin
Sorry, my bad, but the idea was there. Shouldn't do these things in a hurry...
captcha: darwin, guess that fits
Admin
Admin
Previous posters are spot-on that this stems from how floating point values are represented inside a computer.
One poster was flamed for commenting that == doesn't work for floats/doubles. Well it's true and not true. == does work if the values are identical. The problem is that as a result of a computation we need to expect there can be small differences. Consider C# code:
Trivially 11.11 * 3 = 33.33 , but as others have pointed out it's the inexact representations of a floating point number that leads to these kinds of oversight errors and to == not working as we expect it to.
If you're interested go and read further: http://www.yoda.arachsys.com/csharp/floatingpoint.html
Admin
In practice, we (the programmers at my place of work) have seen the behaviour depend on the computer model (drivers?) and the status of the network in Windows. The solution was to set the control word to a fixed value each time before we do a critical calculation, and put it back afterwards. So there is definitely another WTF with x86 and Windows.
Admin
Everybody is talking about floating point numbers as being involved. They are, but not as much as the "base 2" that they work in.
In decimal you have fractions like "1/2" and "1/5" that are exactly representable: 0.5 and 0.2.
In decimal you also have fractions like "1/3" and "1/7" that repeat endlessly: 0.3333333... and 0.142857142857....
In binary you have the same: 1/2 and 1/4 are finitely expresable, 1/3, 1/5, 1/10 are not. In binary they are: 0.01010101... 0.001100110011.... and 0.00011001100110011 respectively.
What does this all mean? This means that when we write 2.5 this can be represented exactly in binary: 00010.10000000 . This translates to an exact representation in a floating point number.
However, when we come to 39.995 this is a repeating fraction in binary: 100111.1111110101110000 (10100011110101110000)* , which, with fixed-length floating point numbers ends up rounded a bit.
If you multiply it by 100 in any fixed-length binary format, you'll end up pretty close to 3999.5 (111110011111.1 in binary), but quite possibly also just a little short of that number. Adding .5, and rounding down to the nearest integer might just give you 3999.
The only way to "fix" this is not to use fractions. If you are working with currency, you should do all the math in cents. Suppose your VAT percentage is 21. So to calculate the VAT on something priced 234.50, you do 0.21 * 234.50 = 49.2450. Multiply by 100, add 0.5 round down, and you get 4924, and not 4925.
Work in cents, and you get the right results: multiply 23450 by 0.21 and you get 4924.5 cents (exact binary representation), which correctly rounds to 4925 cents.
Moreover, using floats is dangerous too. At
Admin
As someone previously suggested using string manipulation, I was curious to see what it would take to minimally implement a rounding function in VB6 allowing for any arbitrary input with validation:
Public Function RoundIt(ByVal Number As String, ByVal NumberOfDigits As Long) As String
Dim char As String
Dim lDecPos As Long Dim lOffset As Long
Dim bNegative As Boolean Dim bStayNegative As Boolean Dim bFoundDecimal As Boolean Dim bDone As Boolean
If NumberOfDigits < 0 Then Err.Raise 32000, , "Number of digits must be non-negative." End If
Number = Trim$(Number) bNegative = Left$(Number, 1) = "-" If bNegative Then Number = Trim(Mid$(Number, 2)) End If
If Number = "" Or Number = "." Then Err.Raise 32000, , "Not a valid decimal number." End If
For lOffset = Len(Number) To 1 Step -1 char = Mid$(Number, lOffset, 1) Select Case char Case "." If bFoundDecimal Then Err.Raise 32000, , "Not a valid decimal number (multiple decimal points)." Else bFoundDecimal = True End If Case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" Case Else Err.Raise 32000, , "Not a valid decimal number." End Select Next
lDecPos = InStr(Number, ".") If lDecPos = 0 Or Len(Number) - lDecPos <= NumberOfDigits Then bStayNegative = True Else char = Mid$(Number, lDecPos + NumberOfDigits + 1, 1) Number = Left$(Number, lDecPos + NumberOfDigits) If CInt(char) >= 5 Then For lOffset = Len(Number) To 1 Step -1 char = Mid$(Number, lOffset, 1) Select Case char Case "9" Mid$(Number, lOffset, 1) = "0" Case "0", "1", "2", "3", "4", "5", "6", "7", "8" Mid$(Number, lOffset, 1) = CStr(CInt(char) + 1) bStayNegative = True Exit For End Select Next Else For lOffset = Len(Number) To 1 Step -1 char = Mid$(Number, lOffset, 1) Select Case char Case "1", "2", "3", "4", "5", "6", "7", "8", "9" bStayNegative = True Exit For End Select Next End If End If
Do While Left$(Number, 1) = "0" Number = Mid$(Number, 2) Loop
If Number = "." Then RoundIt = "0." ElseIf bNegative And bStayNegative Then RoundIt = "-" & Number Else RoundIt = Number End If
End Function
Admin
Something in one of the posts made me think of this
Salary Theorem The less you know, the more you make.
Proof:
Postulate 1: Knowledge is Power. Postulate 2: Time is Money. As every engineer knows: Power = Work / Time And since Knowledge = Power and Time = Money It is therefore true that Knowledge = Work / Money . Solving for Money, we get: Money = Work / Knowledge Thus, as Knowledge approaches zero, Money approaches infinity, regardless of the amount of Work done.
Admin
Huh? Of course it is.
1/3 = .3333.....
Multiply both sides by 3
1 = .9999.....
Admin
This is completely irrelevant to the original posting. The issue is not with rounding.
Admin
Admin
by ".9999~" I mean .3333~
Admin
I haven't figured out why the fact that it is VB is so important. The only thing I could think of is the fact that Int() returns a Variant (I think of the same type as the parameter) and not an Integer but I don't know why that would matter.
Admin
Of course you can. Why wouldn't you be able to multiply an infinite series by a number? It's just the equivalent of multiplying each term of the series by that number.
And .9999... is equal to 1, of course, because it's just another representation for the series 9 / 10^i as i goes from 1 to infinity. Two different notations for the same thing.
Admin
This is my absolute favorite comment ever on this site, and I've been reading the site on and off for several years now!