• (cs)

    Rounding numbers other than to powers of 2 for anything which isn't presentation is always something which merits a double-check for sanity anyway.

  • Pero perić (unregistered)

    TRWTF is bankers rounding especially when it's default one in a framework of choice. If I want rounding error mitigation mechanism, I'll ask for it.

  • RandomGuy (unregistered)

    TRWTF is using binary floating point numbers to represent fixed-point decimals. And if you round in binary, you better round ties to the next even number and not always away from zero (preferably by letting your FPU do that for you, not with some genius hand-made routine).

  • RandomGuy (unregistered) in reply to Pero perić
    Pero perić:
    TRWTF is bankers rounding especially when it's default one in a framework of choice. If I want rounding error mitigation mechanism, I'll ask for it.
    Unfortunately, that is what a lot of programming languages do. E.g., all C based languages truncate to zero when converting floats to int.
  • Franky (unregistered) in reply to RandomGuy
    RandomGuy:
    Pero perić:
    TRWTF is bankers rounding especially when it's default one in a framework of choice. If I want rounding error mitigation mechanism, I'll ask for it.
    Unfortunately, that is what a lot of programming languages do. E.g., all C based languages truncate to zero when converting floats to int.
    that is _not_ bankers rounding (and also can be easily alevated by using the function instead of type conversion). Bankers rounding is rounding to the even number in case the decimal is exactly 0.5.
  • (cs) in reply to Franky
    Bankers rounding is rounding to the even number in case the decimal is exactly 0.5.

    Or more precisely exactly 1/2 of the last significant digit...the irony is that many of these values can NOT be exactly specified in a binary number and therefore bankers rounding should NEVER occur (everything will be above or below the 1/2 way point)...which makes the end results really interesting.

    The (admittedly horrible) string based method actually avoids this issue.

  • Anonymous Will (unregistered)

    Banker's rounding is the default in IEEE floating point hardware.

  • faoileag (unregistered) in reply to RandomGuy
    RandomGuy:
    TRWTF is using binary floating point numbers to represent fixed-point decimals.
    Exactly. String is definitely the type of choice here - unlimited precision and converts easily into other data types as we just have learned yesterday.

    And all the functionality that strings may lack (e.g. "14" / 2 = 7.0) can easily be implemented by operator overloading.

    Ah, I just love strings!

  • faoileag (unregistered)

    And of course TRWTF is non-ascii characters in comments.

  • (cs)

    This is not how it is done in The Book of Five Rings!

  • Pero perić (unregistered) in reply to faoileag
    faoileag:
    RandomGuy:
    TRWTF is using binary floating point numbers to represent fixed-point decimals.
    Exactly. String is definitely the type of choice here - unlimited precision and converts easily into other data types as we just have learned yesterday.

    And all the functionality that strings may lack (e.g. "14" / 2 = 7.0) can easily be implemented by operator overloading.

    Ah, I just love strings!

    Some languages & libraries do provide "binary" fixed-point numbers. If I'm not mistaken "decimal" in C# is basically int32 under the hood.

  • Marphy (unregistered) in reply to faoileag
    faoileag:
    RandomGuy:
    TRWTF is using binary floating point numbers to represent fixed-point decimals.
    Exactly. String is definitely the type of choice here - unlimited precision and converts easily into other data types as we just have learned yesterday.

    And all the functionality that strings may lack (e.g. "14" / 2 = 7.0) can easily be implemented by operator overloading.

    Ah, I just love strings!

    BCD is superior. All the exactness of strings, half the space. Plus the CPU has built-in hardware to do arithmetic on them.

  • Eric (unregistered)

    "how it should have been written"

    What's wrong with just adjusting the FPU rounding mode?

    The rounding isn't done by Delphi, it's done by the CPU, just tell it which rounding mode you want (Set8087CW)

  • faoileag (unregistered) in reply to TGV
    TGV:
    This is not how it is done in The Book of Five Rings!
    "Like a ninja in the night, Paul M., AKA PaulM, stalks across the german offices of Initech Inc. The go-to man in the IT department, he fixes the messes that others leave behind. This is one of his stories."

    "This is why we can't have nice things," Getrude said, pointing to the rounding method.

    PaulM agreed. "This is not how it is done in The Book of Five Rings". He produced his battered old copy and started to flip to the relevant page.

    "The book of Void: Mathematically-Rounding-in-Delphi" he read aloud. "This technique allows you to mathematically round floating-oint numbers in Delphi. But be aware! You must train frequently to achieve the most force of the method!"

    PaulM drew his Ultrabook and let his fingers fly deftly over the surface.

    "There," he said. "Fixed it. Fancy a beer?"

    "You are my hero!" said Getrude.

    To be continued...

  • faoileag (unregistered) in reply to Pero perić
    Pero perić:
    faoileag:
    String is definitely the type of choice here
    Some languages & libraries do provide "binary" fixed-point numbers. If I'm not mistaken "decimal" in C# is basically int32 under the hood.
    Marphy:
    faoileag:
    String is definitely the type of choice here
    BCD is superior. All the exactness of strings, half the space. Plus the CPU has built-in hardware to do arithmetic on them.
    Note to self: next time use [ironic][/ironic] markup.
  • ZoomST (unregistered) in reply to Pero perić
    Pero perić:
    TRWTF is bankers rounding especially when it's default one in a framework of choice. If I want rounding error mitigation mechanism, I'll ask for it.
    Sorry but bankers did ask for it... and they have money to pay people to implement it for them. In some cases they even ask for a "creative" and "secret" bankers rounding that favours them in a way... something like round up when is a customers' debt, and down when is a payment.
  • Rarog-IT (unregistered)

    The new version is of course much better, though I would prefer to avoid almost duplicate code using one additional variable.

    function MyRound(Value: extended; Decimals: integer = 2; const Mathematically: 
     boolean = true): double;
    var
      Factor: Integer; 
    begin 
     if Mathematically then 
        begin
          Factor := 1 - 2 * Integer(Value < 0); 
          Result := Trunc((Value * power(10, Decimals)) + 0.5 * Factor) / power(10, Decimals); 
        end 
     else 
        Result := Round(Value * power(10, Decimals)) / power(10, Decimals); 
    end;
  • ZoomST (unregistered) in reply to Rarog-IT
    Rarog-IT:
    The new version is of course much better, though I would prefer to avoid almost duplicate code using one additional variable.
    [ironic]
    Always avoid duplicating the code -- better use copy & paste!
    [/ironic]
  • gnasher729 (unregistered) in reply to Franky
    Franky:
    that is _not_ bankers rounding (and also can be easily alevated by using the function instead of type conversion). Bankers rounding is rounding to the even number in case the decimal is exactly 0.5.
    It's not.

    Curiously, that's how "Banker's rounding" is defined, but not what banks (especially European banks) do: The rule that they should follow is that 0.5 is rounded up.

  • (cs) in reply to Rarog-IT
    Rarog-IT:
    The new version is of course much better, though I would prefer to avoid almost duplicate code using one additional variable.
    function MyRound(Value: extended; Decimals: integer = 2; const Mathematically: 
     boolean = true): double;
    var
      Factor: Integer; 
    begin 
     if Mathematically then 
        begin
          Factor := 1 - 2 * Integer(Value < 0); 
          Result := Trunc((Value * power(10, Decimals)) + 0.5 * Factor) / power(10, Decimals); 
        end 
     else 
        Result := Round(Value * power(10, Decimals)) / power(10, Decimals); 
    end;

    You've left the 4 exact duplicate copies of power(10, Decimals) intact. Is it only "almost duplicate" code that you don't like?

  • RandomGuy (unregistered) in reply to Franky
    Franky:
    RandomGuy:
    Pero perić:
    TRWTF is bankers rounding especially when it's default one in a framework of choice. If I want rounding error mitigation mechanism, I'll ask for it.
    Unfortunately, that is what a lot of programming languages do. E.g., all C based languages truncate to zero when converting floats to int.
    that is _not_ bankers rounding (and also can be easily alevated by using the function instead of type conversion). Bankers rounding is rounding to the even number in case the decimal is exactly 0.5.
    Sorry, my bad. I always thought "banker's rounding" means "round in a way beneficial for the bank", so usually round down if the bank has to give money to it's customer. And I do think that round-half-even (as I would call it) should generally be the preferred rounding mode. Rounding ties away from or towards zero are like the most arbitrary rounding modes (why would that be called "mathematical" rounding?).
  • Rarog-IT (unregistered) in reply to Hmmmm
    Hmmmm:
    You've left the 4 exact duplicate copies of power(10, Decimals) intact. Is it only "almost duplicate" code that you don't like?
    Of course you're right and this should be saved in another variable as it's the same for all occasions. Don#t ask me, why I overlooked it. :D
  • Pock Suppet (unregistered) in reply to Rarog-IT
    Rarog-IT:
    The new version is of course much better, though I would prefer to avoid almost duplicate code using one additional variable.
    function MyRound(Value: extended; Decimals: integer = 2; const Mathematically: 
     boolean = true): double;
    var
      Factor: Integer; 
    begin 
     if Mathematically then 
        begin
          Factor := 1 - 2 * Integer(Value < 0); 
          Result := Trunc((Value * power(10, Decimals)) + 0.5 * Factor) / power(10, Decimals); 
        end 
     else 
        Result := Round(Value * power(10, Decimals)) / power(10, Decimals); 
    end;

    Ah, "clever". Far superior to

    Factor := Value / Abs(Value)
  • Rarog-IT (unregistered) in reply to Pock Suppet
    Pock Suppet:
    Ah, "clever". Far superior to
    Factor := Value / Abs(Value)
    You're right, too. I definitely shouldn't write code during lunch. :/
  • Walky_one (unregistered) in reply to Rarog-IT
    Rarog-IT:
    The new version is of course much better, though I would prefer to avoid almost duplicate code using one additional variable.
    function MyRound(Value: extended; Decimals: integer = 2; const Mathematically: 
     boolean = true): double;
    var
      Factor: Integer; 
    begin 
     if Mathematically then 
        begin
          Factor := 1 - 2 * Integer(Value < 0); 
          Result := Trunc((Value * power(10, Decimals)) + 0.5 * Factor) / power(10, Decimals); 
        end 
     else 
        Result := Round(Value * power(10, Decimals)) / power(10, Decimals); 
    end;

    Cleanup for readability:

    function MathRound(Value: extended; Decimals: integer): double; overload;
    var
      Factor: Integer; 
    begin 
      Factor := 1 - 2 * Integer(Value < 0); 
      Result := Trunc((Value * power(10, Decimals)) + 0.5 * Factor) / power(10, Decimals);
    end; 
    
    function MathRound(Value: extended): double; overload;
    begin 
      Result := Trunc(Value + 0.5);
    end; 
    
    
    function BankerRound(Value: extended; Decimals: integer): double; overload;
    var
      Factor: Integer; 
    begin 
      Result := Round(Value * power(10, Decimals)) / power(10, Decimals); 
    end;
    
    function BankerRound(Value: extended): double; overload;
    begin 
      Result := Round(Value); 
    end;
    

    Problems:

    1. Don't add boolean flags that "switch" the behavior of a function (even more so, if you always define at compile time which version you need)
    2. Default param of "2" is a bit strange (except if you explicitly work with currency. In which case your method should be called "RoundCurrency" or something similar)
  • Marcel (unregistered) in reply to Pock Suppet
    Pock Suppet:
    Rarog-IT:
    Factor := 1 - 2 * Integer(Value < 0);
    Ah, "clever". Far superior to
    Factor := Value / Abs(Value)

    ... which will crash when Value = 0.

  • Doodpants (unregistered) in reply to ZoomST
    ZoomST:
    Pero perić:
    TRWTF is bankers rounding especially when it's default one in a framework of choice. If I want rounding error mitigation mechanism, I'll ask for it.
    Sorry but bankers did ask for it... and they have money to pay people to implement it for them. In some cases they even ask for a "creative" and "secret" bankers rounding that favours them in a way... something like round up when is a customers' debt, and down when is a payment.
    Don't forget "Superman III/Office Space rounding": round off all fractions of a cent, and deposit them into my bank account.
  • Pero perić (unregistered) in reply to RandomGuy
    RandomGuy:
    And I do think that round-half-even (as I would call it) should generally be the preferred rounding mode. Rounding ties away from or towards zero are like the most arbitrary rounding modes (why would that be called "mathematical" rounding?).
    That's a matter of subjectivity. Around here (in some EU country) "rounding ties away from zero" is taught in elementary school mathematics with "because mathematicians agreed so" explanation. Anything else seam arbitrary especially a method that considers an extra digit to break a tie. Also why is round-half-even standardized instead of round-half-odd?
  • faoileag (unregistered) in reply to Marcel
    Marcel:
    Pock Suppet:
    Rarog-IT:
    Factor := 1 - 2 * Integer(Value < 0);
    Ah, "clever". Far superior to
    Factor := Value / Abs(Value)

    ... which will crash when Value = 0.

    A

    if Value = 0
      begin
        Result := 0;
      end
    else
      begin
        ...all the rest of the code...
      end
    

    would fix that, wouldn't it?

    (I haven't done Delphi in ages, it was still called Borland Pascal in those days...)

  • Walky_one (unregistered)

    And just generally: Bankers rounding might be the way to go for money related math. However if you work in other areas, it's nearly always plain wrong:

    Round(11.5) - Round(10.5) = 2 Round(12.0) - Round(11.0) = 1 Round(12.5) - Round(11.5) = 0

    So if you have two values that have a fix distance of exactly 1, due to inconsistent rounding you get anything between 0 and 2.

    Example: You work with graphics and draw a rectangle from 10.5 to 20.5. Rounded to pixel values you get a 10-pixel wide rectangle.

    If you move the rectangle by 1 pixel (11.5 to 21.5) your rectangle is suddenly 12 pixels wide. If you have a repetitive pattern to display, this is VERY visible...

    (Yes, this is a generic issue in most modern programming languages where every value seems to be money related...) (Yes, I know that it can be switched off... But only global for the whole application)

  • DonaldK (unregistered)

    Excel's spreadsheet functions use banker's rounding.

    Excel's VBA functions use mathematical rounding.

    Good luck with getting around that one...

  • gnasher729 (unregistered) in reply to Walky_one
    Walky_one:
    And just generally: Bankers rounding might be the way to go for money related math. However if you work in other areas, it's nearly always plain wrong:

    Round(11.5) - Round(10.5) = 2 Round(12.0) - Round(11.0) = 1 Round(12.5) - Round(11.5) = 0

    So if you have two values that have a fix distance of exactly 1, due to inconsistent rounding you get anything between 0 and 2.

    Example: You work with graphics and draw a rectangle from 10.5 to 20.5. Rounded to pixel values you get a 10-pixel wide rectangle.

    If you move the rectangle by 1 pixel (11.5 to 21.5) your rectangle is suddenly 12 pixels wide. If you have a repetitive pattern to display, this is VERY visible...

    (Yes, this is a generic issue in most modern programming languages where every value seems to be money related...) (Yes, I know that it can be switched off... But only global for the whole application)

    It's a good idea for rounding the last bit in floating-point arithmetic: It means that the last bit is zero more often, and when you add two numbers of equal magnitude where both have the last bit equal to zero, the sum will more often have no rounding error at all.

    When rounding integer values, you are right, you have to figure out what you actually want to achieve by rounding. For rectangles, it's tricky. Take a few rectangles with integer coordinates that fit together exactly (for example a larger rectangle split into 2 x 5 sub rectangles). Now scale everything by a factor of 1.6 and round, and make sure that all rectangles still fit exactly together, and all have the same size. Impossible.

  • (cs) in reply to Marcel
    Marcel:
    Pock Suppet:
    Rarog-IT:
    Factor := 1 - 2 * Integer(Value < 0);
    Ah, "clever". Far superior to
    Factor := Value / Abs(Value)

    ... which will crash when Value = 0.

    I would expect Delphy to have a
    Sign()
    function...

  • Pete (unregistered)

    Banker's rounding is designed to minimize the difference between round( sum(x_1..x_n) ) and sum( round(x_1) ... round(x_n) ), in essence, if you have a thousand small payments then the rounding shouldn't make a big difference to the total.

    Always rounding up or down (or away from zero / towards zero) will cause the rounding error to accumulate, this usually won't.

  • (cs) in reply to Walky_one
    Walky_one:
    And just generally: Bankers rounding might be the way to go for money related math. However if you work in other areas, it's nearly always plain wrong:

    Round(11.5) - Round(10.5) = 2 Round(12.0) - Round(11.0) = 1 Round(12.5) - Round(11.5) = 0

    So if you have two values that have a fix distance of exactly 1, due to inconsistent rounding you get anything between 0 and 2.

    Example: You work with graphics and draw a rectangle from 10.5 to 20.5. Rounded to pixel values you get a 10-pixel wide rectangle.

    If you move the rectangle by 1 pixel (11.5 to 21.5) your rectangle is suddenly 12 pixels wide. If you have a repetitive pattern to display, this is VERY visible...

    (Yes, this is a generic issue in most modern programming languages where every value seems to be money related...) (Yes, I know that it can be switched off... But only global for the whole application)

    ANY time you do arithmetic with rounded values, you are in for a world of hurt(*), no matter how you do your rounding.

    (*) This is a stock phrase. I think in this case, it's just a small country's worth of hurt, not a whole world, but circumstances may alter that.

    And you're wrong in your example, because bround(11.5) == 12 and bround(21.5) ==> 22. 22-12 = 10. The problem arises when you have 10.5 and 21.5. brounding those numbers gives 10 and 22, difference 12. Add 1 and then bround, 11.5=>12, 22.5=>22, difference 10. (Note: I'm not saying the concept is wrong, just the specific numbers.)

  • Rarog-IT (unregistered) in reply to Medinoc
    Medinoc:
    I would expect Delphy to have a
    Sign()
    function...
    Starting with Delphi XE2 this is the case, it is missing is missing versions.
  • Anonymous Will (unregistered) in reply to Pero perić
    Pero perić:
    That's a matter of subjectivity. Around here (in some EU country) "rounding ties away from zero" is taught in elementary school mathematics with "because mathematicians agreed so" explanation. Anything else seam arbitrary especially a method that considers an extra digit to break a tie. Also why is round-half-even standardized instead of round-half-odd?

    Rounding ties away from zero is exactly as arbitrary as rounding them to even or any other tie-breaking method. Each has its pros and cons.

  • (cs) in reply to Walky_one
    Walky_one:
    Round(11.5) - Round(10.5) = 2 Round(12.0) - Round(11.0) = 1 Round(12.5) - Round(11.5) = 0

    So if you have two values that have a fix distance of exactly 1, due to inconsistent rounding you get anything between 0 and 2.

    Example: You work with graphics and draw a rectangle from 10.5 to 20.5. Rounded to pixel values you get a 10-pixel wide rectangle.

    The error is in your example not in the rounding. If you really care about preserving distance you would round as late as possible, right before showing it on screen:

    Round(11.5 - 10.5) = 1 Round(12.0 - 11.0) = 1 Round(12.5 - 11.5) = 1

  • 30into (unregistered) in reply to faoileag
    faoileag:
    TGV:
    This is not how it is done in The Book of Five Rings!
    "Like a ninja in the night, Paul M., AKA PaulM, stalks across the german offices of Initech Inc. The go-to man in the IT department, he fixes the messes that others leave behind. This is one of his stories."

    "This is why we can't have nice things," Getrude said, pointing to the rounding method.

    PaulM agreed. "This is not how it is done in The Book of Five Rings". He produced his battered old copy and started to flip to the relevant page.

    "The book of Void: Mathematically-Rounding-in-Delphi" he read aloud. "This technique allows you to mathematically round floating-oint numbers in Delphi. But be aware! You must train frequently to achieve the most force of the method!"

    PaulM drew his Ultrabook and let his fingers fly deftly over the surface.

    "There," he said. "Fixed it. Fancy a beer?"

    "You are my hero!" said Getrude.

    To be continued...

    Sorry, I don't buy this. it's too well written to be a real Hanzo story.

  • foo AKA fooo (unregistered) in reply to Rarog-IT
    Rarog-IT:
    Pock Suppet:
    Ah, "clever". Far superior to
    Factor := Value / Abs(Value)
    You're right, too. I definitely shouldn't write code during lunch. :/
    Not sure who's being sarcastic here, but introducing an expensive division (apart from the division by zero problem that has been mentioned) instead of a cheap integer operation is hardly superior. (Now, it the change was the other way around, you could scream premature optimization, but actively spending effort to pessimize code doesn't get that excuse.)

    Now, while "1 - 2 * Integer(Value < 0)" might be clever, it's not the most readable. Remind me again why (most) Pascal programmers dislike the conditional operator. "Value > 0 ? 0.5 : -0.5" seems much clearer and avoids another multiplication (whatever the exact syntax, and whether or not you keep the temp variable or put it in the main expression).

  • foo AKA fooo (unregistered) in reply to Anonymous Will
    Anonymous Will:
    Pero perić:
    That's a matter of subjectivity. Around here (in some EU country) "rounding ties away from zero" is taught in elementary school mathematics with "because mathematicians agreed so" explanation. Anything else seam arbitrary especially a method that considers an extra digit to break a tie. Also why is round-half-even standardized instead of round-half-odd?

    Rounding ties away from zero is exactly as arbitrary as rounding them to even or any other tie-breaking method. Each has its pros and cons.

    No, it's only approximately as arbitrary. :)

  • foo AKA fooo (unregistered) in reply to Steve The Cynic
    Steve The Cynic:
    *ANY* time you do arithmetic with rounded values, you are in for a world of hurt(*), no matter how you do your rounding.

    (*) This is a stock phrase. I think in this case, it's just a small country's worth of hurt, not a whole world, but circumstances may alter that.

    A small country's worth of hurt is not necessarily better when dealing with bankers.

  • Rarog-IT (unregistered) in reply to foo AKA fooo
    foo AKA fooo:
    Now, while "1 - 2 * Integer(Value < 0)" might be clever, it's not the most readable. Remind me again why (most) Pascal programmers dislike the conditional operator. "Value > 0 ? 0.5 : -0.5" seems much clearer and avoids another multiplication (whatever the exact syntax, and whether or not you keep the temp variable or put it in the main expression).
    Ha, I really wasn't aware of the existence of such function in Delphi and I like the ternary operator and use it for such small assignments in C/C++ and Java. Though Delphi IfThen seems to be inferior to the ?: operator in case of evaluation laziness it's still the best compromise in case of readability and functionality.
  • Marcel (unregistered) in reply to foo AKA fooo
    foo AKA fooo:
    Remind me again why (most) Pascal programmers dislike the conditional operator. "Value > 0 ? 0.5 : -0.5" seems much clearer and avoids another multiplication (whatever the exact syntax, and whether or not you keep the temp variable or put it in the main expression).

    We don't have it in Delphi; pretty much everyone else I know working in it wrote their own.

  • Rarog-IT (unregistered) in reply to Marcel
    Marcel:
    We don't have it in Delphi; pretty much everyone else I know working in it wrote their own.
    Delphi 5 doesn't have it, Delphi 2010 has it, so at some point in between these versions it was officially implemented.
  • Marcel (unregistered) in reply to Rarog-IT
    Rarog-IT:
    Marcel:
    We don't have it in Delphi; pretty much everyone else I know working in it wrote their own.
    Delphi 5 doesn't have it, Delphi 2010 has it, so at some point in between these versions it was officially implemented.

    Ah... I haven't worked with Delphi since Delphi 7; I wasn't aware it was added.

  • Marcel (unregistered) in reply to Rarog-IT
    Rarog-IT:
    Marcel:
    We don't have it in Delphi; pretty much everyone else I know working in it wrote their own.
    Delphi 5 doesn't have it, Delphi 2010 has it, so at some point in between these versions it was officially implemented.

    Ah... I haven't worked with Delphi since 7; I wasn't aware it was added.

    (Trying again, the first time the response just didn't show up.)

  • (cs) in reply to faoileag
    faoileag:
    And all the functionality that strings may lack (e.g. "14" / 2 = 7.0) can easily be implemented by operator overloading.
    I propose "bankers adding". It will finally solve the ambiguity between adding and appending in dynamically typed languages:
    13 + 2 = 15
    14 + 2 = 142
    
  • Anon (unregistered)

    I've never been taught rounding other than 'Add 0.5 and then truncate to the integer part.'

    That's clean and simple, and never leaves room for ambiguity.

  • gnasher729 (unregistered) in reply to Anon
    Anon:
    I've never been taught rounding other than 'Add 0.5 and then truncate to the integer part.'

    That's clean and simple, and never leaves room for ambiguity.

    So you would round -12.4 to -11.

    -12.4 + 0.5 = -11.9. Truncated to integer part = -11.

Leave a comment on “Fixing Delphi”

Log In or post as a guest

Replying to comment #:

« Return to Article