Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Re^3: Fix floats like you do in your head

by mojotoad (Monsignor)
on Dec 25, 2002 at 01:07 UTC ( [id://222154]=note: print w/replies, xml ) Need Help??


in reply to Re: Re: Fix floats like you do in your head
in thread Fix floats like you do in your head

No worries. After I went and perused your Math::Trig::Units work, it became evident that this wasn't an oversight on your part. Ah well, the links are there for posterity, in case anyone else wants to learn about it.

As for names...approx() works. How about fudge()?

At any rate, at least you know that we're dealing with a psychological tendency, here. With 6 sig figs, for example, 1.99999 is no more special than 1.46793 on a mathematical basis. On a psychological basis it is somewhat irritating because of our preference for nice, neat numbers.

Matt

Replies are listed 'Best First'.
Re: Re^3: Fix floats like you do in your head
by tachyon (Chancellor) on Dec 25, 2002 at 09:48 UTC

    fudge() is good. I will add it as an alias. I couldn't think of anything better at the time. might_be() looks_like() probably() possibly() neaten() all seemed...well a little wishy washy.

    You are of course right about the psychological tendency. That's why the marketers love $9.99 etc because it just seems less than a 10 spot. Still don't get much useful change though.

    Do you know if there is anything in Perl that implements the standards. A Math::AcurateFloatingPoint class would be quite possible using Math::BigInt without too much work. You just need to delta to integers for the operations and then return the result as a float object (stringified of course to preserve accuracy). I don't know if there would be much demand though.

    The stats about > 50% of database table numerical columns using floats (including financials) was interesting.

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

      I think that might have already been done...in the Math::BigInt package we find the interesting Math::BigFloat. These modules will obey either precision (i.e., number of digits beyond the decimal point) as well as accuracy (i.e., significant digits/figures). That's their choice of terminology, precision vs. accuracy. I'm not sure if they properly apply the rules for sig figs when you get a new float from an operation involving two values with different sig figs.

      I /msg'd you about this, but as I've thought more about this I like the name snap() for the function in question (yea, even better than fudge()). Because really, what's happening is a gravitation towards more psychologically appealing numbers -- no different than applying a layout grid on a canvas, and having things 'snap' towards the grid lines.

      The problem with the BigInt stuff is probably always going to be speed (this is speculation on my part). Perhaps you can use native floats unless specified otherwise, in which case the operations shift into Math::BigFloat mode. (this should be reasonably transparent since the BigInt modules override operators for DWIMery).

      Oh, also -- I found Math::FixedPrecision, which is built on top of Math::BigFloat. It appears to simplify precision math, i.e., nail down the number of decimal places. I see an analagous need here for a 'Math::SigFig' or somesuch. I notice that John Peacock, the author of Math::FixedPrecision, is also the author of Math::Currency -- one obvious application of precision arithmetic vs. significant figures.

      Matt

        For your enjoyment I present Math::SnapTo. Coming to a CPAN mirror near you soon.

        package Math::SnapTo; #use strict; # used in testing but not forced on you #use warnings; require Exporter; use Carp; use vars qw( @ISA @EXPORT_OK $VERSION ); @ISA = qw(Exporter); @EXPORT_OK = qw( snap_basic snap_sigfig snap_units ); $VERSION = '0.01'; sub snap_units { my ( $num, $units ) = @_; confess "Need units to snap to!" unless $units; # integer case if ( $units == int $units ) { if ( my $remainder = $num % $units ) { $num -= $remainder; $num += $units if $remainder >= $units/2; } return $num; } # fractional case else { my $factor = 1/$units; my $int = int ( $num * $factor ); my $remainder = ( $num * $factor ) - $int; $int += 1 if $remainder >= 0.5; return $int / $factor; } } sub snap_sigfig { my ( $num, $sig_figs ) = @_; $sig_figs ||= 6; my $exp; # integer case if ( $num =~ m/^(\d{$sig_figs})(\d*)$/ ) { $num = $1 . ( $2 ? '0' x length $2 : '' ); # round up if required if ( defined $2 and length $2 ) { my $compare_to = (10**(length $2))/2; my $round = $2 >= $compare_to ? 10**length $2 : 0; $num += $round; } return $num; } # float case elsif ( $num =~ m/^(\d*)\.(\d+)$/ and length $num > $sig_figs +1 ) + { # make it an integer case and recurse my $exp = 10 ** length $2; my $int = $num * $exp; return snap_sigfig( $int, $sig_figs ) / $exp; } # default case else { return $num; } } sub snap_basic { my ( $num, $accuracy ) = @_; $accuracy ||= 6; # 9s split across decimal point if ( $num =~ m/(\d*?)(9+)\.(9+)\d*/ and (defined $2 ? length $2 : +0)+( defined $3 ? length $3 : 0 ) >= $accuracy ) { return int($num +1 ); } # 9s after decimal point elsif ( $num =~ m/\d*\.(\d*?)(9{$accuracy,})\d*/ ) { my $pre = defined $1 ? length $1 : 0; my $exp = 10** ($pre + length $2); return int(($num * $exp) +1 )/$exp; } # 9s before decimal point elsif ( $num =~ m/\d*?(9{$accuracy,})(\d*)\.?d*/ ) { my $exp = defined $2 ? length $2 : 0; return $num += 10**$exp; } # 0s split across decimal point if ( $num =~ m/(\d*?)(0+)\.(0+)\d*/ and (defined $2 ? length $2 : +0)+( defined $3 ? length $3 : 0 ) >= $accuracy ) { return $1 . $2; } # 0s after decimal point elsif ( $num =~ m/(\d*)\.(\d*?)(0{$accuracy,})\d*/ ) { return ( $1 ? $1 : '0' ) . ( $2 ? ".$2" : '' ); } # 0s before decimal point elsif ( $num =~ m/(\d*?)(0{$accuracy,})(\d*)\.?d*/ ) { return ( $1 ? $1 : '' ) . ( $2 ? $2 : '' ) . ( $3 ? '0' x leng +th $3 : ''); } else { return $num; } } 1; __END__ =head1 NAME Math::Snap - Perl extension providing numeric snap-to functions =head1 SYNOPSIS use Math::SnapTo qw( snap_basic snap_sigfig snap_units ); =head1 DESCRIPTION The Math::SnapTo module provides several methods to snap numeric value +s to desired values according to various criteria. =head2 FUNCTIONS This module supplies three functions. None are exported by default so +you have to specify what you want in the use or use the fully quialified n +ame. For a detailed overview of how the functions behave run snap.test in t +he ./t directory $ perl snap.test | more =head3 snap_basic( [NUM], [ACCURACY] ); Something that has always anoyed me is how float operations return 0.499999999998 or 5.00000000001 when the actual value is 0.5. The snap_basic() function 'fixes' these ugly numbers in the same way y +ou tend to in your head. By default the approx sub will modify numbers so if we have a number like 0.499999945 with 6 9s or 0.50000012 with 6 0s the number will be rounded to 0.5. The snap_basic() function also takes a second optional argument that s +pecifies how many 0s or 9s in a row will trigger rounding. For want of a better + term I call this the accuracy. The default is 6. snap_basic($num, 7); # will return 0.5 for 0.500000001 but 0.5000 +0001 if # that is passed as it only has 6 zeros. It should be noted that this is largely a cosmetic function rather tha +n an implementation of the IEEE 754 standard. Any sequence of (default 6) 0 +s or 9s will trigger the modification so these modifications will occur: snap_basic(999999) == 1000000; snap_basic(0.999999) == 1; snap_basic(999.999) == 1000; Numbers that do not fulfill the requisite criteria are returned unchan +ged. For example 0.5000001 will not be rounded to 0.5 as it only has 5 0s. =head3 snap_sigfig( [NUMBER], [SIGNIFICANT_FIGURES] ) The snap_sigfig() function returns its integer or float argument modif +ied to the desired number of significant figures. If the SIGNIFICANT_FIGURES +argument is not supplied a value of 6 is used. Appropriate rounding is performe +d. snap_sigfig(999999) == 1000000; snap_sigfig(555555,4) == 555600; snap_sigfig(0.555555,4) == 0.5556; =head3 snap_units( [NUMBER], [UNITS} ) The snap_units() function returns its integer or float argument modifi +ed so that NUMBER % UNITS == 0. UNITS can be an integer or a float. Rounding up occurs if NUMBER % UNITS >= UNITS / 2 snap_units(13,4) == 12 snap_units(14,4) == 16 ; snap_units(15,4) == 16 ; snap_units(16,4) == 16 ; snap_units(17,4) == 16 ; snap_units(18,4) == 20 ; snap_units(0.75, 0.25) == 0.75; snap_units(0.87, 0.25) == 0.75; snap_units(0.88, 0.25) == 1; =head2 EXPORT None by default. =head1 AUTHOR Dr James Freeman, E<lt>james.freeman@id3.org.ukE<gt> =cut

        cheers

        tachyon

        s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://222154]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others studying the Monastery: (4)
As of 2024-03-29 04:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found