[Python-ideas] real numbers with SI scale factors: next steps

Ken Kundert python-ideas at shalmirane.com
Wed Aug 31 03:47:45 EDT 2016


Thanks Chris.

I had misunderstood Steve's request, and I was thinking of something much more 
complicated.

Your code is very helpful.

-Ken


On Wed, Aug 31, 2016 at 05:07:11PM +1000, Chris Angelico wrote:
> On Wed, Aug 31, 2016 at 2:08 PM, Ken Kundert
> <python-ideas at shalmirane.com> wrote:
> > > What's the mnemonic here? Why "r" for scale factor?
> >
> > My thinking was that r stands for real like f stands for float.
> > With the base 2 scale factors, b stands for binary.
> 
> "Real" has historically often been a synonym for "float", and it
> doesn't really say that it'll be shown in engineering notation. But
> then, we currently have format codes 'e', 'f', and 'g', and I don't
> think there's much logic there beyond "exponential", "floating-point",
> and... "general format"? I think that's a back-formation, frankly, and
> 'g' was used simply because it comes nicely after 'e' and 'f'. (C's
> decision, not Python's, fwiw.) I'll stick with 'r' for now, but it
> could just as easily become 'h' to avoid confusion with %r for repr.
> 
> >> (2) Support for full prefix names, so we can format (say) "kilograms" as well
> >> as "kg"?
> >
> > This assumes that somehow this code can access the units so that it can switch
> > between long form 'grams' and short form 'g'. That is a huge expansion in the
> > complexity for what seems like a small benefit.
> >
> 
> AIUI, it's just giving the full word.
> 
> class ScaledNumber(float):
>     invert = {"μ": 1e6, "m": 1e3, "": 1, "k": 1e-3, "M": 1e-6}
>     words = {"μ": "micro", "m": "milli", "": "", "k": "kilo", "M": "mega"}
>     aliases = {"u": "μ"}
>     def autoscale(self):
>         if self < 1e-6: return None
>         if self < 1e-3: return "μ"
>         if self < 1: return "m"
>         if self < 1e3: return ""
>         if self < 1e6: return "k"
>         if self < 1e9: return "M"
>         return None
>     def __format__(self, fmt):
>         if fmt == "r" or fmt == "R":
>             scale = self.autoscale()
>             fmt = fmt + scale if scale else "f"
>         if fmt.startswith("r"):
>             scale = self.aliases.get(fmt[1], fmt[1])
>             return "%g%s" % (self * self.invert[scale], scale)
>         if fmt.startswith("R"):
>             scale = self.aliases.get(fmt[1], fmt[1])
>             return "%g %s" % (self * self.invert[scale], self.words[scale])
>         return super().__format__(self, fmt)
> 
> >>> range = ScaledNumber(50e3)
> >>> print('Attenuation = {:.1f} dB at {:r}m.'.format(-13.7, range))
> Attenuation = -13.7 dB at 50km.
> >>> print('Attenuation = {:.1f} dB at {:R}meters.'.format(-13.7, range))
> Attenuation = -13.7 dB at 50 kilometers.
> >>> print('Attenuation = {:.1f} dB at {:rM}m.'.format(-13.7, range))
> Attenuation = -13.7 dB at 0.05Mm.
> >>> print('Attenuation = {:.1f} dB at {:RM}meters.'.format(-13.7, range))
> Attenuation = -13.7 dB at 0.05 megameters.
> 
> It's a minor flexibility, but could be very useful. As you see, it's
> still not at all unit-aware; but grammatically, these formats only
> make sense if followed by an actual unit name. (And not an SI base
> unit, necessarily - you have to use "gram", not "kilogram", lest you
> get silly constructs like "microkilogram" for milligram.)
> 
> Note that this *already works*. You do have to use an explicit class
> for your scaled numbers, since Python doesn't want you monkey-patching
> the built-in float type, but if you were to request that
> float.__format__ grow support for this, it'd be a relatively
> non-intrusive change. This class could live on PyPI until one day
> becoming subsumed into core, or just be a permanent third-party float
> formatting feature.
> 
> ChrisA
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/


More information about the Python-ideas mailing list